Skip to content

src: move cpSync dir copy logic completely to C++ #58624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
65 changes: 7 additions & 58 deletions lib/internal/fs/cp/cp-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ const {
chmodSync,
copyFileSync,
lstatSync,
mkdirSync,
opendirSync,
readlinkSync,
statSync,
symlinkSync,
Expand All @@ -33,7 +31,6 @@ const {
const {
dirname,
isAbsolute,
join,
resolve,
} = require('path');
const { isPromise } = require('util/types');
Expand Down Expand Up @@ -65,7 +62,13 @@ function getStats(src, dest, opts) {
const destStat = statSyncFn(dest, { bigint: true, throwIfNoEntry: false });

if (srcStat.isDirectory() && opts.recursive) {
return onDir(srcStat, destStat, src, dest, opts);
return fsBinding.cpSyncCopyDir(src, dest,
opts.force,
opts.dereference,
opts.errorOnExist,
opts.verbatimSymlinks,
opts.preserveTimestamps,
opts.filter);
} else if (srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()) {
Expand Down Expand Up @@ -131,60 +134,6 @@ function setDestTimestamps(src, dest) {
return utimesSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime);
}

// TODO(@anonrig): Move this function to C++.
function onDir(srcStat, destStat, src, dest, opts) {
if (!destStat) return copyDir(src, dest, opts, true, srcStat.mode);
return copyDir(src, dest, opts);
}

function copyDir(src, dest, opts, mkDir, srcMode) {
if (!opts.filter) {
// The caller didn't provide a js filter function, in this case
// we can run the whole function faster in C++
// TODO(dario-piotrowicz): look into making cpSyncCopyDir also accept the potential filter function
return fsBinding.cpSyncCopyDir(src, dest,
opts.force,
opts.dereference,
opts.errorOnExist,
opts.verbatimSymlinks,
opts.preserveTimestamps);
}

if (mkDir) {
mkdirSync(dest);
}

const dir = opendirSync(src);

try {
let dirent;

while ((dirent = dir.readSync()) !== null) {
const { name } = dirent;
const srcItem = join(src, name);
const destItem = join(dest, name);
let shouldCopy = true;

if (opts.filter) {
shouldCopy = opts.filter(srcItem, destItem);
if (isPromise(shouldCopy)) {
throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy);
}
}

if (shouldCopy) {
getStats(srcItem, destItem, opts);
}
}
} finally {
dir.closeSync();

if (srcMode !== undefined) {
setDestMode(dest, srcMode);
}
}
}

// TODO(@anonrig): Move this function to C++.
function onLink(destStat, src, dest, verbatimSymlinks) {
let resolvedSrc = readlinkSync(src);
Expand Down
52 changes: 47 additions & 5 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3428,8 +3428,9 @@ bool isInsideDir(const std::filesystem::path& src,
}

static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(args.Length(), 7); // src, dest, force, dereference, errorOnExist,
// verbatimSymlinks, preserveTimestamps
CHECK_EQ(args.Length(),
8); // src, dest, force, dereference, errorOnExist,
// verbatimSymlinks, preserveTimestamps, filterFunction

Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
Expand All @@ -3448,6 +3449,40 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
bool verbatim_symlinks = args[5]->IsTrue();
bool preserve_timestamps = args[6]->IsTrue();

std::optional<std::function<bool(std::string_view, std::string_view)>>
filter_fn;

if (args[7]->IsFunction()) {
Local<v8::Function> args_filter_fn = args[7].As<v8::Function>();

filter_fn = [env, args_filter_fn](std::string_view src,
std::string_view dest) -> bool {
Local<String> src_arg;
Local<String> dest_arg;

if (!String::NewFromUtf8(
env->isolate(), src.data(), v8::NewStringType::kNormal)
.ToLocal(&src_arg) ||
!String::NewFromUtf8(
env->isolate(), dest.data(), v8::NewStringType::kNormal)
.ToLocal(&dest_arg)) {
// if for some reason we fail to load the src or dest strings
// just skip the filtering function and allow the copy
return true;
}

Local<Value> argv[] = {src_arg, dest_arg};

Local<Value> result;
if (!args_filter_fn->Call(env->context(), Null(env->isolate()), 2, argv)
.ToLocal(&result)) {
// if the call failed for whatever reason allow the copy
return true;
}
return result->BooleanValue(env->isolate());
};
}

std::error_code error;
std::filesystem::create_directories(*dest, error);
if (error) {
Expand All @@ -3473,11 +3508,19 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
force,
error_on_exist,
dereference,
&isolate](std::filesystem::path src,
std::filesystem::path dest) {
&isolate,
&filter_fn](std::filesystem::path src,
std::filesystem::path dest) {
std::error_code error;
for (auto dir_entry : std::filesystem::directory_iterator(src)) {
auto dir_entry_path_str = PathToString(dir_entry.path());
auto dest_file_path = dest / dir_entry.path().filename();
auto dest_file_path_str = PathToString(dest_file_path);

if (filter_fn && !(*filter_fn)(dir_entry_path_str, dest_file_path_str)) {
continue;
}

auto dest_str = PathToString(dest);

if (dir_entry.is_symlink()) {
Expand Down Expand Up @@ -3541,7 +3584,6 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
}
} else if (std::filesystem::is_regular_file(dest_file_path)) {
if (!dereference || (!force && error_on_exist)) {
auto dest_file_path_str = PathToString(dest_file_path);
env->ThrowStdErrException(
std::make_error_code(std::errc::file_exists),
"cp",
Expand Down
Loading