Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a54e898

Browse filesBrowse files
tetsuharuohzekitargos
authored andcommitted
fs: add support for mode flag to specify the copy behavior
`fs.copyFile()` supports copy-on-write operation if the underlying platform supports it by passing a mode flag. This behavior was added in a16d88d. This patch adds `mode` flag to `fs.cp()`, `fs.cpSync()`, and `fsPromises.cp()` to allow to change their behaviors to copy files. This test case is based on the test case that was introduced when we add `fs.constants.COPYFILE_FICLONE`. a16d88d. This test strategy is: - If the platform supports copy-on-write operation, check whether the destination is expected - Otherwise, the operation will fail and check whether the failure error information is expected. Fixes: #47080 PR-URL: #47084 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent cfafe43 commit a54e898
Copy full SHA for a54e898

File tree

Expand file treeCollapse file tree

5 files changed

+127
-2
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+127
-2
lines changed
Open diff view settings
Collapse file

‎doc/api/fs.md‎

Copy file name to clipboardExpand all lines: doc/api/fs.md
+20Lines changed: 20 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,10 @@ try {
966966
<!-- YAML
967967
added: v16.7.0
968968
changes:
969+
- version: REPLACEME
970+
pr-url: https://github.com/nodejs/node/pull/47084
971+
description: Accept an additional `mode` option to specify
972+
the copy behavior as the `mode` argument of `fs.copyFile()`.
969973
- version:
970974
- v17.6.0
971975
- v16.15.0
@@ -992,6 +996,8 @@ changes:
992996
operation will ignore errors if you set this to false and the destination
993997
exists. Use the `errorOnExist` option to change this behavior.
994998
**Default:** `true`.
999+
* `mode` {integer} modifiers for copy operation. **Default:** `0`.
1000+
See `mode` flag of [`fsPromises.copyFile()`][].
9951001
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
9961002
be preserved. **Default:** `false`.
9971003
* `recursive` {boolean} copy directories recursively **Default:** `false`
@@ -2286,6 +2292,10 @@ copyFile('source.txt', 'destination.txt', constants.COPYFILE_EXCL, callback);
22862292
<!-- YAML
22872293
added: v16.7.0
22882294
changes:
2295+
- version: REPLACEME
2296+
pr-url: https://github.com/nodejs/node/pull/47084
2297+
description: Accept an additional `mode` option to specify
2298+
the copy behavior as the `mode` argument of `fs.copyFile()`.
22892299
- version: v18.0.0
22902300
pr-url: https://github.com/nodejs/node/pull/41678
22912301
description: Passing an invalid callback to the `callback` argument
@@ -2317,6 +2327,8 @@ changes:
23172327
operation will ignore errors if you set this to false and the destination
23182328
exists. Use the `errorOnExist` option to change this behavior.
23192329
**Default:** `true`.
2330+
* `mode` {integer} modifiers for copy operation. **Default:** `0`.
2331+
See `mode` flag of [`fs.copyFile()`][].
23202332
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
23212333
be preserved. **Default:** `false`.
23222334
* `recursive` {boolean} copy directories recursively **Default:** `false`
@@ -5191,6 +5203,10 @@ copyFileSync('source.txt', 'destination.txt', constants.COPYFILE_EXCL);
51915203
<!-- YAML
51925204
added: v16.7.0
51935205
changes:
5206+
- version: REPLACEME
5207+
pr-url: https://github.com/nodejs/node/pull/47084
5208+
description: Accept an additional `mode` option to specify
5209+
the copy behavior as the `mode` argument of `fs.copyFile()`.
51945210
- version:
51955211
- v17.6.0
51965212
- v16.15.0
@@ -5216,6 +5232,8 @@ changes:
52165232
operation will ignore errors if you set this to false and the destination
52175233
exists. Use the `errorOnExist` option to change this behavior.
52185234
**Default:** `true`.
5235+
* `mode` {integer} modifiers for copy operation. **Default:** `0`.
5236+
See `mode` flag of [`fs.copyFileSync()`][].
52195237
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
52205238
be preserved. **Default:** `false`.
52215239
* `recursive` {boolean} copy directories recursively **Default:** `false`
@@ -8004,6 +8022,7 @@ the file contents.
80048022
[`fs.chmod()`]: #fschmodpath-mode-callback
80058023
[`fs.chown()`]: #fschownpath-uid-gid-callback
80068024
[`fs.copyFile()`]: #fscopyfilesrc-dest-mode-callback
8025+
[`fs.copyFileSync()`]: #fscopyfilesyncsrc-dest-mode
80078026
[`fs.createReadStream()`]: #fscreatereadstreampath-options
80088027
[`fs.createWriteStream()`]: #fscreatewritestreampath-options
80098028
[`fs.exists()`]: #fsexistspath-callback
@@ -8037,6 +8056,7 @@ the file contents.
80378056
[`fs.writeFile()`]: #fswritefilefile-data-options-callback
80388057
[`fs.writev()`]: #fswritevfd-buffers-position-callback
80398058
[`fsPromises.access()`]: #fspromisesaccesspath-mode
8059+
[`fsPromises.copyFile()`]: #fspromisescopyfilesrc-dest-mode
80408060
[`fsPromises.open()`]: #fspromisesopenpath-flags-mode
80418061
[`fsPromises.opendir()`]: #fspromisesopendirpath-options
80428062
[`fsPromises.rm()`]: #fspromisesrmpath-options
Collapse file

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

Copy file name to clipboardExpand all lines: lib/internal/fs/cp/cp-sync.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ function mayCopyFile(srcStat, src, dest, opts) {
226226
}
227227

228228
function copyFile(srcStat, src, dest, opts) {
229-
copyFileSync(src, dest);
229+
copyFileSync(src, dest, opts.mode);
230230
if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest);
231231
return setDestMode(dest, srcStat.mode);
232232
}
Collapse file

