diff options
-rw-r--r-- | libbutl/filesystem.cxx | 179 | ||||
-rw-r--r-- | libbutl/filesystem.hxx | 43 | ||||
-rw-r--r-- | tests/dir-iterator/driver.cxx | 26 | ||||
-rw-r--r-- | tests/dir-iterator/testscript | 2 |
4 files changed, 198 insertions, 52 deletions
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<duration> (chrono::nanoseconds (nsec)); + }; + + return {tm (s.st_mtime, mnsec<struct stat> (&s, true)), + tm (s.st_atime, ansec<struct stat> (&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<duration> (chrono::nanoseconds (nsec)); - }; - - return {tm (s.st_mtime, mnsec<struct stat> (&s, true)), - tm (s.st_atime, ansec<struct stat> (&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<bool, entry_stat> - 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<uint64_t> (t.dwHighDateTime) << 32) | + t.dwLowDateTime); + + nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch. + nsec *= 100; // Now in nanoseconds. + + return timestamp ( + chrono::duration_cast<duration> (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<chrono::nanoseconds> ( + 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<bool, entry_stat> + 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<bool, entry_stat> + path_entry (const path& p, bool fl, bool ie, entry_time* et) + { + return path_entry (p.string ().c_str (), fl, ie, et); + } + + pair<bool, entry_stat> + 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<uint64_t> (t.dwHighDateTime) << 32) | - t.dwLowDateTime); - - nsec -= 11644473600ULL * 10000000; // Now in UNIX epoch. - nsec *= 100; // Now in nanoseconds. - - return timestamp ( - chrono::duration_cast<duration> (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<bool, WIN32_FILE_ATTRIBUTE_DATA> 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<chrono::nanoseconds> ( - 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<struct dirent> (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<bool, entry_stat> e (path_entry (p, follow_symlinks)); + entry_time et; + pair<bool, entry_stat> 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<entry_type> t_; // Entry type. mutable optional<entry_type> lt_; // Symlink target type. + mutable timestamp mtime_ = timestamp_unknown; + mutable timestamp atime_ = timestamp_unknown; + path_type p_; dir_path b_; }; diff --git a/tests/dir-iterator/driver.cxx b/tests/dir-iterator/driver.cxx index 92b0b57..c9f7218 100644 --- a/tests/dir-iterator/driver.cxx +++ b/tests/dir-iterator/driver.cxx @@ -7,6 +7,7 @@ #include <libbutl/path.hxx> #include <libbutl/path-io.hxx> #include <libbutl/utility.hxx> // operator<<(ostream, exception) +#include <libbutl/timestamp.hxx> #include <libbutl/filesystem.hxx> #undef NDEBUG @@ -38,6 +39,10 @@ operator<< (ostream& os, entry_type e) // Ignore dangling symlinks, rather than fail trying to obtain the target // type. // +// -d +// Detect dangling symlinks, rather than fail trying to obtain the target +// type. +// int main (int argc, const char* argv[]) { @@ -80,9 +85,30 @@ main (int argc, const char* argv[]) detect_dangling ? dir_iterator::detect_dangling : dir_iterator::no_follow))) { + timestamp mt (de.mtime ()); + timestamp at (de.atime ()); + entry_type lt (de.ltype ()); entry_type t (lt == entry_type::symlink ? de.type () : lt); + const path& p (de.path ()); + path fp (de.base () / p); + + entry_time et (t == entry_type::directory + ? dir_time (path_cast<dir_path> (fp)) + : file_time (fp)); + + if (mt != timestamp_unknown) + assert (mt == et.modification); + + if (at != timestamp_unknown) + assert (mt == et.access); + + if (de.mtime () != timestamp_unknown) + assert (de.mtime () == et.modification); + + if (de.atime () != timestamp_unknown) + assert (de.atime () == et.access); if (verbose) { diff --git a/tests/dir-iterator/testscript b/tests/dir-iterator/testscript index 9ecc58b..9bc5513 100644 --- a/tests/dir-iterator/testscript +++ b/tests/dir-iterator/testscript @@ -7,6 +7,8 @@ test.options = -v : mkdir a; touch a/b; +sleep 1; +echo "a" >=a/b; # Change modification time. $* a >"reg b" : dir |