Skip to content

Commit 71f90c6

Browse files
cjihrigMylesBorins
authored andcommitted
fs: add fs.copyFile{Sync}
Fixes: #14906 PR-URL: #15034 Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent 3a68b0b commit 71f90c6

File tree

5 files changed

+262
-5
lines changed

5 files changed

+262
-5
lines changed

doc/api/fs.md

+82
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,88 @@ Returns an object containing commonly used constants for file system
750750
operations. The specific constants currently defined are described in
751751
[FS Constants][].
752752

753+
## fs.copyFile(src, dest[, flags], callback)
754+
<!-- YAML
755+
added: REPLACEME
756+
-->
757+
758+
* `src` {string|Buffer|URL} source filename to copy
759+
* `dest` {string|Buffer|URL} destination filename of the copy operation
760+
* `flags` {number} modifiers for copy operation. **Default:** `0`
761+
* `callback` {Function}
762+
763+
Asynchronously copies `src` to `dest`. By default, `dest` is overwritten if it
764+
already exists. No arguments other than a possible exception are given to the
765+
callback function. Node.js makes no guarantees about the atomicity of the copy
766+
operation. If an error occurs after the destination file has been opened for
767+
writing, Node.js will attempt to remove the destination.
768+
769+
`flags` is an optional integer that specifies the behavior
770+
of the copy operation. The only supported flag is `fs.constants.COPYFILE_EXCL`,
771+
which causes the copy operation to fail if `dest` already exists.
772+
773+
Example:
774+
775+
```js
776+
const fs = require('fs');
777+
778+
// destination.txt will be created or overwritten by default.
779+
fs.copyFile('source.txt', 'destination.txt', (err) => {
780+
if (err) throw err;
781+
console.log('source.txt was copied to destination.txt');
782+
});
783+
```
784+
785+
If the third argument is a number, then it specifies `flags`, as shown in the
786+
following example.
787+
788+
```js
789+
const fs = require('fs');
790+
const { COPYFILE_EXCL } = fs.constants;
791+
792+
// By using COPYFILE_EXCL, the operation will fail if destination.txt exists.
793+
fs.copyFile('source.txt', 'destination.txt', COPYFILE_EXCL, callback);
794+
```
795+
796+
## fs.copyFileSync(src, dest[, flags])
797+
<!-- YAML
798+
added: REPLACEME
799+
-->
800+
801+
* `src` {string|Buffer|URL} source filename to copy
802+
* `dest` {string|Buffer|URL} destination filename of the copy operation
803+
* `flags` {number} modifiers for copy operation. **Default:** `0`
804+
805+
Synchronously copies `src` to `dest`. By default, `dest` is overwritten if it
806+
already exists. Returns `undefined`. Node.js makes no guarantees about the
807+
atomicity of the copy operation. If an error occurs after the destination file
808+
has been opened for writing, Node.js will attempt to remove the destination.
809+
810+
`flags` is an optional integer that specifies the behavior
811+
of the copy operation. The only supported flag is `fs.constants.COPYFILE_EXCL`,
812+
which causes the copy operation to fail if `dest` already exists.
813+
814+
Example:
815+
816+
```js
817+
const fs = require('fs');
818+
819+
// destination.txt will be created or overwritten by default.
820+
fs.copyFileSync('source.txt', 'destination.txt');
821+
console.log('source.txt was copied to destination.txt');
822+
```
823+
824+
If the third argument is a number, then it specifies `flags`, as shown in the
825+
following example.
826+
827+
```js
828+
const fs = require('fs');
829+
const { COPYFILE_EXCL } = fs.constants;
830+
831+
// By using COPYFILE_EXCL, the operation will fail if destination.txt exists.
832+
fs.copyFileSync('source.txt', 'destination.txt', COPYFILE_EXCL);
833+
```
834+
753835
## fs.createReadStream(path[, options])
754836
<!-- YAML
755837
added: v0.1.31

lib/fs.js

+56
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const { isUint8Array, createPromise, promiseResolve } = process.binding('util');
3333
const binding = process.binding('fs');
3434
const fs = exports;
3535
const Buffer = require('buffer').Buffer;
36+
const errors = require('internal/errors');
3637
const Stream = require('stream').Stream;
3738
const EventEmitter = require('events');
3839
const FSReqWrap = binding.FSReqWrap;
@@ -1864,6 +1865,61 @@ fs.mkdtempSync = function(prefix, options) {
18641865
};
18651866

18661867

