From 21c56816eabee559559b5a03131a22dc420f0888 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 24 Jun 2020 08:53:16 +0200 Subject: Don't use utime() on Windows It has the seconds precision even if the time is unspecified. --- libbutl/filesystem.cxx | 100 ++++++++++++++++++++++++++++++++------------ libbutl/filesystem.mxx | 2 + tests/entry-time/driver.cxx | 2 +- 3 files changed, 77 insertions(+), 27 deletions(-) diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx index 4698ed9..e4d85b7 100644 --- a/libbutl/filesystem.cxx +++ b/libbutl/filesystem.cxx @@ -9,7 +9,6 @@ #ifndef _WIN32 # include // rename() -# include // utime() # include // PATH_MAX # include // struct dirent, *dir() # include // symlink(), link(), stat(), rmdir(), unlink() @@ -24,7 +23,6 @@ # include // FSCTL_SET_REPARSE_POINT # include // _stat # include // _stat(), S_I* -# include // _utime() # include // mbsrtowcs(), wcsrtombs(), mbstate_t # include // strncmp() @@ -247,6 +245,8 @@ namespace butl static void entry_tm (const char* p, const entry_time& t, bool dir) { + // @@ Perhaps fdopen/fstat/futimens would be more efficient (also below)? + struct stat s; if (stat (p, &s) != 0) throw_generic_error (errno); @@ -342,7 +342,7 @@ namespace butl // path refers to an existing entry and nullhandle otherwise. Follow reparse // points by default. Underlying OS errors are reported by throwing // std::system_error, unless ignore_error is true in which case nullhandle - // is returned. In the later case the error code can be obtained by calling + // is returned. In the latter case the error code can be obtained by calling // GetLastError(). // static inline pair @@ -799,11 +799,13 @@ namespace butl static void entry_tm (const char* p, const entry_time& t, bool dir) { + // See also touch_file() below. + // pair hi ( entry_info_handle (p, true /* write */)); // If the entry is of the wrong type, then let's pretend that it doesn't - // exists. + // exist. // if (hi.first == nullhandle || directory (hi.second.dwFileAttributes) != dir) @@ -831,41 +833,87 @@ namespace butl #endif +#ifndef _WIN32 bool touch_file (const path& p, bool create) { - if (file_exists (p)) + // utimes() (as well as utimensat()) have an unfortunate property of + // succeeding if the path is a directory. + // + // @@ Perhaps fdopen/fstat/futimens would be more efficient (also above)? + // + pair pe (path_entry (p, true /* follow_symlinks */)); + + // If the entry is of the wrong type, then let's pretend that it doesn't + // exist. + // + if (!pe.first || pe.second.type != entry_type::regular) { - // Note that on Windows there are two times: the precise time (which is - // what we get with system_clock::now()) and what we will call the - // "filesystem time", which can lag the precise time by as much as a - // couple of milliseconds. To get the filesystem time use - // GetSystemTimeAsFileTime(). This is just a heads-up in case we decide - // to change this code at some point. - // -#ifndef _WIN32 - if (utime (p.string ().c_str (), nullptr) == -1) -#else - // Note: follows reparse points. - // - if (_utime (p.string ().c_str (), nullptr) == -1) -#endif + if (!create) + throw_generic_error (ENOENT); + } + else + { + if (utimes (p.string ().c_str (), nullptr) == -1) throw_generic_error (errno); return false; } - if (create && !entry_exists (p)) + // Assuming the file access and modification times are set to the + // current time automatically. + // + fdopen (p, fdopen_mode::out | fdopen_mode::create); + return true; + } + +#else + + bool + touch_file (const path& p, bool create) + { + // We cannot use utime() on Windows since it has the seconds precision + // even if we don't specify the time explicitly. So we use SetFileTime() + // similar to entry_tm() above. + // + // Note also that on Windows there are two times: the precise time (which + // is what we get with system_clock::now()) and what we will call the + // "filesystem time", which can lag the precise time by as much as a + // couple of milliseconds. To get the filesystem time one uses + // GetSystemTimeAsFileTime(). We use this time here in order to keep + // timestamps consistent with other operations where they are updated + // implicitly. + // + pair hi ( + entry_info_handle (p.string ().c_str (), true /* write */)); + + // If the entry is of the wrong type, then let's pretend that it doesn't + // exist. + // + if (hi.first == nullhandle || directory (hi.second.dwFileAttributes)) { - // Assuming the file access and modification times are set to the - // current time automatically. - // - fdopen (p, fdopen_mode::out | fdopen_mode::create); - return true; + if (!create) + throw_generic_error (ENOENT); } + else + { + FILETIME ft; + GetSystemTimeAsFileTime (&ft); // Does not fail. - throw_generic_error (ENOENT); // Does not exist or not a file. + if (!SetFileTime (hi.first.get (), NULL /* lpCreationTime */, &ft, &ft)) + throw_system_error (GetLastError ()); + + hi.first.close (); // Checks for error. + return false; + } + + // Assuming the file access and modification times are set to the current + // time automatically. + // + fdopen (p, fdopen_mode::out | fdopen_mode::create); + return true; } +#endif mkdir_status #ifndef _WIN32 diff --git a/libbutl/filesystem.mxx b/libbutl/filesystem.mxx index 94ed366..935fc3f 100644 --- a/libbutl/filesystem.mxx +++ b/libbutl/filesystem.mxx @@ -548,6 +548,8 @@ LIBBUTL_MODEXPORT namespace butl // timestamp_nonexistent then it is left unchanged. All errors are reported // by throwing std::system_error. // + // Note: use touch_file() instead of file_mtime(system_clock::now()). + // LIBBUTL_SYMEXPORT void file_time (const char*, const entry_time&); diff --git a/tests/entry-time/driver.cxx b/tests/entry-time/driver.cxx index 5432dcc..1e64b0d 100644 --- a/tests/entry-time/driver.cxx +++ b/tests/entry-time/driver.cxx @@ -36,7 +36,7 @@ using namespace butl; // entry. // // -p -// Print the filesystem entry time as as a number of milliseconds passed +// Print the filesystem entry time as a number of milliseconds passed // since UNIX epoch. // // -s -- cgit v1.1