Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: expose copy-on-write flags for fs.copyFile() #19759

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 64 additions & 6 deletions doc/api/fs.md
Expand Up @@ -1175,8 +1175,18 @@ operation. If an error occurs after the destination file has been opened for
writing, Node.js will attempt to remove the destination.

`flags` is an optional integer that specifies the behavior
of the copy operation. The only supported flag is `fs.constants.COPYFILE_EXCL`,
which causes the copy operation to fail if `dest` already exists.
of the copy operation. It is possible to create a mask consisting of the bitwise
OR of two or more values (e.g.
`fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE`).

* `fs.constants.COPYFILE_EXCL` - The copy operation will fail if `dest` already
exists.
* `fs.constants.COPYFILE_FICLONE` - The copy operation will attempt to create a
copy-on-write reflink. If the platform does not support copy-on-write, then a
fallback copy mechanism is used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which fallback copy mechanism? Is it system specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It falls back to uv_fs_copyfile()'s default behavior for that platform.

* `fs.constants.COPYFILE_FICLONE_FORCE` - The copy operation will attempt to
create a copy-on-write reflink. If the platform does not support copy-on-write,
then the operation will fail.

Example:

Expand Down Expand Up @@ -1216,8 +1226,18 @@ atomicity of the copy operation. If an error occurs after the destination file
has been opened for writing, Node.js will attempt to remove the destination.

`flags` is an optional integer that specifies the behavior
of the copy operation. The only supported flag is `fs.constants.COPYFILE_EXCL`,
which causes the copy operation to fail if `dest` already exists.
of the copy operation. It is possible to create a mask consisting of the bitwise
OR of two or more values (e.g.
`fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE`).

* `fs.constants.COPYFILE_EXCL` - The copy operation will fail if `dest` already
exists.
* `fs.constants.COPYFILE_FICLONE` - The copy operation will attempt to create a
copy-on-write reflink. If the platform does not support copy-on-write, then a
fallback copy mechanism is used.
* `fs.constants.COPYFILE_FICLONE_FORCE` - The copy operation will attempt to
create a copy-on-write reflink. If the platform does not support copy-on-write,
then the operation will fail.

Example:

Expand Down Expand Up @@ -3814,8 +3834,18 @@ error occurs after the destination file has been opened for writing, Node.js
will attempt to remove the destination.

`flags` is an optional integer that specifies the behavior
of the copy operation. The only supported flag is `fs.constants.COPYFILE_EXCL`,
which causes the copy operation to fail if `dest` already exists.
of the copy operation. It is possible to create a mask consisting of the bitwise
OR of two or more values (e.g.
`fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE`).

* `fs.constants.COPYFILE_EXCL` - The copy operation will fail if `dest` already
exists.
* `fs.constants.COPYFILE_FICLONE` - The copy operation will attempt to create a
copy-on-write reflink. If the platform does not support copy-on-write, then a
fallback copy mechanism is used.
* `fs.constants.COPYFILE_FICLONE_FORCE` - The copy operation will attempt to
create a copy-on-write reflink. If the platform does not support copy-on-write,
then the operation will fail.

Example:

Expand Down Expand Up @@ -4449,6 +4479,34 @@ The following constants are meant for use with [`fs.access()`][].
</tr>
</table>

### File Copy Constants

The following constants are meant for use with [`fs.copyFile()`][].

<table>
<tr>
<th>Constant</th>
<th>Description</th>
</tr>
<tr>
<td><code>COPYFILE_EXCL</code></td>
<td>If present, the copy operation will fail with an error if the
destination path already exists.</td>
</tr>
<tr>
<td><code>COPYFILE_FICLONE</code></td>
<td>If present, the copy operation will attempt to create a
copy-on-write reflink. If the underlying platform does not support
copy-on-write, then a fallback copy mechanism is used.</td>
</tr>
<tr>
<td><code>COPYFILE_FICLONE_FORCE</code></td>
<td>If present, the copy operation will attempt to create a
copy-on-write reflink. If the underlying platform does not support
copy-on-write, then the operation will fail with an error.</td>
</tr>
</table>

