diff --git a/docs/src/fs.rst b/docs/src/fs.rst index 87af828a..77096eff 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -249,6 +249,9 @@ API - `UV_FS_COPYFILE_EXCL`: If present, `uv_fs_copyfile()` will fail with `UV_EEXIST` if the destination path already exists. The default behavior is to overwrite the destination if it exists. + - `UV_FS_COPYFILE_FICLONE`: If present, `uv_fs_copyfile()` 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. .. warning:: If the destination path is created, but an error occurs while copying @@ -258,6 +261,8 @@ API .. versionadded:: 1.14.0 + .. versionchanged:: 1.20.0 `UV_FS_COPYFILE_FICLONE` is supported. + .. c:function:: int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length, uv_fs_cb cb) Limited equivalent to :man:`sendfile(2)`. diff --git a/include/uv.h b/include/uv.h index 5328373a..cdd251d8 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1190,6 +1190,12 @@ UV_EXTERN int uv_fs_write(uv_loop_t* loop, */ #define UV_FS_COPYFILE_EXCL 0x0001 +/* + * This flag can be used with uv_fs_copyfile() to attempt to create a reflink. + * If copy-on-write is not supported, a fallback copy mechanism is used. + */ +#define UV_FS_COPYFILE_FICLONE 0x0002 + UV_EXTERN int uv_fs_copyfile(uv_loop_t* loop, uv_fs_t* req, const char* path, diff --git a/src/unix/fs.c b/src/unix/fs.c index 92e2d255..e0518d05 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -62,6 +62,9 @@ #if defined(__APPLE__) # include +#elif defined(__linux__) && !defined(FICLONE) +# include +# define FICLONE _IOW(0x94, 9, int) #endif #define INIT(subtype) \ @@ -790,6 +793,10 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { if (req->flags & UV_FS_COPYFILE_EXCL) flags |= COPYFILE_EXCL; +#ifdef COPYFILE_CLONE + if (req->flags & UV_FS_COPYFILE_FICLONE) + flags |= COPYFILE_CLONE; +#endif return copyfile(req->path, req->new_path, NULL, flags); #else uv_fs_t fs_req; @@ -842,6 +849,21 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { goto out; } +#ifdef FICLONE + if (req->flags & UV_FS_COPYFILE_FICLONE) { + if (ioctl(dstfd, FICLONE, srcfd) == -1) { + /* If an error occurred that the sendfile fallback also won't handle, + then exit. Otherwise, fall through to try using sendfile(). */ + if (errno != ENOTTY && errno != EOPNOTSUPP && errno != EXDEV) { + err = -errno; + goto out; + } + } else { + goto out; + } + } +#endif + bytes_to_send = statsbuf.st_size; in_offset = 0; while (bytes_to_send != 0) { @@ -1504,7 +1526,7 @@ int uv_fs_copyfile(uv_loop_t* loop, uv_fs_cb cb) { INIT(COPYFILE); - if (flags & ~UV_FS_COPYFILE_EXCL) + if (flags & ~(UV_FS_COPYFILE_EXCL | UV_FS_COPYFILE_FICLONE)) return UV_EINVAL; PATH2; diff --git a/src/win/fs.c b/src/win/fs.c index 097b00e0..8143d171 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -2334,7 +2334,7 @@ int uv_fs_copyfile(uv_loop_t* loop, INIT(UV_FS_COPYFILE); - if (flags & ~UV_FS_COPYFILE_EXCL) + if (flags & ~(UV_FS_COPYFILE_EXCL | UV_FS_COPYFILE_FICLONE)) return UV_EINVAL; err = fs__capture_path(req, path, new_path, cb != NULL); diff --git a/test/test-fs-copyfile.c b/test/test-fs-copyfile.c index 4b1fdc5e..59f3a2c5 100644 --- a/test/test-fs-copyfile.c +++ b/test/test-fs-copyfile.c @@ -168,6 +168,13 @@ TEST_IMPL(fs_copyfile) { r = uv_fs_copyfile(loop, &req, fixture, dst, -1, fail_cb); ASSERT(r == UV_EINVAL); uv_run(loop, UV_RUN_DEFAULT); + + /* Copies file using UV_FS_COPYFILE_FICLONE. */ + unlink(dst); + r = uv_fs_copyfile(NULL, &req, fixture, dst, UV_FS_COPYFILE_FICLONE, NULL); + ASSERT(r == 0); + handle_result(&req); + unlink(dst); /* Cleanup */ return 0; }