fs: improve cpSync dest overriding performance

move the logic in `cpSync` that overrides existing
dest files from JavaScript to C++ increasing its performance

PR-URL: https://github.com/nodejs/node/pull/58160
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
Dario Piotrowicz 2025-05-23 13:26:04 +01:00 committed by GitHub
parent d96d57d5a7
commit 1a5ab963b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 85 additions and 7 deletions

View File

@ -81,15 +81,12 @@ function getStats(src, dest, opts) {
function onFile(srcStat, destStat, src, dest, opts) {
if (!destStat) return copyFile(srcStat, src, dest, opts);
return mayCopyFile(srcStat, src, dest, opts);
}
// TODO(@anonrig): Move this function to C++.
function mayCopyFile(srcStat, src, dest, opts) {
if (opts.force) {
unlinkSync(dest);
return copyFile(srcStat, src, dest, opts);
} else if (opts.errorOnExist) {
return fsBinding.cpSyncOverrideFile(src, dest, opts.mode, opts.preserveTimestamps);
}
if (opts.errorOnExist) {
throw new ERR_FS_CP_EEXIST({
message: `${dest} already exists`,
path: dest,

View File

@ -775,6 +775,13 @@ inline void Environment::ThrowError(
isolate()->ThrowException(fun(OneByteString(isolate(), errmsg), {}));
}
inline void Environment::ThrowStdErrException(std::error_code error_code,
const char* syscall,
const char* path) {
ThrowErrnoException(
error_code.value(), syscall, error_code.message().c_str(), path);
}
inline void Environment::ThrowErrnoException(int errorno,
const char* syscall,
const char* message,

View File

@ -830,6 +830,9 @@ class Environment final : public MemoryRetainer {
inline void ThrowError(const char* errmsg);
inline void ThrowTypeError(const char* errmsg);
inline void ThrowRangeError(const char* errmsg);
inline void ThrowStdErrException(std::error_code error_code,
const char* syscall,
const char* path = nullptr);
inline void ThrowErrnoException(int errorno,
const char* syscall = nullptr,
const char* message = nullptr,

View File

@ -43,6 +43,7 @@
#include "uv.h"
#include "v8-fast-api-calls.h"
#include <cstdio>
#include <filesystem>
#if defined(__MINGW32__) || defined(_MSC_VER)
@ -3350,6 +3351,72 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
}
}
static void CpSyncOverrideFile(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
CHECK_EQ(args.Length(), 4); // src, dest, mode, preserveTimestamps
BufferValue src(isolate, args[0]);
CHECK_NOT_NULL(*src);
ToNamespacedPath(env, &src);
BufferValue dest(isolate, args[1]);
CHECK_NOT_NULL(*dest);
ToNamespacedPath(env, &dest);
int mode;
if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&mode)) {
return;
}
bool preserve_timestamps = args[3]->IsTrue();
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
std::error_code error;
if (!std::filesystem::remove(*dest, error)) {
return env->ThrowStdErrException(error, "unlink", *dest);
}
if (mode == 0) {
// if no mode is specified use the faster std::filesystem API
if (!std::filesystem::copy_file(*src, *dest, error)) {
return env->ThrowStdErrException(error, "cp", *dest);
}
} else {
uv_fs_t req;
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
auto result = uv_fs_copyfile(nullptr, &req, *src, *dest, mode, nullptr);
if (is_uv_error(result)) {
return env->ThrowUVException(result, "cp", nullptr, *src, *dest);
}
}
if (preserve_timestamps) {
uv_fs_t req;
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
int result = uv_fs_stat(nullptr, &req, *src, nullptr);
if (is_uv_error(result)) {
return env->ThrowUVException(result, "stat", nullptr, *src);
}
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
const double source_atime = s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9;
const double source_mtime = s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9;
int utime_result =
uv_fs_utime(nullptr, &req, *dest, source_atime, source_mtime, nullptr);
if (is_uv_error(utime_result)) {
return env->ThrowUVException(utime_result, "utime", nullptr, *dest);
}
}
}
BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
Environment* env, const std::string& file_path) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
@ -3689,6 +3756,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
StatWatcher::CreatePerIsolateProperties(isolate_data, target);
BindingData::CreatePerIsolateProperties(isolate_data, target);
@ -3801,6 +3869,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(CopyFile);
registry->Register(CpSyncCheckPaths);
registry->Register(CpSyncOverrideFile);
registry->Register(Chmod);
registry->Register(FChmod);

View File

@ -77,6 +77,7 @@ declare namespace InternalFSBinding {
function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, usePromises: typeof kUsePromises): Promise<void>;
function cpSyncCheckPaths(src: StringOrBuffer, dest: StringOrBuffer, dereference: boolean, recursive: boolean): void;
function cpSyncOverrideFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, preserveTimestamps: boolean): void;
function fchmod(fd: number, mode: number, req: FSReqCallback): void;
function fchmod(fd: number, mode: number): void;
@ -260,6 +261,7 @@ export interface FsBinding {
close: typeof InternalFSBinding.close;
copyFile: typeof InternalFSBinding.copyFile;
cpSyncCheckPaths: typeof InternalFSBinding.cpSyncCheckPaths;
cpSyncOverrideFile: typeof InternalFSBinding.cpSyncOverrideFile;
fchmod: typeof InternalFSBinding.fchmod;
fchown: typeof InternalFSBinding.fchown;
fdatasync: typeof InternalFSBinding.fdatasync;