### File Open Constants

The following constants are meant for use with `fs.open()`.
Expand Down
10 changes: 9 additions & 1 deletion lib/fs.js
Expand Up @@ -1916,7 +1916,15 @@ fs.mkdtempSync = function(prefix, options) {

// Define copyFile() flags.
Object.defineProperties(fs.constants, {
COPYFILE_EXCL: { enumerable: true, value: constants.UV_FS_COPYFILE_EXCL }
COPYFILE_EXCL: { enumerable: true, value: constants.UV_FS_COPYFILE_EXCL },
COPYFILE_FICLONE: {
enumerable: true,
value: constants.UV_FS_COPYFILE_FICLONE
},
COPYFILE_FICLONE_FORCE: {
enumerable: true,
value: constants.UV_FS_COPYFILE_FICLONE_FORCE
}
});


Expand Down
2 changes: 2 additions & 0 deletions src/node_constants.cc
Expand Up @@ -1314,6 +1314,8 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
// Define libuv constants.
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_FICLONE);
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_FICLONE_FORCE);

os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants);
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
Expand Down
34 changes: 32 additions & 2 deletions test/parallel/test-fs-copyfile.js
Expand Up @@ -8,7 +8,14 @@ const uv = process.binding('uv');
const path = require('path');
const src = fixtures.path('a.js');
const dest = path.join(tmpdir.path, 'copyfile.out');
const { COPYFILE_EXCL, UV_FS_COPYFILE_EXCL } = fs.constants;
const {
COPYFILE_EXCL,
COPYFILE_FICLONE,
COPYFILE_FICLONE_FORCE,
UV_FS_COPYFILE_EXCL,
UV_FS_COPYFILE_FICLONE,
UV_FS_COPYFILE_FICLONE_FORCE
} = fs.constants;

function verify(src, dest) {
const srcData = fs.readFileSync(src, 'utf8');
Expand All @@ -25,8 +32,14 @@ tmpdir.refresh();

// Verify that flags are defined.
assert.strictEqual(typeof COPYFILE_EXCL, 'number');
assert.strictEqual(typeof COPYFILE_FICLONE, 'number');
assert.strictEqual(typeof COPYFILE_FICLONE_FORCE, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_EXCL, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE, 'number');
assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE_FORCE, 'number');
assert.strictEqual(COPYFILE_EXCL, UV_FS_COPYFILE_EXCL);
assert.strictEqual(COPYFILE_FICLONE, UV_FS_COPYFILE_FICLONE);
assert.strictEqual(COPYFILE_FICLONE_FORCE, UV_FS_COPYFILE_FICLONE_FORCE);

// Verify that files are overwritten when no flags are provided.
fs.writeFileSync(dest, '', 'utf8');
Expand All @@ -38,9 +51,26 @@ verify(src, dest);
fs.copyFileSync(src, dest, 0);
verify(src, dest);

// Verify that UV_FS_COPYFILE_FICLONE can be used.
fs.unlinkSync(dest);
fs.copyFileSync(src, dest, UV_FS_COPYFILE_FICLONE);
verify(src, dest);

// Verify that COPYFILE_FICLONE_FORCE can be used.
try {
fs.unlinkSync(dest);
fs.copyFileSync(src, dest, COPYFILE_FICLONE_FORCE);
verify(src, dest);
} catch (err) {
assert.strictEqual(err.syscall, 'copyfile');
assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' ||
err.code === 'ENOSYS');
assert.strictEqual(err.path, src);
assert.strictEqual(err.dest, dest);
}

// Copies asynchronously.
fs.unlinkSync(dest);
tmpdir.refresh(); // Don't use unlinkSync() since the last test may fail.
fs.copyFile(src, dest, common.mustCall((err) => {
assert.ifError(err);
verify(src, dest);
Expand Down