Repository: nodejs/node
Pull request: fs: improve `cpSync` dest overriding performance

move the logic in `cpSync` that overrides existing dest files from JavaScript to C++ increasing its performance

___

This is the type of perf improvement that this change makes:
![Screenshot at 2025-05-13 01-47-25](https://github.com/user-attachments/assets/31b0fbc9-096c-4218-a972-db946225ebd6)




<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/fs/cp/cp-sync.js
patch: @@ -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,

filename: src/env-inl.h
patch: @@ -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,

filename: src/env.h
patch: @@ -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,

filename: src/node_file.cc
patch: @@ -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);

filename: typings/internalBinding/fs.d.ts
patch: @@ -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;