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

fs: optimize fs.cpSync js calls #53614

Merged
merged 1 commit into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 31 additions & 133 deletions lib/internal/fs/cp/cp-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,25 @@

// This file is a modified version of the fs-extra's copySync method.

const { areIdentical, isSrcSubdir } = require('internal/fs/cp/cp');
const fsBinding = internalBinding('fs');
const { isSrcSubdir } = require('internal/fs/cp/cp');
const { codes: {
ERR_FS_CP_DIR_TO_NON_DIR,
ERR_FS_CP_EEXIST,
ERR_FS_CP_EINVAL,
ERR_FS_CP_FIFO_PIPE,
ERR_FS_CP_NON_DIR_TO_DIR,
ERR_FS_CP_SOCKET,
ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY,
ERR_FS_CP_UNKNOWN,
ERR_FS_EISDIR,
ERR_INVALID_RETURN_VALUE,
} } = require('internal/errors');
const {
os: {
errno: {
EEXIST,
EISDIR,
EINVAL,
ENOTDIR,
},
},
} = internalBinding('constants');
const {
chmodSync,
copyFileSync,
existsSync,
lstatSync,
mkdirSync,
opendirSync,
Expand All @@ -42,7 +34,6 @@ const {
dirname,
isAbsolute,
join,
parse,
resolve,
} = require('path');
const { isPromise } = require('util/types');
Expand All @@ -54,152 +45,46 @@ function cpSyncFn(src, dest, opts) {
'node is not recommended';
process.emitWarning(warning, 'TimestampPrecisionWarning');
}
const { srcStat, destStat, skipped } = checkPathsSync(src, dest, opts);
if (skipped) return;
checkParentPathsSync(src, srcStat, dest);
return checkParentDir(destStat, src, dest, opts);
}

function checkPathsSync(src, dest, opts) {
if (opts.filter) {
const shouldCopy = opts.filter(src, dest);
if (isPromise(shouldCopy)) {
throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy);
}
if (!shouldCopy) return { __proto__: null, skipped: true };
if (!shouldCopy) return;
}
const { srcStat, destStat } = getStatsSync(src, dest, opts);

if (destStat) {
if (areIdentical(srcStat, destStat)) {
throw new ERR_FS_CP_EINVAL({
message: 'src and dest cannot be the same',
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new ERR_FS_CP_DIR_TO_NON_DIR({
message: `cannot overwrite non-directory ${dest} ` +
`with directory ${src}`,
path: dest,
syscall: 'cp',
errno: EISDIR,
code: 'EISDIR',
});
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new ERR_FS_CP_NON_DIR_TO_DIR({
message: `cannot overwrite directory ${dest} ` +
`with non-directory ${src}`,
path: dest,
syscall: 'cp',
errno: ENOTDIR,
code: 'ENOTDIR',
});
}
}

if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new ERR_FS_CP_EINVAL({
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
}
return { __proto__: null, srcStat, destStat, skipped: false };
}
fsBinding.cpSyncCheckPaths(src, dest, opts.dereference, opts.recursive);

function getStatsSync(src, dest, opts) {
const statFunc = opts.dereference ? statSync : lstatSync;
const srcStat = statFunc(src, { bigint: true, throwIfNoEntry: true });
const destStat = statFunc(dest, { bigint: true, throwIfNoEntry: false });
return { srcStat, destStat };
return getStats(src, dest, opts);
}

function checkParentPathsSync(src, srcStat, dest) {
const srcParent = resolve(dirname(src));
const destParent = resolve(dirname(dest));
if (destParent === srcParent || destParent === parse(destParent).root) return;
const destStat = statSync(destParent, { bigint: true, throwIfNoEntry: false });

if (destStat === undefined) {
return;
}

if (areIdentical(srcStat, destStat)) {
throw new ERR_FS_CP_EINVAL({
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
}
return checkParentPathsSync(src, srcStat, destParent);
}

function checkParentDir(destStat, src, dest, opts) {
const destParent = dirname(dest);
if (!existsSync(destParent)) mkdirSync(destParent, { recursive: true });
return getStats(destStat, src, dest, opts);
}

function getStats(destStat, src, dest, opts) {
function getStats(src, dest, opts) {
// TODO(@anonrig): Avoid making two stat calls.
const statSyncFn = opts.dereference ? statSync : lstatSync;
const srcStat = statSyncFn(src);
const destStat = statSyncFn(dest, { bigint: true, throwIfNoEntry: false });

if (srcStat.isDirectory() && opts.recursive) {
return onDir(srcStat, destStat, src, dest, opts);
} else if (srcStat.isDirectory()) {
throw new ERR_FS_EISDIR({
message: `${src} is a directory (not copied)`,
path: src,
syscall: 'cp',
errno: EINVAL,
code: 'EISDIR',
});
} else if (srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()) {
return onFile(srcStat, destStat, src, dest, opts);
} else if (srcStat.isSymbolicLink()) {
return onLink(destStat, src, dest, opts);
} else if (srcStat.isSocket()) {
throw new ERR_FS_CP_SOCKET({
message: `cannot copy a socket file: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
} else if (srcStat.isFIFO()) {
throw new ERR_FS_CP_FIFO_PIPE({
message: `cannot copy a FIFO pipe: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
return onLink(destStat, src, dest, opts.verbatimSymlinks);
}
throw new ERR_FS_CP_UNKNOWN({
message: `cannot copy an unknown file type: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});

// It is not possible to get here because all possible cases are handled above.
const assert = require('internal/assert');
assert.fail('Unreachable code');
}

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);
Expand Down Expand Up @@ -249,6 +134,7 @@ 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 mkDirAndCopy(srcStat.mode, src, dest, opts);
return copyDir(src, dest, opts);
Expand All @@ -260,6 +146,7 @@ function mkDirAndCopy(srcMode, src, dest, opts) {
return setDestMode(dest, srcMode);
}

// TODO(@anonrig): Move this function to C++.
function copyDir(src, dest, opts) {
const dir = opendirSync(src);

Expand All @@ -270,17 +157,28 @@ function copyDir(src, dest, opts) {
const { name } = dirent;
const srcItem = join(src, name);
const destItem = join(dest, name);
const { destStat, skipped } = checkPathsSync(srcItem, destItem, opts);
if (!skipped) getStats(destStat, srcItem, destItem, opts);
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();
}
}

function onLink(destStat, src, dest, opts) {
// TODO(@anonrig): Move this function to C++.
function onLink(destStat, src, dest, verbatimSymlinks) {
let resolvedSrc = readlinkSync(src);
if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) {
if (!verbatimSymlinks && !isAbsolute(resolvedSrc)) {
resolvedSrc = resolve(dirname(src), resolvedSrc);
}
if (!destStat) {
Expand Down
6 changes: 6 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
V(ERR_DLOPEN_FAILED, Error) \
V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
V(ERR_FS_CP_EINVAL, Error) \
V(ERR_FS_CP_DIR_TO_NON_DIR, Error) \
V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \
V(ERR_FS_EISDIR, Error) \
V(ERR_FS_CP_SOCKET, Error) \
V(ERR_FS_CP_FIFO_PIPE, Error) \
V(ERR_FS_CP_UNKNOWN, Error) \
V(ERR_ILLEGAL_CONSTRUCTOR, Error) \
V(ERR_INVALID_ADDRESS, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
Expand Down
Loading
Loading