1868+
// Define copyFile() flags.
1869+
Object.defineProperties(fs.constants, {
1870+
COPYFILE_EXCL: { enumerable: true, value: constants.UV_FS_COPYFILE_EXCL }
1871+
});
1872+
1873+
1874+
fs.copyFile = function(src, dest, flags, callback) {
1875+
if (typeof flags === 'function') {
1876+
callback = flags;
1877+
flags = 0;
1878+
} else if (typeof callback !== 'function') {
1879+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'callback', 'function');
1880+
}
1881+
1882+
src = getPathFromURL(src);
1883+
1884+
if (handleError(src, callback))
1885+
return;
1886+
1887+
if (!nullCheck(src, callback))
1888+
return;
1889+
1890+
dest = getPathFromURL(dest);
1891+
1892+
if (handleError(dest, callback))
1893+
return;
1894+
1895+
if (!nullCheck(dest, callback))
1896+
return;
1897+
1898+
src = pathModule._makeLong(src);
1899+
dest = pathModule._makeLong(dest);
1900+
flags = flags | 0;
1901+
const req = new FSReqWrap();
1902+
req.oncomplete = makeCallback(callback);
1903+
binding.copyFile(src, dest, flags, req);
1904+
};
1905+
1906+
1907+
fs.copyFileSync = function(src, dest, flags) {
1908+
src = getPathFromURL(src);
1909+
handleError(src);
1910+
nullCheck(src);
1911+
1912+
dest = getPathFromURL(dest);
1913+
handleError(dest);
1914+
nullCheck(dest);
1915+
1916+
src = pathModule._makeLong(src);
1917+
dest = pathModule._makeLong(dest);
1918+
flags = flags | 0;
1919+
binding.copyFile(src, dest, flags);
1920+
};
1921+
1922+
18671923
var pool;
18681924

