Skip to content

Commit e2396c6

Browse files
committed
fs: use libuv for recursive cpSync to avoid VirtioFS EACCES
fs.cpSync() recursive copy fails EACCES on Docker VirtioFS bind mounts. CopyDirRecursive()'s regular-file branch calls std::filesystem::copy_file(), which libstdc++ implements by creating dest with mode 0200 and then fchmod(). VirtioFS rejects that initial 0200 open with EACCES. The single-file cpSync path and the fresh-destination branch of CopyDirRecursive() already avoid this by using uv_fs_copyfile(), which opens dest with its final mode directly. Use uv_fs_copyfile() for the merge-into-existing-directory case too, preserving force, errorOnExist and skip-existing semantics via UV_FS_COPYFILE_EXCL and an existence check. Also drops the file_copy_opts capture, now unused once the regular-file branch no longer calls std::filesystem::copy_file(). Fixes: #65497 Signed-off-by: Jeremy Schoemaker <jeremy@shoemoney.com>
1 parent 9afe7dc commit e2396c6

1 file changed

Lines changed: 33 additions & 21 deletions

File tree

‎src/node_file.cc‎

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4827,18 +4827,9 @@ CpError CopyDirRecursive(const std::filesystem::path& src_path,
48274827
}
48284828
}
48294829

4830-
auto file_copy_opts = std::filesystem::copy_options::recursive;
4831-
if (options.force) {
4832-
file_copy_opts |= std::filesystem::copy_options::overwrite_existing;
4833-
} else if (options.error_on_exist) {
4834-
file_copy_opts |= std::filesystem::copy_options::none;
4835-
} else {
4836-
file_copy_opts |= std::filesystem::copy_options::skip_existing;
4837-
}
4838-
48394830
std::function<CpError(std::filesystem::path, std::filesystem::path)>
48404831
copy_dir_contents;
4841-
copy_dir_contents = [&options, &copy_dir_contents, file_copy_opts](
4832+
copy_dir_contents = [&options, &copy_dir_contents](
48424833
std::filesystem::path src,
48434834
std::filesystem::path dest) -> CpError {
48444835
std::error_code error;
@@ -4989,20 +4980,41 @@ CpError CopyDirRecursive(const std::filesystem::path& src_path,
49894980
CpError fresh = CopyFileFresh(
49904981
dir_entry.path(), dest_file_path, options.copyfile_flags);
49914982
if (fresh.kind != CpError::kNone) return fresh;
4983+
} else if (!options.force && !options.error_on_exist &&
4984+
std::filesystem::exists(dest_file_path, error)) {
4985+
copied = false;
49924986
} else {
4993-
copied = std::filesystem::copy_file(
4994-
dir_entry.path(), dest_file_path, file_copy_opts, error);
4987+
// uv_fs_copyfile() instead of std::filesystem::copy_file():
4988+
// libstdc++ implements copy_file() by creating dest with mode
4989+
// 0200 and then fchmod(), which VirtioFS bind mounts reject
4990+
// with EACCES. uv_fs_copyfile() opens dest with the final mode
4991+
// directly, matching the single-file cpSync path.
4992+
error.clear();
4993+
auto src_str = ConvertPathToUTF8(dir_entry.path());
4994+
auto dest_file_str = ConvertPathToUTF8(dest_file_path);
4995+
uv_fs_t req;
4996+
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
4997+
int flags = options.error_on_exist ? UV_FS_COPYFILE_EXCL : 0;
4998+
int rc = uv_fs_copyfile(nullptr,
4999+
&req,
5000+
src_str.c_str(),
5001+
dest_file_str.c_str(),
5002+
flags,
5003+
nullptr);
5004+
if (is_uv_error(rc)) {
5005+
if (rc == UV_EEXIST) {
5006+
return {CpError::kEexist,
5007+
0,
5008+
"cp",
5009+
SPrintF("[ERR_FS_CP_EEXIST]: Target already exists: "
5010+
"cp returned EEXIST (%s already exists)",
5011+
dest_file_path),
5012+
{}};
5013+
}
5014+
return CpError::Uv(rc, "cp", dest_file_str);
5015+
}
49955016
}
49965017
if (error) {
4997-
if (error == std::errc::file_exists) {
4998-
return {CpError::kEexist,
4999-
0,
5000-
"cp",
5001-
SPrintF("[ERR_FS_CP_EEXIST]: Target already exists: "
5002-
"cp returned EEXIST (%s already exists)",
5003-
dest_file_path),
5004-
{}};
5005-
}
50065018
return CpError::Std(error, dest_str);
50075019
}
50085020

0 commit comments

Comments
 (0)