‎lib/internal/fs/cp/cp.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/cp/cp.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ async function mayCopyFile(srcStat, src, dest, opts) {
257257
}
258258

259259
async function _copyFile(srcStat, src, dest, opts) {
260-
await copyFile(src, dest);
260+
await copyFile(src, dest, opts.mode);
261261
if (opts.preserveTimestamps) {
262262
return handleTimestampsAndMode(srcStat.mode, src, dest);
263263
}
Collapse file

‎lib/internal/fs/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/fs/utils.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ const validateCpOptions = hideStackFrames((options) => {
787787
validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
788788
validateBoolean(options.recursive, 'options.recursive');
789789
validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks');
790+
options.mode = getValidMode(options.mode, 'copyFile');
790791
if (options.dereference === true && options.verbatimSymlinks === true) {
791792
throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks');
792793
}
Collapse file

‎test/parallel/test-fs-cp.mjs‎

Copy file name to clipboardExpand all lines: test/parallel/test-fs-cp.mjs
+104Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,30 @@ function nextdir() {
3838
assertDirEquivalent(src, dest);
3939
}
4040

41+
// It copies a nested folder structure with mode flags.
42+
// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
43+
(() => {
44+
const src = './test/fixtures/copy/kitchen-sink';
45+
const dest = nextdir();
46+
try {
47+
cpSync(src, dest, mustNotMutateObjectDeep({
48+
recursive: true,
49+
mode: fs.constants.COPYFILE_FICLONE_FORCE,
50+
}));
51+
} catch (err) {
52+
// If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
53+
// it should enter this path.
54+
assert.strictEqual(err.syscall, 'copyfile');
55+
assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
56+
err.code === 'ENOSYS' || err.code === 'EXDEV');
57+
return;
58+
}
59+
60+
// If the platform support `COPYFILE_FICLONE_FORCE` operation,
61+
// it should reach to here.
62+
assertDirEquivalent(src, dest);
63+
})();
64+
4165
// It does not throw errors when directory is copied over and force is false.
4266
{
4367
const src = nextdir();
@@ -107,6 +131,14 @@ function nextdir() {
107131
});
108132
}
109133

134+
// It rejects if options.mode is invalid.
135+
{
136+
assert.throws(
137+
() => cpSync('a', 'b', { mode: -1 }),
138+
{ code: 'ERR_OUT_OF_RANGE' }
139+
);
140+
}
141+
110142

111143
// It throws an error when both dereference and verbatimSymlinks are enabled.
112144
{
@@ -425,6 +457,31 @@ if (!isWindows) {
425457
}));
426458
}
427459