18691925
function allocNewPool(poolSize) {

src/node_constants.cc

+4-5
Original file line numberDiff line numberDiff line change
@@ -1164,10 +1164,6 @@ void DefineSystemConstants(Local<Object> target) {
11641164
#endif
11651165
}
11661166

1167-
void DefineUVConstants(Local<Object> target) {
1168-
NODE_DEFINE_CONSTANT(target, UV_UDP_REUSEADDR);
1169-
}
1170-
11711167
void DefineCryptoConstants(Local<Object> target) {
11721168
#if HAVE_OPENSSL
11731169
NODE_DEFINE_STRING_CONSTANT(target,
@@ -1290,12 +1286,15 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
12901286
DefineErrnoConstants(err_constants);
12911287
DefineWindowsErrorConstants(err_constants);
12921288
DefineSignalConstants(sig_constants);
1293-
DefineUVConstants(os_constants);
12941289
DefineSystemConstants(fs_constants);
12951290
DefineOpenSSLConstants(crypto_constants);
12961291
DefineCryptoConstants(crypto_constants);
12971292
DefineZlibConstants(zlib_constants);
12981293

1294+
// Define libuv constants.
1295+
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
1296+
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);
1297+
12991298
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
13001299
os_constants->Set(OneByteString(isolate, "signals"), sig_constants);
13011300
target->Set(OneByteString(isolate, "os"), os_constants);

src/node_file.cc

+26
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ void After(uv_fs_t *req) {
212212
case UV_FS_FCHMOD:
213213
case UV_FS_CHOWN:
214214
case UV_FS_FCHOWN:
215+
case UV_FS_COPYFILE:
215216
// These, however, don't.
216217
argc = 1;
217218
break;
@@ -994,6 +995,30 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
994995
}
995996

996997

998+
static void CopyFile(const FunctionCallbackInfo<Value>& args) {
999+
Environment* env = Environment::GetCurrent(args);
1000+
1001+
if (!args[0]->IsString())
1002+
return TYPE_ERROR("src must be a string");
1003+
if (!args[1]->IsString())
1004+
return TYPE_ERROR("dest must be a string");
1005+
if (!args[2]->IsInt32())
1006+
return TYPE_ERROR("flags must be an int");
1007+
1008+
BufferValue src(env->isolate(), args[0]);
1009+
ASSERT_PATH(src)
1010+
BufferValue dest(env->isolate(), args[1]);
1011+
ASSERT_PATH(dest)
1012+
int flags = args[2]->Int32Value();
1013+
1014+
if (args[3]->IsObject()) {
1015+
ASYNC_DEST_CALL(copyfile, args[3], *dest, UTF8, *src, *dest, flags)
1016+
} else {
1017+
SYNC_DEST_CALL(copyfile, *src, *dest, *src, *dest, flags)
1018+
}
1019+
}
1020+
1021+
9971022
// Wrapper for write(2).
9981023
//
9991024
// bytesWritten = write(fd, buffer, offset, length, position, callback)
@@ -1461,6 +1486,7 @@ void InitFs(Local<Object> target,
14611486
env->SetMethod(target, "writeBuffers", WriteBuffers);
14621487
env->SetMethod(target, "writeString", WriteString);
14631488
env->SetMethod(target, "realpath", RealPath);
1489+
env->SetMethod(target, "copyFile", CopyFile);
14641490

14651491
env->SetMethod(target, "chmod", Chmod);
14661492
env->SetMethod(target, "fchmod", FChmod);

test/parallel/test-fs-copyfile.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const fs = require('fs');
5+
const path = require('path');
6+
const src = path.join(common.fixturesDir, 'a.js');
7+
const dest = path.join(common.tmpDir, 'copyfile.out');
8+
const { COPYFILE_EXCL, UV_FS_COPYFILE_EXCL } = fs.constants;
9+
10+
function verify(src, dest) {
11+
const srcData = fs.readFileSync(src, 'utf8');
12+
const srcStat = fs.statSync(src);
13+
const destData = fs.readFileSync(dest, 'utf8');
14+
const destStat = fs.statSync(dest);
15+
16+
assert.strictEqual(srcData, destData);
17+
assert.strictEqual(srcStat.mode, destStat.mode);
18+
assert.strictEqual(srcStat.size, destStat.size);
19+
}
20+
21+
common.refreshTmpDir();
22+
23+
// Verify that flags are defined.
24+
assert.strictEqual(typeof COPYFILE_EXCL, 'number');
25+
assert.strictEqual(typeof UV_FS_COPYFILE_EXCL, 'number');
26+
assert.strictEqual(COPYFILE_EXCL, UV_FS_COPYFILE_EXCL);
27+
28+
// Verify that files are overwritten when no flags are provided.
29+
fs.writeFileSync(dest, '', 'utf8');
30+
const result = fs.copyFileSync(src, dest);
31+
assert.strictEqual(result, undefined);
32+
verify(src, dest);
33+
34+
// Verify that files are overwritten with default flags.
35+
fs.copyFileSync(src, dest, 0);
36+
verify(src, dest);
37+
38+
// Throws if destination exists and the COPYFILE_EXCL flag is provided.
39+
assert.throws(() => {
40+
fs.copyFileSync(src, dest, COPYFILE_EXCL);
41+
}, /^Error: EEXIST|ENOENT:.+, copyfile/);
42+
43+
// Throws if the source does not exist.
44+
assert.throws(() => {
45+
fs.copyFileSync(src + '__does_not_exist', dest, COPYFILE_EXCL);
46+
}, /^Error: ENOENT: no such file or directory, copyfile/);
47+
48+
// Copies asynchronously.
49+
fs.unlinkSync(dest);
50+
fs.copyFile(src, dest, common.mustCall((err) => {
51+
assert.ifError(err);
52+
verify(src, dest);
53+
54+
// Copy asynchronously with flags.
55+
fs.copyFile(src, dest, COPYFILE_EXCL, common.mustCall((err) => {
56+
assert(
57+
/^Error: EEXIST: file already exists, copyfile/.test(err.toString())
58+
);
59+
}));
60+
}));
61+
62+
// Throws if callback is not a function.
63+
common.expectsError(() => {
64+
fs.copyFile(src, dest, 0, 0);
65+
}, {
66+
code: 'ERR_INVALID_ARG_TYPE',
67+
type: TypeError,
68+
message: 'The "callback" argument must be of type function'
69+
});
70+
71+
// Throws if the source path is not a string.
72+
assert.throws(() => {
73+
fs.copyFileSync(null, dest);
74+
}, /^TypeError: src must be a string$/);
75+
76+
// Throws if the source path is an invalid path.
77+
assert.throws(() => {
78+
fs.copyFileSync('\u0000', dest);
79+
}, /^Error: Path must be a string without null bytes$/);
80+
81+
// Throws if the destination path is not a string.
82+
assert.throws(() => {
83+
fs.copyFileSync(src, null);
84+
}, /^TypeError: dest must be a string$/);
85+
86+
// Throws if the destination path is an invalid path.
87+
assert.throws(() => {
88+
fs.copyFileSync(src, '\u0000');
89+
}, /^Error: Path must be a string without null bytes$/);
90+
91+
// Errors if invalid flags are provided.
92+
assert.throws(() => {
93+
fs.copyFileSync(src, dest, -1);
94+
}, /^Error: EINVAL: invalid argument, copyfile/);

0 commit comments

Comments
 (0)