Skip to content
Open
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
33 changes: 33 additions & 0 deletions benchmark/fs/bench-cp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

// fs.promises.cp() of a directory tree.

const common = require('../common');
const fs = require('fs');
const path = require('path');
const tmpdir = require('../../test/common/tmpdir');

const bench = common.createBenchmark(main, {
files: [500],
n: [3],
});

function prepareSource(files) {
const src = tmpdir.resolve('cp-src');
for (let i = 0; i < files; i++) {
const dir = path.join(src, `dir-${i % 10}`, `sub-${i % 7}`);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, `file-${i}.js`), 'x'.repeat(1024 + (i % 512)));
}
return src;
}

async function main({ files, n }) {
tmpdir.refresh();
const src = prepareSource(files);
bench.start();
for (let i = 0; i < n; i++) {
await fs.promises.cp(src, tmpdir.resolve(`cp-dest-${i}`), { recursive: true });
}
bench.end(n);
}
68 changes: 45 additions & 23 deletions lib/internal/fs/cp/cp.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const {
ArrayPrototypeEvery,
ArrayPrototypeFilter,
Boolean,
ErrorCaptureStackTrace,
Promise,
PromisePrototypeThen,
PromiseReject,
SafePromiseAll,
Expand Down Expand Up @@ -55,6 +57,7 @@ const {
sep,
} = require('path');
const fsBinding = internalBinding('fs');
const permission = require('internal/process/permission');

async function cpFn(src, dest, opts) {
// Warn about using preserveTimestamps on 32-bit node
Expand Down Expand Up @@ -211,30 +214,19 @@ async function getStatsForCopy(destStat, src, dest, opts) {
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',
});
}
throw new ERR_FS_CP_UNKNOWN({
message: `cannot copy an unknown file type: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
code: 'EINVAL',
});
throw errorForSpecialFile(srcStat.isSocket() ? 'socket' : srcStat.isFIFO() ? 'fifo' : 'unknown', dest);
}

function errorForSpecialFile(kind, dest) {
const info = { path: dest, syscall: 'cp', errno: EINVAL, code: 'EINVAL' };
if (kind === 'socket') {
return new ERR_FS_CP_SOCKET({ message: `cannot copy a socket file: ${dest}`, ...info });
}
if (kind === 'fifo') {
return new ERR_FS_CP_FIFO_PIPE({ message: `cannot copy a FIFO pipe: ${dest}`, ...info });
}
return new ERR_FS_CP_UNKNOWN({ message: `cannot copy an unknown file type: ${dest}`, ...info });
}

function onFile(srcStat, destStat, src, dest, opts) {
Expand Down Expand Up @@ -315,11 +307,41 @@ async function onDir(srcStat, destStat, src, dest, opts) {
}

async function mkDirAndCopy(srcMode, src, dest, opts) {
// A destination directory that does not exist yet is filled in one thread
// pool request by the walk fs.cpSync() uses, unless a filter has to run per
// entry, links inside the tree must be dereferenced, or the permission model
// has to check each path. Copying into an existing tree keeps the per-entry
// walk below and its rules for what may already be there.
if (!opts.filter && !opts.dereference && !permission.isEnabled()) {
// Creates dest itself, with the mode of src.
return copyDirNative(src, dest, opts);
}
await mkdir(dest);
await copyDir(src, dest, opts);
return setDestMode(dest, srcMode);
}

function copyDirNative(src, dest, opts) {
return new Promise((resolve, reject) => {
const job = new fsBinding.CpDirJob(src, dest, opts.force, opts.dereference, opts.errorOnExist,
opts.verbatimSymlinks, opts.preserveTimestamps);
// Sockets, FIFOs and unknown entries come back as (kind, path) so that
// they reject with the same errors as the walk above.
job.ondone = (err, specialFile, specialFilePath) => {
if (specialFile !== undefined) {
err = errorForSpecialFile(specialFile, specialFilePath);
}
if (err != null) {
ErrorCaptureStackTrace(err, copyDirNative);
reject(err);
} else {
resolve();
}
};
job.run();
});
}

async function copyDir(src, dest, opts) {
const dir = await opendir(src);

Expand Down
Loading
Loading