mirror of
https://github.com/libuv/libuv
synced 2025-03-28 21:13:16 +00:00
unix, windows: add basic uv_fs_copyfile()
Fixes: https://github.com/libuv/libuv/issues/925 PR-URL: https://github.com/libuv/libuv/pull/1465 Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com> Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
ce56a85b19
commit
766d7e9c0b
@ -169,6 +169,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
|
||||
test/test-env-vars.c \
|
||||
test/test-error.c \
|
||||
test/test-fail-always.c \
|
||||
test/test-fs-copyfile.c \
|
||||
test/test-fs-event.c \
|
||||
test/test-fs-poll.c \
|
||||
test/test-fs.c \
|
||||
|
@ -96,6 +96,7 @@ test/test-embed.c
|
||||
test/test-env-vars.c
|
||||
test/test-error.c
|
||||
test/test-fail-always.c
|
||||
test/test-fs-copyfile.c
|
||||
test/test-fs-event.c
|
||||
test/test-fs-poll.c
|
||||
test/test-fs.c
|
||||
|
@ -92,7 +92,8 @@ Data types
|
||||
UV_FS_READLINK,
|
||||
UV_FS_CHOWN,
|
||||
UV_FS_FCHOWN,
|
||||
UV_FS_REALPATH
|
||||
UV_FS_REALPATH,
|
||||
UV_FS_COPYFILE
|
||||
} uv_fs_type;
|
||||
|
||||
.. c:type:: uv_dirent_t
|
||||
@ -241,6 +242,22 @@ API
|
||||
|
||||
Equivalent to :man:`ftruncate(2)`.
|
||||
|
||||
.. c:function:: int uv_fs_copyfile(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, int flags, uv_fs_cb cb)
|
||||
|
||||
Copies a file from `path` to `new_path`. Supported `flags` are described below.
|
||||
|
||||
- `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.
|
||||
|
||||
.. warning::
|
||||
If the destination path is created, but an error occurs while copying
|
||||
the data, then the destination path is removed. There is a brief window
|
||||
of time between closing and removing the file where another process
|
||||
could access the file.
|
||||
|
||||
.. versionadded:: 1.14.0
|
||||
|
||||
.. 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)`.
|
||||
|
15
include/uv.h
15
include/uv.h
@ -1114,7 +1114,8 @@ typedef enum {
|
||||
UV_FS_READLINK,
|
||||
UV_FS_CHOWN,
|
||||
UV_FS_FCHOWN,
|
||||
UV_FS_REALPATH
|
||||
UV_FS_REALPATH,
|
||||
UV_FS_COPYFILE
|
||||
} uv_fs_type;
|
||||
|
||||
/* uv_fs_t is a subclass of uv_req_t. */
|
||||
@ -1159,6 +1160,18 @@ UV_EXTERN int uv_fs_write(uv_loop_t* loop,
|
||||
unsigned int nbufs,
|
||||
int64_t offset,
|
||||
uv_fs_cb cb);
|
||||
/*
|
||||
* This flag can be used with uv_fs_copyfile() to return an error if the
|
||||
* destination already exists.
|
||||
*/
|
||||
#define UV_FS_COPYFILE_EXCL 0x0001
|
||||
|
||||
UV_EXTERN int uv_fs_copyfile(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
const char* new_path,
|
||||
int flags,
|
||||
uv_fs_cb cb);
|
||||
UV_EXTERN int uv_fs_mkdir(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
|
114
src/unix/fs.c
114
src/unix/fs.c
@ -60,6 +60,10 @@
|
||||
# include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
# include <copyfile.h>
|
||||
#endif
|
||||
|
||||
#define INIT(subtype) \
|
||||
do { \
|
||||
req->type = UV_FS; \
|
||||
@ -766,6 +770,102 @@ done:
|
||||
return r;
|
||||
}
|
||||
|
||||
static ssize_t uv__fs_copyfile(uv_fs_t* req) {
|
||||
#if defined(__APPLE__) && !TARGET_OS_IPHONE
|
||||
/* On macOS, use the native copyfile(3). */
|
||||
copyfile_flags_t flags;
|
||||
|
||||
flags = COPYFILE_ALL;
|
||||
|
||||
if (req->flags & UV_FS_COPYFILE_EXCL)
|
||||
flags |= COPYFILE_EXCL;
|
||||
|
||||
return copyfile(req->path, req->new_path, NULL, flags);
|
||||
#else
|
||||
uv_fs_t fs_req;
|
||||
uv_file srcfd;
|
||||
uv_file dstfd;
|
||||
struct stat statsbuf;
|
||||
int dst_flags;
|
||||
int result;
|
||||
int err;
|
||||
|
||||
dstfd = -1;
|
||||
|
||||
/* Open the source file. */
|
||||
srcfd = uv_fs_open(req->loop, &fs_req, req->path, O_RDONLY, 0, NULL);
|
||||
uv_fs_req_cleanup(&fs_req);
|
||||
|
||||
if (srcfd < 0)
|
||||
return srcfd;
|
||||
|
||||
/* Get the source file's mode. */
|
||||
if (fstat(srcfd, &statsbuf)) {
|
||||
err = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst_flags = O_WRONLY | O_CREAT;
|
||||
|
||||
if (req->flags & UV_FS_COPYFILE_EXCL)
|
||||
dst_flags |= O_EXCL;
|
||||
|
||||
/* Open the destination file. */
|
||||
dstfd = uv_fs_open(req->loop,
|
||||
&fs_req,
|
||||
req->new_path,
|
||||
dst_flags,
|
||||
statsbuf.st_mode,
|
||||
NULL);
|
||||
uv_fs_req_cleanup(&fs_req);
|
||||
|
||||
if (dstfd < 0) {
|
||||
err = dstfd;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = uv_fs_sendfile(req->loop,
|
||||
&fs_req,
|
||||
dstfd,
|
||||
srcfd,
|
||||
0,
|
||||
statsbuf.st_size,
|
||||
NULL);
|
||||
uv_fs_req_cleanup(&fs_req);
|
||||
|
||||
out:
|
||||
if (err < 0)
|
||||
result = err;
|
||||
else
|
||||
result = 0;
|
||||
|
||||
/* Close the source file. */
|
||||
err = uv__close_nocheckstdio(srcfd);
|
||||
|
||||
/* Don't overwrite any existing errors. */
|
||||
if (err != 0 && result == 0)
|
||||
result = err;
|
||||
|
||||
/* Close the destination file if it is open. */
|
||||
if (dstfd >= 0) {
|
||||
err = uv__close_nocheckstdio(dstfd);
|
||||
|
||||
/* Don't overwrite any existing errors. */
|
||||
if (err != 0 && result == 0)
|
||||
result = err;
|
||||
|
||||
/* Remove the destination file if something went wrong. */
|
||||
if (result != 0) {
|
||||
uv_fs_unlink(NULL, &fs_req, req->new_path, NULL);
|
||||
/* Ignore the unlink return value, as an error already happened. */
|
||||
uv_fs_req_cleanup(&fs_req);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void uv__to_stat(struct stat* src, uv_stat_t* dst) {
|
||||
dst->st_dev = src->st_dev;
|
||||
dst->st_mode = src->st_mode;
|
||||
@ -946,6 +1046,7 @@ static void uv__fs_work(struct uv__work* w) {
|
||||
X(CHMOD, chmod(req->path, req->mode));
|
||||
X(CHOWN, chown(req->path, req->uid, req->gid));
|
||||
X(CLOSE, close(req->file));
|
||||
X(COPYFILE, uv__fs_copyfile(req));
|
||||
X(FCHMOD, fchmod(req->file, req->mode));
|
||||
X(FCHOWN, fchown(req->file, req->uid, req->gid));
|
||||
X(FDATASYNC, uv__fs_fdatasync(req));
|
||||
@ -1367,3 +1468,16 @@ void uv_fs_req_cleanup(uv_fs_t* req) {
|
||||
uv__free(req->ptr);
|
||||
req->ptr = NULL;
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_copyfile(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
const char* new_path,
|
||||
int flags,
|
||||
uv_fs_cb cb) {
|
||||
INIT(COPYFILE);
|
||||
PATH2;
|
||||
req->flags = flags;
|
||||
POST;
|
||||
}
|
||||
|
42
src/win/fs.c
42
src/win/fs.c
@ -1331,6 +1331,22 @@ static void fs__ftruncate(uv_fs_t* req) {
|
||||
}
|
||||
|
||||
|
||||
static void fs__copyfile(uv_fs_t* req) {
|
||||
int flags;
|
||||
int overwrite;
|
||||
|
||||
flags = req->fs.info.file_flags;
|
||||
overwrite = flags & UV_FS_COPYFILE_EXCL;
|
||||
|
||||
if (CopyFileW(req->file.pathw, req->fs.info.new_pathw, overwrite) == 0) {
|
||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
SET_REQ_RESULT(req, 0);
|
||||
}
|
||||
|
||||
|
||||
static void fs__sendfile(uv_fs_t* req) {
|
||||
int fd_in = req->file.fd, fd_out = req->fs.info.fd_out;
|
||||
size_t length = req->fs.info.bufsml[0].len;
|
||||
@ -1853,6 +1869,7 @@ static void uv__fs_work(struct uv__work* w) {
|
||||
XX(CLOSE, close)
|
||||
XX(READ, read)
|
||||
XX(WRITE, write)
|
||||
XX(COPYFILE, copyfile)
|
||||
XX(SENDFILE, sendfile)
|
||||
XX(STAT, stat)
|
||||
XX(LSTAT, lstat)
|
||||
@ -2387,6 +2404,31 @@ int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file fd,
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_copyfile(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
const char* new_path,
|
||||
int flags,
|
||||
uv_fs_cb cb) {
|
||||
int err;
|
||||
|
||||
uv_fs_req_init(loop, req, UV_FS_COPYFILE, cb);
|
||||
err = fs__capture_path(req, path, new_path, cb != NULL);
|
||||
|
||||
if (err)
|
||||
return uv_translate_sys_error(err);
|
||||
|
||||
req->fs.info.file_flags = flags;
|
||||
|
||||
if (cb != NULL) {
|
||||
QUEUE_FS_TP_JOB(loop, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs__copyfile(req);
|
||||
return req->result;
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file fd_out,
|
||||
uv_file fd_in, int64_t in_offset, size_t length, uv_fs_cb cb) {
|
||||
|
145
test/test-fs-copyfile.c
Normal file
145
test/test-fs-copyfile.c
Normal file
@ -0,0 +1,145 @@
|
||||
/* Copyright libuv project contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "uv.h"
|
||||
#include "task.h"
|
||||
|
||||
#if defined(__unix__) || defined(__POSIX__) || \
|
||||
defined(__APPLE__) || defined(_AIX) || defined(__MVS__)
|
||||
#include <unistd.h> /* unlink, etc. */
|
||||
#else
|
||||
# include <direct.h>
|
||||
# include <io.h>
|
||||
# define unlink _unlink
|
||||
#endif
|
||||
|
||||
static const char fixture[] = "test/fixtures/load_error.node";
|
||||
static const char dst[] = "test_file_dst";
|
||||
static int result_check_count;
|
||||
|
||||
|
||||
static void handle_result(uv_fs_t* req) {
|
||||
uv_fs_t stat_req;
|
||||
uint64_t size;
|
||||
uint64_t mode;
|
||||
int r;
|
||||
|
||||
ASSERT(req->fs_type == UV_FS_COPYFILE);
|
||||
ASSERT(req->result == 0);
|
||||
|
||||
/* Verify that the file size and mode are the same. */
|
||||
r = uv_fs_stat(NULL, &stat_req, req->path, NULL);
|
||||
ASSERT(r == 0);
|
||||
size = stat_req.statbuf.st_size;
|
||||
mode = stat_req.statbuf.st_mode;
|
||||
uv_fs_req_cleanup(&stat_req);
|
||||
r = uv_fs_stat(NULL, &stat_req, dst, NULL);
|
||||
ASSERT(r == 0);
|
||||
ASSERT(stat_req.statbuf.st_size == size);
|
||||
ASSERT(stat_req.statbuf.st_mode == mode);
|
||||
uv_fs_req_cleanup(&stat_req);
|
||||
uv_fs_req_cleanup(req);
|
||||
result_check_count++;
|
||||
}
|
||||
|
||||
|
||||
static void touch_file(const char* name, unsigned int size) {
|
||||
uv_file file;
|
||||
uv_fs_t req;
|
||||
uv_buf_t buf;
|
||||
int r;
|
||||
unsigned int i;
|
||||
|
||||
r = uv_fs_open(NULL, &req, name, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
ASSERT(r >= 0);
|
||||
file = r;
|
||||
|
||||
buf = uv_buf_init("a", 1);
|
||||
|
||||
/* Inefficient but simple. */
|
||||
for (i = 0; i < size; i++) {
|
||||
r = uv_fs_write(NULL, &req, file, &buf, 1, i, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
ASSERT(r >= 0);
|
||||
}
|
||||
|
||||
r = uv_fs_close(NULL, &req, file, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
ASSERT(r == 0);
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(fs_copyfile) {
|
||||
const char src[] = "test_file_src";
|
||||
uv_loop_t* loop;
|
||||
uv_fs_t req;
|
||||
int r;
|
||||
|
||||
loop = uv_default_loop();
|
||||
|
||||
/* Fails with ENOENT if source does not exist. */
|
||||
unlink(src);
|
||||
unlink(dst);
|
||||
r = uv_fs_copyfile(NULL, &req, src, dst, 0, NULL);
|
||||
ASSERT(req.result == UV_ENOENT);
|
||||
ASSERT(r == UV_ENOENT);
|
||||
uv_fs_req_cleanup(&req);
|
||||
/* The destination should not exist. */
|
||||
r = uv_fs_stat(NULL, &req, dst, NULL);
|
||||
ASSERT(r != 0);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
/* Copies file synchronously. Creates new file. */
|
||||
unlink(dst);
|
||||
r = uv_fs_copyfile(NULL, &req, fixture, dst, 0, NULL);
|
||||
ASSERT(r == 0);
|
||||
handle_result(&req);
|
||||
|
||||
/* Copies file synchronously. Overwrites existing file. */
|
||||
r = uv_fs_copyfile(NULL, &req, fixture, dst, 0, NULL);
|
||||
ASSERT(r == 0);
|
||||
handle_result(&req);
|
||||
|
||||
/* Fails to overwrites existing file. */
|
||||
r = uv_fs_copyfile(NULL, &req, fixture, dst, UV_FS_COPYFILE_EXCL, NULL);
|
||||
ASSERT(r == UV_EEXIST);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
/* Copies a larger file. */
|
||||
unlink(dst);
|
||||
touch_file(src, 4096 * 2);
|
||||
r = uv_fs_copyfile(NULL, &req, src, dst, 0, NULL);
|
||||
ASSERT(r == 0);
|
||||
handle_result(&req);
|
||||
unlink(src);
|
||||
|
||||
/* Copies file asynchronously */
|
||||
unlink(dst);
|
||||
r = uv_fs_copyfile(loop, &req, fixture, dst, 0, handle_result);
|
||||
ASSERT(r == 0);
|
||||
ASSERT(result_check_count == 3);
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
ASSERT(result_check_count == 4);
|
||||
unlink(dst); /* Cleanup */
|
||||
|
||||
return 0;
|
||||
}
|
@ -274,6 +274,7 @@ TEST_DECLARE (fs_mkdtemp)
|
||||
TEST_DECLARE (fs_fstat)
|
||||
TEST_DECLARE (fs_access)
|
||||
TEST_DECLARE (fs_chmod)
|
||||
TEST_DECLARE (fs_copyfile)
|
||||
TEST_DECLARE (fs_unlink_readonly)
|
||||
TEST_DECLARE (fs_chown)
|
||||
TEST_DECLARE (fs_link)
|
||||
@ -778,6 +779,7 @@ TASK_LIST_START
|
||||
TEST_ENTRY (fs_fstat)
|
||||
TEST_ENTRY (fs_access)
|
||||
TEST_ENTRY (fs_chmod)
|
||||
TEST_ENTRY (fs_copyfile)
|
||||
TEST_ENTRY (fs_unlink_readonly)
|
||||
TEST_ENTRY (fs_chown)
|
||||
TEST_ENTRY (fs_utime)
|
||||
|
Loading…
x
Reference in New Issue
Block a user