Skip to content

Commit e2c27ae

Browse files
committed
fs: optimize fs.cpSync js calls
1 parent 1fb23f1 commit e2c27ae

File tree

4 files changed

+168
-133
lines changed

4 files changed

+168
-133
lines changed

lib/internal/fs/cp/cp-sync.js

+31-133
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,25 @@
22

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

5-
const { areIdentical, isSrcSubdir } = require('internal/fs/cp/cp');
5+
const fsBinding = internalBinding('fs');
6+
const { isSrcSubdir } = require('internal/fs/cp/cp');
67
const { codes: {
7-
ERR_FS_CP_DIR_TO_NON_DIR,
88
ERR_FS_CP_EEXIST,
99
ERR_FS_CP_EINVAL,
10-
ERR_FS_CP_FIFO_PIPE,
11-
ERR_FS_CP_NON_DIR_TO_DIR,
12-
ERR_FS_CP_SOCKET,
1310
ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY,
14-
ERR_FS_CP_UNKNOWN,
15-
ERR_FS_EISDIR,
1611
ERR_INVALID_RETURN_VALUE,
1712
} } = require('internal/errors');
1813
const {
1914
os: {
2015
errno: {
2116
EEXIST,
22-
EISDIR,
2317
EINVAL,
24-
ENOTDIR,
2518
},
2619
},
2720
} = internalBinding('constants');
2821
const {
2922
chmodSync,
3023
copyFileSync,
31-
existsSync,
3224
lstatSync,
3325
mkdirSync,
3426
opendirSync,
@@ -42,7 +34,6 @@ const {
4234
dirname,
4335
isAbsolute,
4436
join,
45-
parse,
4637
resolve,
4738
} = require('path');
4839
const { isPromise } = require('util/types');
@@ -54,152 +45,46 @@ function cpSyncFn(src, dest, opts) {
5445
'node is not recommended';
5546
process.emitWarning(warning, 'TimestampPrecisionWarning');
5647
}
57-
const { srcStat, destStat, skipped } = checkPathsSync(src, dest, opts);
58-
if (skipped) return;
59-
checkParentPathsSync(src, srcStat, dest);
60-
return checkParentDir(destStat, src, dest, opts);
61-
}
62-
63-
function checkPathsSync(src, dest, opts) {
6448
if (opts.filter) {
6549
const shouldCopy = opts.filter(src, dest);
6650
if (isPromise(shouldCopy)) {
6751
throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy);
6852
}
69-
if (!shouldCopy) return { __proto__: null, skipped: true };
53+
if (!shouldCopy) return;
7054
}
71-
const { srcStat, destStat } = getStatsSync(src, dest, opts);
7255

73-
if (destStat) {
74-
if (areIdentical(srcStat, destStat)) {
75-
throw new ERR_FS_CP_EINVAL({
76-
message: 'src and dest cannot be the same',
77-
path: dest,
78-
syscall: 'cp',
79-
errno: EINVAL,
80-
code: 'EINVAL',
81-
});
82-
}
83-
if (srcStat.isDirectory() && !destStat.isDirectory()) {
84-
throw new ERR_FS_CP_DIR_TO_NON_DIR({
85-
message: `cannot overwrite non-directory ${dest} ` +
86-
`with directory ${src}`,
87-
path: dest,
88-
syscall: 'cp',
89-
errno: EISDIR,
90-
code: 'EISDIR',
91-
});
92-
}
93-
if (!srcStat.isDirectory() && destStat.isDirectory()) {
94-
throw new ERR_FS_CP_NON_DIR_TO_DIR({
95-
message: `cannot overwrite directory ${dest} ` +
96-
`with non-directory ${src}`,
97-
path: dest,
98-
syscall: 'cp',
99-
errno: ENOTDIR,
100-
code: 'ENOTDIR',
101-
});
102-
}
103-
}
104-
105-
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
106-
throw new ERR_FS_CP_EINVAL({
107-
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
108-
path: dest,
109-
syscall: 'cp',
110-
errno: EINVAL,
111-
code: 'EINVAL',
112-
});
113-
}
114-
return { __proto__: null, srcStat, destStat, skipped: false };
115-
}
56+
fsBinding.cpSyncCheckPaths(src, dest, opts.dereference, opts.recursive);
11657

117-
function getStatsSync(src, dest, opts) {
118-
const statFunc = opts.dereference ? statSync : lstatSync;
119-
const srcStat = statFunc(src, { bigint: true, throwIfNoEntry: true });
120-
const destStat = statFunc(dest, { bigint: true, throwIfNoEntry: false });
121-
return { srcStat, destStat };
58+
return getStats(src, dest, opts);
12259
}
12360

