From 31b74a064317186d7e891207de803b9d6dfb2642 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 23 Dec 2022 22:57:27 +0300 Subject: Add mtime() and atime() to dir_entry --- libbutl/filesystem.cxx | 179 +++++++++++++++++++++++++++++++++++-------------- libbutl/filesystem.hxx | 43 +++++++++++- 2 files changed, 170 insertions(+), 52 deletions(-) (limited to 'libbutl') diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx index 55ead14..bb3e8b0 100644 --- a/libbutl/filesystem.cxx +++ b/libbutl/filesystem.cxx @@ -184,6 +184,19 @@ namespace butl // static inline constexpr int // ansec (...) {return 0;} + static inline entry_time + entry_tm (const struct stat& s) noexcept + { + auto tm = [] (time_t sec, auto nsec) -> timestamp + { + return system_clock::from_time_t (sec) + + chrono::duration_cast (chrono::nanoseconds (nsec)); + }; + + return {tm (s.st_mtime, mnsec (&s, true)), + tm (s.st_atime, ansec (&s, true))}; + } + // Return the modification and access times of a regular file or directory. // static entry_time @@ -201,14 +214,7 @@ namespace butl if (dir ? !S_ISDIR (s.st_mode) : !S_ISREG (s.st_mode)) return {timestamp_nonexistent, timestamp_nonexistent}; - auto tm = [] (time_t sec, auto nsec) -> timestamp - { - return system_clock::from_time_t (sec) + - chrono::duration_cast (chrono::nanoseconds (nsec)); - }; - - return {tm (s.st_mtime, mnsec (&s, true)), - tm (s.st_atime, ansec (&s, true))}; + return entry_tm (s); } // Set the modification and access times for a regular file or directory. @@ -641,8 +647,48 @@ namespace butl return reparse_point_entry (p.string ().c_str (), ie); } - pair - path_entry (const char* p, bool fl, bool ie) + static inline timestamp + to_timestamp (const FILETIME& t) + { + // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch" + // (1601-01-01T00:00:00Z). To convert it to "UNIX epoch" + // (1970-01-01T00:00:00Z) we need to subtract 11644473600 seconds. + // + uint64_t nsec ((static_cast (t.dwHighDateTime) << 32) | + t.dwLowDateTime); + + nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch. + nsec *= 100; // Now in nanoseconds. + + return timestamp ( + chrono::duration_cast (chrono::nanoseconds (nsec))); + } + + static inline FILETIME + to_filetime (timestamp t) + { + // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch" + // (1601-01-01T00:00:00Z). To convert "UNIX epoch" (1970-01-01T00:00:00Z) + // to it we need to add 11644473600 seconds. + // + uint64_t ticks (chrono::duration_cast ( + t.time_since_epoch ()).count ()); + + ticks /= 100; // Now in 100 nanosecond "ticks". + ticks += 11644473600ULL * 10000000; // Now in "Windows epoch". + + FILETIME r; + r.dwHighDateTime = (ticks >> 32) & 0xFFFFFFFF; + r.dwLowDateTime = ticks & 0xFFFFFFFF; + return r; + } + + // If the being returned entry type is regular or directory and et is not + // NULL, then also save the entry modification and access times into the + // referenced variable. + // + static inline pair + path_entry (const char* p, bool fl, bool ie, entry_time* et) { // A path like 'C:', while being a root path in our terminology, is not as // such for Windows, that maintains current directory for each drive, and @@ -665,8 +711,14 @@ namespace butl if (!pi.first) return make_pair (false, entry_stat {entry_type::unknown, 0}); - auto entry_info = [] (const auto& ei) + auto entry_info = [et] (const auto& ei) { + if (et != nullptr) + { + et->modification = to_timestamp (ei.ftLastWriteTime); + et->access = to_timestamp (ei.ftLastAccessTime); + } + if (directory (ei.dwFileAttributes)) return make_pair (true, entry_stat {entry_type::directory, 0}); else @@ -705,6 +757,18 @@ namespace butl return make_pair (true, entry_stat {entry_type::other, 0}); } + static inline pair + path_entry (const path& p, bool fl, bool ie, entry_time* et) + { + return path_entry (p.string ().c_str (), fl, ie, et); + } + + pair + path_entry (const char* p, bool fl, bool ie) + { + return path_entry (p, fl, ie, nullptr /* entry_time */); + } + permissions path_permissions (const path& p) { @@ -770,24 +834,8 @@ namespace butl if (!pi.first || directory (pi.second.dwFileAttributes) != dir) return entry_time {timestamp_nonexistent, timestamp_nonexistent}; - auto tm = [] (const FILETIME& t) -> timestamp - { - // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch" - // (1601-01-01T00:00:00Z). To convert it to "UNIX epoch" - // (1970-01-01T00:00:00Z) we need to subtract 11644473600 seconds. - // - uint64_t nsec ((static_cast (t.dwHighDateTime) << 32) | - t.dwLowDateTime); - - nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch. - nsec *= 100; // Now in nanoseconds. - - return timestamp ( - chrono::duration_cast (chrono::nanoseconds (nsec))); - }; - - return entry_time {tm (pi.second.ftLastWriteTime), - tm (pi.second.ftLastAccessTime)}; + return entry_time {to_timestamp (pi.second.ftLastWriteTime), + to_timestamp (pi.second.ftLastAccessTime)}; }; pair pi (path_entry_info (p)); @@ -797,25 +845,6 @@ namespace butl path_entry_handle_info (p, true /* follow_reparse_points */)); } - static inline FILETIME - to_filetime (timestamp t) - { - // Time in FILETIME is in 100 nanosecond "ticks" since "Windows epoch" - // (1601-01-01T00:00:00Z). To convert "UNIX epoch" - // (1970-01-01T00:00:00Z) to it we need to add 11644473600 seconds. - // - uint64_t ticks (chrono::duration_cast ( - t.time_since_epoch ()).count ()); - - ticks /= 100; // Now in 100 nanosecond "ticks". - ticks += 11644473600ULL * 10000000; // Now in "Windows epoch". - - FILETIME r; - r.dwHighDateTime = (ticks >> 32) & 0xFFFFFFFF; - r.dwLowDateTime = ticks & 0xFFFFFFFF; - return r; - } - // Set the modification and access times for a regular file or directory. // static void @@ -1900,7 +1929,18 @@ namespace butl : lstat (p.string ().c_str (), &s)) != 0) throw_generic_error (errno); - return butl::type (s); + entry_type r (butl::type (s)); + + // While at it, also save the entry modification and access times. + // + if (r != entry_type::symlink) + { + entry_time t (entry_tm (s)); + mtime_ = t.modification; + atime_ = t.access; + } + + return r; } // dir_iterator @@ -1986,6 +2026,9 @@ namespace butl e_.t_ = d_type (de, nullptr); e_.lt_ = nullopt; + e_.mtime_ = timestamp_unknown; + e_.atime_ = timestamp_unknown; + // If requested, we ignore dangling symlinks, skipping ones with // non-existing or inaccessible targets (ignore_dangling mode), or set // the entry_type::unknown type for them (detect_dangling mode). @@ -2025,6 +2068,13 @@ namespace butl } e_.t_ = type (s); + + if (*e_.t_ != entry_type::symlink) + { + entry_time t (entry_tm (s)); + e_.mtime_ = t.modification; + e_.atime_ = t.access; + } } // The entry type should be present and may not be @@ -2051,7 +2101,13 @@ namespace butl throw_generic_error (errno); } else + { e_.lt_ = type (s); + + entry_time t (entry_tm (s)); + e_.mtime_ = t.modification; + e_.atime_ = t.access; + } } // The symlink target type should be present and in the @@ -2087,12 +2143,23 @@ namespace butl // mode (see dir_iterator::next () implementation for details). Thus, we // always throw if the entry info can't be retrieved. // + // While at it, also save the entry modification and access times. + // path_type p (base () / path ()); - pair e (path_entry (p, follow_symlinks)); + entry_time et; + pair e ( + path_entry (p, follow_symlinks, false /* ignore_error */, &et)); if (!e.first) throw_generic_error (ENOENT); + if (e.second.type == entry_type::regular || + e.second.type == entry_type::directory) + { + mtime_ = et.modification; + atime_ = et.access; + } + return e.second.type; } @@ -2210,6 +2277,15 @@ namespace butl e_.lt_ = nullopt; + e_.mtime_ = rp ? timestamp_unknown : to_timestamp (fi.ftLastWriteTime); + + // Note that according to MSDN for the FindFirstFile[Ex]() function + // "the NTFS file system delays updates to the last access time for a + // file by up to 1 hour after the last access" and "on the FAT file + // system access time has a resolution of 1 day". + // + e_.atime_ = timestamp_unknown; + // If requested, we ignore dangling symlinks and junctions, skipping // ones with non-existing or inaccessible targets (ignore_dangling // mode), or set the entry_type::unknown type for them @@ -2306,6 +2382,9 @@ namespace butl e_.lt_ = directory (ti.second.dwFileAttributes) ? entry_type::directory : entry_type::regular; + + e_.mtime_ = to_timestamp (ti.second.ftLastWriteTime); + e_.atime_ = to_timestamp (ti.second.ftLastAccessTime); } } diff --git a/libbutl/filesystem.hxx b/libbutl/filesystem.hxx index 71b3ebd..ea5597d 100644 --- a/libbutl/filesystem.hxx +++ b/libbutl/filesystem.hxx @@ -664,6 +664,33 @@ namespace butl entry_type ltype () const; + // Modification and access times of the filesystem entry if it is not a + // symlink and of the symlink target otherwise. + // + // These are provided as an optimization if they can be obtained as a + // byproduct of work that is already being done anyway (iteration itself, + // calls to [l]type(), etc). If (not yet) available, timestamp_unknown is + // returned. + // + // Specifically: + // + // - On Windows mtime is always set by dir_iterator for entries other than + // reparse points. + // + // - On all platforms mtime and atime are always set for symlink targets + // by dir_iterator in the {detect,ignore}_dangling modes. + // + // - On all platforms mtime and atime can potentially be set by [l]type() + // if the stat() call is required to retrieve the type information (the + // native directory entry iterating API doesn't provide it, the type of + // the symlink target is queried, etc). + // + timestamp + mtime () const {return mtime_;} + + timestamp + atime () const {return atime_;} + // Entry path (excluding the base). To get the full path, do // base () / path (). // @@ -674,8 +701,17 @@ namespace butl base () const {return b_;} dir_entry () = default; - dir_entry (entry_type t, path_type p, dir_path b) - : t_ (t), p_ (std::move (p)), b_ (std::move (b)) {} + + dir_entry (entry_type t, + path_type p, + dir_path b, + timestamp mt = timestamp_unknown, + timestamp at = timestamp_unknown) + : t_ (t), + mtime_ (mt), + atime_ (at), + p_ (std::move (p)), + b_ (std::move (b)) {} private: entry_type @@ -689,6 +725,9 @@ namespace butl mutable optional t_; // Entry type. mutable optional lt_; // Symlink target type. + mutable timestamp mtime_ = timestamp_unknown; + mutable timestamp atime_ = timestamp_unknown; + path_type p_; dir_path b_; }; -- cgit v1.1