From c51ead5c704cae957b9323feef97e842e1bdb033 Mon Sep 17 00:00:00 2001 From: Jeremy Whitlock Date: Tue, 27 Oct 2015 13:49:06 -0600 Subject: [PATCH] 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 Reviewed-By: Colin Ihrig Reviewed-By: Santiago Gimeno --- docs/src/fs.rst | 11 ++- include/uv.h | 14 ++++ include/uv/unix.h | 1 + include/uv/win.h | 1 + src/unix/fs.c | 88 ++++++++++++++++++++- src/win/fs.c | 52 +++++++++++-- test/test-fs.c | 190 ++++++++++++++++++++++++++++++++++++++++++++-- test/test-list.h | 4 + 8 files changed, 345 insertions(+), 16 deletions(-) diff --git a/docs/src/fs.rst b/docs/src/fs.rst index 563e64af..61c7f3fc 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -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)`. diff --git a/include/uv.h b/include/uv.h index 373c47f1..e550b417 100644 --- a/include/uv.h +++ b/include/uv.h @@ -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, diff --git a/include/uv/unix.h b/include/uv/unix.h index 41848c06..a1bddec1 100644 --- a/include/uv/unix.h +++ b/include/uv/unix.h @@ -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; \ diff --git a/include/uv/win.h b/include/uv/win.h index b2ac7bb1..e8e07b2a 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -468,6 +468,7 @@ typedef struct { uv_buf_t bufsml[4]; \ } info; \ struct { \ + double btime; \ double atime; \ double mtime; \ } time; \ diff --git a/src/unix/fs.c b/src/unix/fs.c index f980011a..e1229bf0 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -30,6 +30,7 @@ #include "internal.h" #include +#include #include #include #include @@ -62,6 +63,49 @@ #if defined(__APPLE__) # include +# include + +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, ×, &i); + + return fsetattrlist(req->file, &attr_list, ×, 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, ×, &i); + + return setattrlist(req->path, &attr_list, ×, 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; diff --git a/src/win/fs.c b/src/win/fs.c index 782241f9..d8649951 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -25,7 +25,10 @@ #include #include #include /* file constants */ +#include #include /* stat constants */ +#include +#include #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; diff --git a/test/test-fs.c b/test/test-fs.c index 8727e78e..4e3e96c7 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -23,8 +23,9 @@ #include "task.h" #include -#include /* memset */ #include +#include +#include /* memset */ #include /* 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; diff --git a/test/test-list.h b/test/test-list.h index ab015193..e46e2fda 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -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)