124-
function checkParentPathsSync(src, srcStat, dest) {
125-
const srcParent = resolve(dirname(src));
126-
const destParent = resolve(dirname(dest));
127-
if (destParent === srcParent || destParent === parse(destParent).root) return;
128-
const destStat = statSync(destParent, { bigint: true, throwIfNoEntry: false });
129-
130-
if (destStat === undefined) {
131-
return;
132-
}
133-
134-
if (areIdentical(srcStat, destStat)) {
135-
throw new ERR_FS_CP_EINVAL({
136-
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
137-
path: dest,
138-
syscall: 'cp',
139-
errno: EINVAL,
140-
code: 'EINVAL',
141-
});
142-
}
143-
return checkParentPathsSync(src, srcStat, destParent);
144-
}
145-
146-
function checkParentDir(destStat, src, dest, opts) {
147-
const destParent = dirname(dest);
148-
if (!existsSync(destParent)) mkdirSync(destParent, { recursive: true });
149-
return getStats(destStat, src, dest, opts);
150-
}
151-
152-
function getStats(destStat, src, dest, opts) {
61+
function getStats(src, dest, opts) {
62+
// TODO(@anonrig): Avoid making two stat calls.
15363
const statSyncFn = opts.dereference ? statSync : lstatSync;
15464
const srcStat = statSyncFn(src);
65+
const destStat = statSyncFn(dest, { bigint: true, throwIfNoEntry: false });
15566

15667
if (srcStat.isDirectory() && opts.recursive) {
15768
return onDir(srcStat, destStat, src, dest, opts);
158-
} else if (srcStat.isDirectory()) {
159-
throw new ERR_FS_EISDIR({
160-
message: `${src} is a directory (not copied)`,
161-
path: src,
162-
syscall: 'cp',
163-
errno: EINVAL,
164-
code: 'EISDIR',
165-
});
16669
} else if (srcStat.isFile() ||
16770
srcStat.isCharacterDevice() ||
16871
srcStat.isBlockDevice()) {
16972
return onFile(srcStat, destStat, src, dest, opts);
17073
} else if (srcStat.isSymbolicLink()) {
171-
return onLink(destStat, src, dest, opts);
172-
} else if (srcStat.isSocket()) {
173-
throw new ERR_FS_CP_SOCKET({
174-
message: `cannot copy a socket file: ${dest}`,
175-
path: dest,
176-
syscall: 'cp',
177-
errno: EINVAL,
178-
code: 'EINVAL',
179-
});
180-
} else if (srcStat.isFIFO()) {
181-
throw new ERR_FS_CP_FIFO_PIPE({
182-
message: `cannot copy a FIFO pipe: ${dest}`,
183-
path: dest,
184-
syscall: 'cp',
185-
errno: EINVAL,
186-
code: 'EINVAL',
187-
});
74+
return onLink(destStat, src, dest, opts.verbatimSymlinks);
18875
}
189-
throw new ERR_FS_CP_UNKNOWN({
190-
message: `cannot copy an unknown file type: ${dest}`,
191-
path: dest,
192-
syscall: 'cp',
193-
errno: EINVAL,
194-
code: 'EINVAL',
195-
});
76+
77+
// It is not possible to get here because all possible cases are handled above.
78+
const assert = require('internal/assert');
79+
assert.fail('Unreachable code');
19680
}
19781

19882
function onFile(srcStat, destStat, src, dest, opts) {
19983
if (!destStat) return copyFile(srcStat, src, dest, opts);
20084
return mayCopyFile(srcStat, src, dest, opts);
20185
}
20286

87+
// TODO(@anonrig): Move this function to C++.
20388
function mayCopyFile(srcStat, src, dest, opts) {
20489
if (opts.force) {
20590
unlinkSync(dest);
@@ -249,6 +134,7 @@ function setDestTimestamps(src, dest) {
249134
return utimesSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime);
250135
}
251136

137+
// TODO(@anonrig): Move this function to C++.
252138
function onDir(srcStat, destStat, src, dest, opts) {
253139
if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts);
254140
return copyDir(src, dest, opts);
@@ -260,6 +146,7 @@ function mkDirAndCopy(srcMode, src, dest, opts) {
260146
return setDestMode(dest, srcMode);
261147
}
262148

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

@@ -270,17 +157,28 @@ function copyDir(src, dest, opts) {
270157
const { name } = dirent;
271158
const srcItem = join(src, name);
272159
const destItem = join(dest, name);
273-
const { destStat, skipped } = checkPathsSync(srcItem, destItem, opts);
274-
if (!skipped) getStats(destStat, srcItem, destItem, opts);
160+
let shouldCopy = true;
161+
162+
if (opts.filter) {
163+
shouldCopy = opts.filter(srcItem, destItem);
164+
if (isPromise(shouldCopy)) {
165+
throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy);
166+
}
167+
}
168+
169+
if (shouldCopy) {
170+
getStats(srcItem, destItem, opts);
171+
}
275172
}
276173
} finally {
277174
dir.closeSync();
278175
}
279176
}
280177

281-
function onLink(destStat, src, dest, opts) {
178+
// TODO(@anonrig): Move this function to C++.
179+
function onLink(destStat, src, dest, verbatimSymlinks) {
282180
let resolvedSrc = readlinkSync(src);
283-
if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) {
181+
if (!verbatimSymlinks && !isAbsolute(resolvedSrc)) {
284182
resolvedSrc = resolve(dirname(src), resolvedSrc);
285183
}
286184
if (!destStat) {

src/node_errors.h

+6
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
7070
V(ERR_DLOPEN_FAILED, Error) \
7171
V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \
7272
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
73+
V(ERR_FS_CP_EINVAL, Error) \
74+
V(ERR_FS_CP_DIR_TO_NON_DIR, Error) \
75+
V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \
7376
V(ERR_FS_EISDIR, Error) \
77+
V(ERR_FS_CP_SOCKET, Error) \
78+
V(ERR_FS_CP_FIFO_PIPE, Error) \
79+
V(ERR_FS_CP_UNKNOWN, Error) \
7480
V(ERR_ILLEGAL_CONSTRUCTOR, Error) \
7581
V(ERR_INVALID_ADDRESS, Error) \
7682
V(ERR_INVALID_ARG_VALUE, TypeError) \

0 commit comments

Comments
 (0)