aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2022-12-23 22:57:27 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2023-01-09 11:33:08 +0300
commit31b74a064317186d7e891207de803b9d6dfb2642 (patch)
tree5c38a5728d72168a8c9218befaa2a6e42674ff6e
parentbded624bacbc284e523e44d73572a4f60d817b03 (diff)
Add mtime() and atime() to dir_entry
-rw-r--r--libbutl/filesystem.cxx179
-rw-r--r--libbutl/filesystem.hxx43
-rw-r--r--tests/dir-iterator/driver.cxx26
-rw-r--r--tests/dir-iterator/testscript2
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