460+
// It copies a nested folder structure with mode flags.
461+
// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
462+
{
463+
const src = './test/fixtures/copy/kitchen-sink';
464+
const dest = nextdir();
465+
cp(src, dest, mustNotMutateObjectDeep({
466+
recursive: true,
467+
mode: fs.constants.COPYFILE_FICLONE_FORCE,
468+
}), mustCall((err) => {
469+
if (!err) {
470+
// If the platform support `COPYFILE_FICLONE_FORCE` operation,
471+
// it should reach to here.
472+
assert.strictEqual(err, null);
473+
assertDirEquivalent(src, dest);
474+
return;
475+
}
476+
477+
// If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
478+
// it should enter this path.
479+
assert.strictEqual(err.syscall, 'copyfile');
480+
assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
481+
err.code === 'ENOSYS' || err.code === 'EXDEV');
482+
}));
483+
}
484+
428485
// It does not throw errors when directory is copied over and force is false.
429486
{
430487
const src = nextdir();
@@ -799,6 +856,14 @@ if (!isWindows) {
799856
);
800857
}
801858

859+
// It throws if options is not object.
860+
{
861+
assert.throws(
862+
() => cp('a', 'b', { mode: -1 }, () => {}),
863+
{ code: 'ERR_OUT_OF_RANGE' }
864+
);
865+
}
866+
802867
// Promises implementation of copy.
803868

804869
// It copies a nested folder structure with files and folders.
@@ -810,6 +875,35 @@ if (!isWindows) {
810875
assertDirEquivalent(src, dest);
811876
}
812877

878+
// It copies a nested folder structure with mode flags.
879+
// This test is based on fs.promises.copyFile() with `COPYFILE_FICLONE_FORCE`.
880+
{
881+
const src = './test/fixtures/copy/kitchen-sink';
882+
const dest = nextdir();
883+
let p = null;
884+
let successFiClone = false;
885+
try {
886+
p = await fs.promises.cp(src, dest, mustNotMutateObjectDeep({
887+
recursive: true,
888+
mode: fs.constants.COPYFILE_FICLONE_FORCE,
889+
}));
890+
successFiClone = true;
891+
} catch (err) {
892+
// If the platform does not support `COPYFILE_FICLONE_FORCE` operation,
893+
// it should enter this path.
894+
assert.strictEqual(err.syscall, 'copyfile');
895+
assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
896+
err.code === 'ENOSYS' || err.code === 'EXDEV');
897+
}
898+
899+
if (successFiClone) {
900+
// If the platform support `COPYFILE_FICLONE_FORCE` operation,
901+
// it should reach to here.
902+
assert.strictEqual(p, undefined);
903+
assertDirEquivalent(src, dest);
904+
}
905+
}
906+
813907
// It accepts file URL as src and dest.
814908
{
815909
const src = './test/fixtures/copy/kitchen-sink';
@@ -847,6 +941,16 @@ if (!isWindows) {
847941
);
848942
}
849943

944+
// It rejects if options.mode is invalid.
945+
{
946+
await assert.rejects(
947+
fs.promises.cp('a', 'b', {
948+
mode: -1,
949+
}),
950+
{ code: 'ERR_OUT_OF_RANGE' }
951+
);
952+
}
953+
850954
function assertDirEquivalent(dir1, dir2) {
851955
const dir1Entries = [];
852956
collectEntries(dir1, dir1Entries);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.