Skip to content

Commit bb7cfd5

Browse files
cjihrigaddaleax
authored andcommitted
fs: add fs.copyFile{Sync}
Fixes: nodejs#14906 PR-URL: nodejs#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 09c1171 commit bb7cfd5

File tree

5 files changed

+271
-5
lines changed

5 files changed

+271
-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

+55
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,61 @@ fs.mkdtempSync = function(prefix, options) {
18841884
};
18851885

18861886

1887+
// Define copyFile() flags.
1888+
Object.defineProperties(fs.constants, {
1889+
COPYFILE_EXCL: { enumerable: true, value: constants.UV_FS_COPYFILE_EXCL }
1890+
});
1891+
1892+
1893+
fs.copyFile = function(src, dest, flags, callback) {
1894+
if (typeof flags === 'function') {
1895+
callback = flags;
1896+
flags = 0;
1897+
} else if (typeof callback !== 'function') {
1898+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'callback', 'function');
1899+
}
1900+
1901+
src = getPathFromURL(src);
1902+
1903+
if (handleError(src, callback))
1904+
return;
1905+
1906+
if (!nullCheck(src, callback))
1907+
return;
1908+
1909+
dest = getPathFromURL(dest);
1910+
1911+
if (handleError(dest, callback))
1912+
return;
1913+
1914+
if (!nullCheck(dest, callback))
1915+
return;
1916+
1917+
src = pathModule._makeLong(src);
1918+
dest = pathModule._makeLong(dest);
1919+
flags = flags | 0;
1920+
const req = new FSReqWrap();
1921+
req.oncomplete = makeCallback(callback);
1922+
binding.copyFile(src, dest, flags, req);
1923+
};
1924+
1925+
1926+
fs.copyFileSync = function(src, dest, flags) {
1927+
src = getPathFromURL(src);
1928+
handleError(src);
1929+
nullCheck(src);
1930+
1931+
dest = getPathFromURL(dest);
1932+
handleError(dest);
1933+
nullCheck(dest);
1934+
1935+
src = pathModule._makeLong(src);
1936+
dest = pathModule._makeLong(dest);
1937+
flags = flags | 0;
1938+
binding.copyFile(src, dest, flags);
1939+
};
1940+
1941+
18871942
var pool;
18881943

18891944
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,
@@ -1274,12 +1270,15 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
12741270
DefineErrnoConstants(err_constants);
12751271
DefineWindowsErrorConstants(err_constants);
12761272
DefineSignalConstants(sig_constants);
1277-
DefineUVConstants(os_constants);
12781273
DefineSystemConstants(fs_constants);
12791274
DefineOpenSSLConstants(crypto_constants);
12801275
DefineCryptoConstants(crypto_constants);
12811276
DefineZlibConstants(zlib_constants);
12821277

1278+
// Define libuv constants.
1279+
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
1280+
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);
1281+
12831282
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
12841283
os_constants->Set(OneByteString(isolate, "signals"), sig_constants);
12851284
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;
@@ -961,6 +962,30 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
961962
}
962963

963964

965+
static void CopyFile(const FunctionCallbackInfo<Value>& args) {
966+
Environment* env = Environment::GetCurrent(args);
967+
968+
if (!args[0]->IsString())
969+
return TYPE_ERROR("src must be a string");
970+
if (!args[1]->IsString())
971+
return TYPE_ERROR("dest must be a string");
972+
if (!args[2]->IsInt32())
973+
return TYPE_ERROR("flags must be an int");
974+
975+
BufferValue src(env->isolate(), args[0]);
976+
ASSERT_PATH(src)
977+
BufferValue dest(env->isolate(), args[1]);
978+
ASSERT_PATH(dest)
979+
int flags = args[2]->Int32Value();
980+
981+
if (args[3]->IsObject()) {
982+
ASYNC_DEST_CALL(copyfile, args[3], *dest, UTF8, *src, *dest, flags)
983+
} else {
984+
SYNC_DEST_CALL(copyfile, *src, *dest, *src, *dest, flags)
985+
}
986+
}
987+
988+
964989
// Wrapper for write(2).
965990
//
966991
// bytesWritten = write(fd, buffer, offset, length, position, callback)
@@ -1425,6 +1450,7 @@ void InitFs(Local<Object> target,
14251450
env->SetMethod(target, "writeBuffers", WriteBuffers);
14261451
env->SetMethod(target, "writeString", WriteString);
14271452
env->SetMethod(target, "realpath", RealPath);
1453+
env->SetMethod(target, "copyFile", CopyFile);
14281454

14291455
env->SetMethod(target, "chmod", Chmod);
14301456
env->SetMethod(target, "fchmod", FChmod);

test/parallel/test-fs-copyfile.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
common.expectsError(() => {
78+
fs.copyFileSync('\u0000', dest);
79+
}, {
80+
code: 'ERR_INVALID_ARG_TYPE',
81+
type: Error,
82+
message: 'The "path" argument must be of type string without null bytes.' +
83+
' Received type string'
84+
});
85+
86+
// Throws if the destination path is not a string.
87+
assert.throws(() => {
88+
fs.copyFileSync(src, null);
89+
}, /^TypeError: dest must be a string$/);
90+
91+
// Throws if the destination path is an invalid path.
92+
common.expectsError(() => {
93+
fs.copyFileSync(src, '\u0000');
94+
}, {
95+
code: 'ERR_INVALID_ARG_TYPE',
96+
type: Error,
97+
message: 'The "path" argument must be of type string without null bytes.' +
98+
' Received type string'
99+
});
100+
101+
// Errors if invalid flags are provided.
102+
assert.throws(() => {
103+
fs.copyFileSync(src, dest, -1);
104+
}, /^Error: EINVAL: invalid argument, copyfile/);

0 commit comments

Comments
 (0)