Skip to content
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