diff --git a/lib/internal/fs/cp/cp-sync.js b/lib/internal/fs/cp/cp-sync.js index 1e922b7805fc8c..9e67ae6335ec46 100644 --- a/lib/internal/fs/cp/cp-sync.js +++ b/lib/internal/fs/cp/cp-sync.js @@ -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, diff --git a/src/env-inl.h b/src/env-inl.h index 9b8d8f6ab070ff..8e72e0500ee656 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -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, diff --git a/src/env.h b/src/env.h index b428f5937b96fd..657c619a329b48 100644 --- a/src/env.h +++ b/src/env.h @@ -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, diff --git a/src/node_file.cc b/src/node_file.cc index 56b7a94ecdfe42..ba8a1c464d5799 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -43,6 +43,7 @@ #include "uv.h" #include "v8-fast-api-calls.h" +#include #include #if defined(__MINGW32__) || defined(_MSC_VER) @@ -3350,6 +3351,72 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { } } +static void CpSyncOverrideFile(const FunctionCallbackInfo& 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(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); diff --git a/typings/internalBinding/fs.d.ts b/typings/internalBinding/fs.d.ts index e666725d39ac77..c413c6969fe888 100644 --- a/typings/internalBinding/fs.d.ts +++ b/typings/internalBinding/fs.d.ts @@ -77,6 +77,7 @@ declare namespace InternalFSBinding { function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, usePromises: typeof kUsePromises): Promise; 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;