fs: add uv_fs_utime_ex and uv_fs_futime_ex

These new functions behave identicaly to uv_fs_utime and uv_fs_futime
respectively for every OS except macOS and Windows.  For macOS and
Windows, these new APIs will allow the birth/creation time to be set.

Fixes: https://github.com/libuv/libuv/issues/499
PR-URL: https://github.com/libuv/libuv/pull/590
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
This commit is contained in:
Jeremy Whitlock 2015-10-27 13:49:06 -06:00 committed by Santiago Gimeno
parent d5870c71ea
commit c51ead5c70
No known key found for this signature in database
GPG Key ID: F28C3C8DA33C03BE
8 changed files with 345 additions and 16 deletions

View File

@ -298,9 +298,18 @@ API
AIX: This function only works for AIX 7.1 and newer. It can still be called on older
versions but will return ``UV_ENOSYS``.
.. versionchanged:: 1.10.0 sub-second precission is supported on Windows
.. versionchanged:: 1.10.0 sub-second precision is supported on Windows
.. versionchanged:: 2.0.0 replace uv_file with uv_os_fd_t
.. c:function:: int uv_fs_utime_ex(uv_loop_t* loop, uv_fs_t* req, const char* path, double btime, double atime, double mtime, uv_fs_cb cb)
.. c:function:: int uv_fs_futime_ex(uv_loop_t* loop, uv_fs_t* req, uv_file file, double btime, double atime, double mtime, uv_fs_cb cb)
Equivalent to :c:func:`uv_fs_utime` and :c:func:`uv_fs_futime` except on
macOS and Windows, in which case these variants also allow the
birth/creation time to be set.
.. versionadded:: 2.0.0
.. c:function:: int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path, uv_fs_cb cb)
Equivalent to :man:`link(2)`.

View File

