From c1b0d4b1e5dbea7d9e161f0842df4fcc7173e6e1 Mon Sep 17 00:00:00 2001 From: Brendan Ashworth Date: Sat, 15 Aug 2015 18:53:21 -0700 Subject: [PATCH] unix: use oldest ctime,atime,mtime as birthtime When the operating system does not support birthtime for stat, previously behaviour was to use the ctime (last changed) time instead. This change instead enforces that uv_fs_stat will use the oldest available time (of ctime, atime, and mtime) rather than arbitrarily picking ctime. Fixes: https://github.com/nodejs/node/issues/2222 --- src/unix/fs.c | 39 +++++++++++++++++++++++++++---- test/test-fs.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ test/test-list.h | 2 ++ 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/unix/fs.c b/src/unix/fs.c index 3ec6bf8956c..a001f41562e 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -701,8 +701,24 @@ static void uv__to_stat(struct stat* src, uv_stat_t* dst) { dst->st_mtim.tv_nsec = src->st_mtime_nsec; dst->st_ctim.tv_sec = src->st_ctime; dst->st_ctim.tv_nsec = src->st_ctime_nsec; - dst->st_birthtim.tv_sec = src->st_ctime; - dst->st_birthtim.tv_nsec = src->st_ctime_nsec; + + /* On systems that do not support birthtime, resolve the value to the least + * time available (oldest of atime, mtime, ctime), disregarding nanoseconds. + */ + if (src->st_atime < src->st_mtime && src->st_atime < src->st_ctime) { + /* Use last accessed time. */ + dst->st_birthtim.tv_sec = src->st_atime; + dst->st_birthtim.tv_nsec = src->st_atime_nsec; + } else if (src->st_mtime < src->st_ctime) { + /* Use last modified time. */ + dst->st_birthtim.tv_sec = src->st_mtime; + dst->st_birthtim.tv_nsec = src->st_mtime_nsec; + } else { + /* Use last change time. */ + dst->st_birthtim.tv_sec = src->st_ctime; + dst->st_birthtim.tv_nsec = src->st_ctime_nsec; + } + dst->st_flags = 0; dst->st_gen = 0; #elif !defined(_AIX) && ( \ @@ -725,8 +741,23 @@ static void uv__to_stat(struct stat* src, uv_stat_t* dst) { dst->st_flags = src->st_flags; dst->st_gen = src->st_gen; # else - dst->st_birthtim.tv_sec = src->st_ctim.tv_sec; - dst->st_birthtim.tv_nsec = src->st_ctim.tv_nsec; + /* On systems that do not support birthtime, resolve the value to the least + * time available (oldest of atime, mtime, ctime), disregarding nanoseconds. + */ + if (src->st_atime < src->st_mtime && src->st_atime < src->st_ctime) { + /* Use last accessed time. */ + dst->st_birthtim.tv_sec = src->st_atim.tv_sec; + dst->st_birthtim.tv_nsec = src->st_atim.tv_nsec; + } else if (src->st_mtime < src->st_ctime) { + /* Use last modified time. */ + dst->st_birthtim.tv_sec = src->st_mtim.tv_sec; + dst->st_birthtim.tv_nsec = src->st_mtim.tv_nsec; + } else { + /* Use last change time. */ + dst->st_birthtim.tv_sec = src->st_ctim.tv_sec; + dst->st_birthtim.tv_nsec = src->st_ctim.tv_nsec; + } + dst->st_flags = 0; dst->st_gen = 0; # endif diff --git a/test/test-fs.c b/test/test-fs.c index a96f4325fd7..531194fb0f7 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -2535,3 +2535,63 @@ TEST_IMPL(fs_read_write_null_arguments) { return 0; } + + +TEST_IMPL(fs_stat_small_birthtime) { + int r; + long birthtime; + uv_buf_t* iovs; + + unlink("test_file"); + + loop = uv_default_loop(); + + /* First open the file, then write and read, in that order. uv_fs_stat used + * to give preference to ctime, which would in this case give the smaller + * time. In this case, atime will be the returned birthtime, for systems that + * do not support birthtime. + */ + r = uv_fs_open(loop, + &open_req1, + "test_file", + O_RDWR | O_CREAT, + S_IWUSR | S_IRUSR, + NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&open_req1); + + iovs = malloc(sizeof(*iovs) * 1); + iovs[0] = uv_buf_init(test_buf, sizeof(test_buf)); + + r = uv_fs_write(loop, &write_req, open_req1.result, iovs, 1, 0, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&write_req); + + r = uv_fs_read(loop, &read_req, open_req1.result, iovs, 1, 0, NULL); + ASSERT(r >= 0); + uv_fs_req_cleanup(&read_req); + + /* And now, we test. */ + + r = uv_fs_stat(loop, &stat_req, "test_file", NULL); + ASSERT(r >= 0); + ASSERT(stat_req.result >= 0); + + /* Birthtime must be less than or equal to all of the other stat time fields: + * ctime, atime, and mtime. + */ + birthtime = stat_req.statbuf.st_birthtim.tv_sec; + + ASSERT(birthtime <= stat_req.statbuf.st_ctim.tv_sec); + ASSERT(birthtime <= stat_req.statbuf.st_atim.tv_sec); + ASSERT(birthtime <= stat_req.statbuf.st_mtim.tv_sec); + + /* Cleanup. */ + uv_fs_req_cleanup(&stat_req); + unlink("test_file"); + free(iovs); + + MAKE_VALGRIND_HAPPY(); + + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index c6f1d3c3327..5d4e825a88f 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -276,6 +276,7 @@ TEST_DECLARE (fs_open_dir) TEST_DECLARE (fs_rename_to_existing_file) TEST_DECLARE (fs_write_multiple_bufs) TEST_DECLARE (fs_read_write_null_arguments) +TEST_DECLARE (fs_stat_small_birthtime) TEST_DECLARE (fs_write_alotof_bufs) TEST_DECLARE (fs_write_alotof_bufs_with_offset) TEST_DECLARE (threadpool_queue_work_simple) @@ -692,6 +693,7 @@ TASK_LIST_START TEST_ENTRY (fs_write_alotof_bufs) TEST_ENTRY (fs_write_alotof_bufs_with_offset) TEST_ENTRY (fs_read_write_null_arguments) + TEST_ENTRY (fs_stat_small_birthtime) TEST_ENTRY (threadpool_queue_work_simple) TEST_ENTRY (threadpool_queue_work_einval) TEST_ENTRY (threadpool_multiple_event_loops)