@ -1333,12 +1333,26 @@ UV_EXTERN int uv_fs_utime(uv_loop_t* loop,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_utime_ex(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
double btime,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_futime(uv_loop_t* loop,
uv_fs_t* req,
uv_os_fd_t file,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_futime_ex(uv_loop_t* loop,
uv_fs_t* req,
uv_os_fd_t file,
double btime,
double atime,
double mtime,
uv_fs_cb cb);
UV_EXTERN int uv_fs_lstat(uv_loop_t* loop,
uv_fs_t* req,
const char* path,

View File

@ -339,6 +339,7 @@ typedef struct {
off_t off; \
uv_uid_t uid; \
uv_gid_t gid; \
double btime; \
double atime; \
double mtime; \
struct uv__work work_req; \

View File

@ -468,6 +468,7 @@ typedef struct {
uv_buf_t bufsml[4]; \
} info; \
struct { \
double btime; \
double atime; \
double mtime; \
} time; \

View File

@ -30,6 +30,7 @@
#include "internal.h"
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -62,6 +63,49 @@
#if defined(__APPLE__)
# include <copyfile.h>
# include <sys/attr.h>
static void uv__prepare_setattrlist_args(uv_fs_t* req,
struct attrlist* attr_list,
struct timespec (*times)[3],
unsigned int* size) {
memset(attr_list, 0, sizeof(*attr_list));
memset(times, 0, sizeof(*times));
attr_list->bitmapcount = ATTR_BIT_MAP_COUNT;
*size = 0;
if (!isnan(req->btime)) {
attr_list->commonattr |= ATTR_CMN_CRTIME;
(*times)[*size].tv_sec = req->btime;
(*times)[*size].tv_nsec =
(unsigned long)(req->btime * 1000000) % 1000000 * 1000;
++*size;
}
if (!isnan(req->mtime)) {
attr_list->commonattr |= ATTR_CMN_MODTIME;
(*times)[*size].tv_sec = req->mtime;
(*times)[*size].tv_nsec =
(unsigned long)(req->mtime * 1000000) % 1000000 * 1000;
++*size;
}
if (!isnan(req->atime)) {
attr_list->commonattr |= ATTR_CMN_ACCTIME;
(*times)[*size].tv_sec = req->atime;
(*times)[*size].tv_nsec =
(unsigned long)(req->atime * 1000000) % 1000000 * 1000;
++*size;
}
}
#endif
#define INIT(subtype) \
@ -230,9 +274,15 @@ skip:
}
return r;
#elif defined(__APPLE__)
struct attrlist attr_list;
unsigned i;
struct timespec times[3];
#elif defined(__APPLE__) \
|| defined(__DragonFly__) \
uv__prepare_setattrlist_args(req, &attr_list, &times, &i);
return fsetattrlist(req->file, &attr_list, &times, i * sizeof(times[0]), 0);
#elif defined(__DragonFly__) \
|| defined(__FreeBSD__) \
|| defined(__FreeBSD_kernel__) \
|| defined(__NetBSD__) \
@ -704,10 +754,20 @@ static ssize_t uv__fs_sendfile(uv_fs_t* req) {
static ssize_t uv__fs_utime(uv_fs_t* req) {
#if defined(__APPLE__)
struct attrlist attr_list;
unsigned i;
struct timespec times[3];
uv__prepare_setattrlist_args(req, &attr_list, &times, &i);
return setattrlist(req->path, &attr_list, &times, i * sizeof(times[0]), 0);
#else
struct utimbuf buf;
buf.actime = req->atime;
buf.modtime = req->mtime;
return utime(req->path, &buf); /* TODO use utimes() where available */
#endif
}
@ -1251,8 +1311,20 @@ int uv_fs_futime(uv_loop_t* loop,
double atime,
double mtime,
uv_fs_cb cb) {
return uv_fs_futime_ex(loop, req, file, NAN, atime, mtime, cb);
}
int uv_fs_futime_ex(uv_loop_t* loop,
uv_fs_t* req,
uv_os_fd_t file,
double btime,
double atime,
double mtime,
uv_fs_cb cb) {
INIT(FUTIME);
req->file = file;
req->btime = btime;
req->atime = atime;
req->mtime = mtime;
POST;
@ -1442,8 +1514,20 @@ int uv_fs_utime(uv_loop_t* loop,
double atime,
double mtime,
uv_fs_cb cb) {
return uv_fs_utime_ex(loop, req, path, NAN, atime, mtime, cb);
}
int uv_fs_utime_ex(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
double btime,
double atime,
double mtime,
uv_fs_cb cb) {
INIT(UTIME);
PATH;
req->btime = btime;
req->atime = atime;
req->mtime = mtime;
POST;

View File

@ -25,7 +25,10 @@
#include <errno.h>
#include <limits.h>
#include <fcntl.h> /* file constants */
#include <math.h>
#include <sys/stat.h> /* stat constants */
#include <sys/utime.h>
#include <stdio.h>
#include "uv.h"
#include "internal.h"
@ -1497,13 +1500,28 @@ static void fs__fchmod(uv_fs_t* req) {
}
INLINE static int fs__utime_handle(HANDLE handle, double atime, double mtime) {
FILETIME filetime_a, filetime_m;
INLINE static int fs__utime_handle(HANDLE handle,
double btime,
double atime,
double mtime) {
FILETIME filetime_b;
FILETIME filetime_a;
FILETIME filetime_m;
if (isnan(btime)) {
filetime_b.dwHighDateTime = 0;
filetime_b.dwLowDateTime = 0;
} else {
TIME_T_TO_FILETIME(btime, &filetime_b);
}
TIME_T_TO_FILETIME(atime, &filetime_a);
TIME_T_TO_FILETIME(mtime, &filetime_m);
if (!SetFileTime(handle, NULL, &filetime_a, &filetime_m)) {
if (!SetFileTime(handle,
&filetime_b,
&filetime_a,
&filetime_m)) {
return -1;
}
@ -1527,7 +1545,10 @@ static void fs__utime(uv_fs_t* req) {
return;
}
if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
if (fs__utime_handle(handle,
req->fs.time.btime,
req->fs.time.atime,
req->fs.time.mtime) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
CloseHandle(handle);
return;
@ -1543,7 +1564,10 @@ static void fs__futime(uv_fs_t* req) {
HANDLE handle = req->file.hFile;
VERIFY_HANDLE(handle, req);
if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
if (fs__utime_handle(handle,
req->fs.time.btime,
req->fs.time.atime,
req->fs.time.mtime) != 0) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
@ -1964,7 +1988,8 @@ void uv_fs_req_cleanup(uv_fs_t* req) {
uv__free(req->ptr);
}
if (req->fs.info.bufs != req->fs.info.bufsml)
if (req->fs.info.bufs != req->fs.info.bufsml &&
ARRAY_SIZE(req->fs.info.bufs) > 0)
uv__free(req->fs.info.bufs);
req->path = NULL;
@ -2357,7 +2382,12 @@ int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_os_fd_t handle, int mode,
int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
double mtime, uv_fs_cb cb) {
double mtime, uv_fs_cb cb) {
return uv_fs_utime_ex(loop, req, path, NAN, atime, mtime, cb);
}
int uv_fs_utime_ex(uv_loop_t* loop, uv_fs_t* req, const char* path,
double btime, double atime, double mtime, uv_fs_cb cb) {
int err;
INIT(UV_FS_UTIME);
@ -2366,6 +2396,7 @@ int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
return uv_translate_sys_error(err);
}
req->fs.time.btime = btime;
req->fs.time.atime = atime;
req->fs.time.mtime = mtime;
POST;
@ -2374,8 +2405,15 @@ int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_os_fd_t handle, double atime,
double mtime, uv_fs_cb cb) {
return uv_fs_futime_ex(loop, req, handle, NAN, atime, mtime, cb);
}
int uv_fs_futime_ex(uv_loop_t* loop, uv_fs_t* req, uv_os_fd_t handle,
double btime, double atime, double mtime, uv_fs_cb cb) {
INIT(UV_FS_FUTIME);
req->file.hFile = handle;
req->fs.time.btime = btime;
req->fs.time.atime = atime;
req->fs.time.mtime = mtime;
POST;

View File

@ -23,8 +23,9 @@
#include "task.h"
#include <errno.h>
#include <string.h> /* memset */
#include <fcntl.h>
#include <math.h>
#include <string.h> /* memset */
#include <sys/stat.h>
/* FIXME we shouldn't need to branch in this file */
@ -56,6 +57,7 @@
typedef struct {
const char* path;
double btime;
double atime;
double mtime;
} utime_check_t;
@ -687,17 +689,36 @@ TEST_IMPL(fs_file_loop) {
return 0;
}
static void check_utime(const char* path, double atime, double mtime) {
static void check_utime_ex(const char* path,
double btime,
double atime,
double mtime) {
uv_stat_t* s;
uv_fs_t req;
int r;
r = uv_fs_stat(loop, &req, path, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
s = &req.statbuf;
#if defined(__APPLE__) || defined(_WIN32)
/* When check_utime_ex is called with a btime of NAN, this means that we are
* checking uv_fs_utime and uv_fs_futime results, which SHOULD NOT allow the
* caller to alter the btime. Well some utime implementations, like FreeBSD,
* have conditions where btime can be altered via utime even though btime is
* not an argument. The conditions to identify this are impossible to check
* at test time so we will not check that btime is unaltered when checking
* the results of uv_fs_utime and uv_fs_futime.
*/
if (!isnan(btime)) {
/* Make sure the birth/creation time was altered as expected. */
ASSERT(s->st_birthtim.tv_sec + (s->st_birthtim.tv_nsec / 1000000000.0) ==
btime);
}
#endif
ASSERT(s->st_atim.tv_sec + (s->st_atim.tv_nsec / 1000000000.0) == atime);
ASSERT(s->st_mtim.tv_sec + (s->st_mtim.tv_nsec / 1000000000.0) == mtime);
@ -705,6 +726,11 @@ static void check_utime(const char* path, double atime, double mtime) {
}
static void check_utime(const char* path, double atime, double mtime) {
check_utime_ex(path, NAN, atime, mtime);
}
static void utime_cb(uv_fs_t* req) {
utime_check_t* c;
@ -2136,7 +2162,7 @@ TEST_IMPL(fs_non_symlink_reparse_point) {
TEST_IMPL(fs_utime) {
utime_check_t checkme;
const char* path = "test_file";
const char path[] = "test_file";
double atime;
double mtime;
uv_fs_t req;
@ -2200,6 +2226,74 @@ TEST_IMPL(fs_utime) {
}
TEST_IMPL(fs_utime_ex) {
utime_check_t checkme;
const char path[] = "test_file";
double atime;
double btime;
double mtime;
uv_fs_t req;
uv_os_fd_t file;
int r;
/* Setup. */
loop = uv_default_loop();
unlink(path);
r = uv_fs_open(NULL, &req, path, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR, NULL);
ASSERT(r == 0);
ASSERT(req.result >= 0);
file = (uv_os_fd_t)req.result;
uv_fs_req_cleanup(&req);
/* Close file */
r = uv_fs_close(NULL, &req, file, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
uv_fs_req_cleanup(&req);
atime = btime = mtime = 400497753; /* 1982-09-10 11:22:33 */
/*
* Test sub-second timestamps only on Windows (assuming NTFS). Some other
* platforms support sub-second timestamps, but that support is filesystem-
* dependent. Notably OS X (HFS Plus) does NOT support sub-second timestamps.
*/
#ifdef _WIN32
mtime += 0.444; /* 1982-09-10 11:22:33.444 */
#endif
r = uv_fs_utime_ex(NULL, &req, path, btime, atime, mtime, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
uv_fs_req_cleanup(&req);
r = uv_fs_stat(NULL, &req, path, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
check_utime_ex(path, btime, atime, mtime);
uv_fs_req_cleanup(&req);
atime = btime = mtime = 1291404900; /* 2010-12-03 20:35:00 - mees <3 */
checkme.path = path;
checkme.atime = atime;
checkme.btime = btime;
checkme.mtime = mtime;
/* async utime */
utime_req.data = &checkme;
r = uv_fs_utime_ex(loop, &utime_req, path, btime, atime, mtime, utime_cb);
ASSERT(r == 0);
uv_run(loop, UV_RUN_DEFAULT);
ASSERT(utime_cb_count == 1);
/* Cleanup. */
unlink(path);
MAKE_VALGRIND_HAPPY();
return 0;
}
#ifdef _WIN32
TEST_IMPL(fs_stat_root) {
int r;
@ -2237,7 +2331,7 @@ TEST_IMPL(fs_futime) {
RETURN_SKIP("futime is not implemented for AIX versions below 7.1");
#else
utime_check_t checkme;
const char* path = "test_file";
const char path[] = "test_file";
double atime;
double mtime;
uv_os_fd_t file;
@ -2273,7 +2367,7 @@ TEST_IMPL(fs_futime) {
r = uv_fs_open(NULL, &req, path, O_RDWR, 0, NULL);
ASSERT(r == 0);
ASSERT(req.result >= 0);
file = (uv_os_fd_t)req.result; /* FIXME probably not how it's supposed to be used */
file = (uv_os_fd_t)req.result;
uv_fs_req_cleanup(&req);
r = uv_fs_futime(NULL, &req, file, atime, mtime, NULL);
@ -2314,6 +2408,90 @@ TEST_IMPL(fs_futime) {
}
TEST_IMPL(fs_futime_ex) {
#if defined(_AIX) && !defined(_AIX71)
RETURN_SKIP("futime is not implemented for AIX versions below 7.1");
#else
utime_check_t checkme;
const char path[] = "test_file";
double atime;
double btime;
double mtime;
uv_os_fd_t file;
uv_fs_t req;
int r;
/* Setup. */
loop = uv_default_loop();
unlink(path);
r = uv_fs_open(NULL, &req, path, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR, NULL);
ASSERT(r == 0);
ASSERT(req.result >= 0);
file = (uv_os_fd_t)req.result;
uv_fs_req_cleanup(&req);
/* Close file */
r = uv_fs_close(NULL, &req, file, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
uv_fs_req_cleanup(&req);
atime = btime = mtime = 400497753; /* 1982-09-10 11:22:33 */
/*
* Test sub-second timestamps only on Windows (assuming NTFS). Some other
* platforms support sub-second timestamps, but that support is filesystem-
* dependent. Notably OS X (HFS Plus) does NOT support sub-second timestamps.
*/
#ifdef _WIN32
mtime += 0.444; /* 1982-09-10 11:22:33.444 */
#endif
r = uv_fs_open(NULL, &req, path, O_RDWR, 0, NULL);
ASSERT(r == 0);
ASSERT(req.result >= 0);
file = (uv_os_fd_t)req.result;
uv_fs_req_cleanup(&req);
r = uv_fs_futime_ex(NULL, &req, file, btime, atime, mtime, NULL);
#if defined(__CYGWIN__) || defined(__MSYS__)
ASSERT(r == UV_ENOSYS);
RETURN_SKIP("futime not supported on Cygwin");
#else
ASSERT(r == 0);
ASSERT(req.result == 0);
#endif
uv_fs_req_cleanup(&req);
r = uv_fs_stat(NULL, &req, path, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
check_utime_ex(path, btime, atime, mtime);
uv_fs_req_cleanup(&req);
atime = btime = mtime = 1291404900; /* 2010-12-03 20:35:00 - mees <3 */
checkme.atime = atime;
checkme.btime = btime;
checkme.mtime = mtime;
checkme.path = path;
/* async futime */
futime_req.data = &checkme;
r = uv_fs_futime_ex(loop, &futime_req, file, btime, atime, mtime, futime_cb);
ASSERT(r == 0);
uv_run(loop, UV_RUN_DEFAULT);
ASSERT(futime_cb_count == 1);
/* Cleanup. */
unlink(path);
MAKE_VALGRIND_HAPPY();
return 0;
#endif
}
TEST_IMPL(fs_stat_missing_path) {
uv_fs_t req;
int r;

View File

@ -305,7 +305,9 @@ TEST_DECLARE (fs_symlink_junction)
TEST_DECLARE (fs_non_symlink_reparse_point)
#endif
TEST_DECLARE (fs_utime)
TEST_DECLARE (fs_utime_ex)
TEST_DECLARE (fs_futime)
TEST_DECLARE (fs_futime_ex)
TEST_DECLARE (fs_file_open_append)
TEST_DECLARE (fs_stat_missing_path)
TEST_DECLARE (fs_read_file_eof)
@ -836,7 +838,9 @@ TASK_LIST_START
TEST_ENTRY (fs_unlink_readonly)
TEST_ENTRY (fs_chown)
TEST_ENTRY (fs_utime)
TEST_ENTRY (fs_utime_ex)
TEST_ENTRY (fs_futime)
TEST_ENTRY (fs_futime_ex)
TEST_ENTRY (fs_readlink)
TEST_ENTRY (fs_realpath)
TEST_ENTRY (fs_symlink)