aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2018-03-18 00:31:56 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2018-03-19 16:11:56 +0300
commitda7be5f8c18879d1ca21f76470fc83d2c5cbd1c4 (patch)
tree0fd0dc1dc61c6a23b49ff2c15ed5084b671b95de
parentf3eef5c4ddfa54fa7f6d155e90721cde318cc6f2 (diff)
Add support for copying file timestamps by cpfile()
-rw-r--r--libbutl/filesystem.cxx203
-rw-r--r--libbutl/filesystem.mxx138
-rw-r--r--tests/entry-time/buildfile8
-rw-r--r--tests/entry-time/driver.cxx151
-rw-r--r--tests/entry-time/testscript63
5 files changed, 514 insertions, 49 deletions
diff --git a/libbutl/filesystem.cxx b/libbutl/filesystem.cxx
index bd91c07..a6c7ae8 100644
--- a/libbutl/filesystem.cxx
+++ b/libbutl/filesystem.cxx
@@ -484,6 +484,9 @@ namespace butl
cpflags::overwrite_permissions)
path_permissions (to, perm);
+ if ((fl & cpflags::copy_timestamps) == cpflags::copy_timestamps)
+ file_time (to, file_time (from));
+
rm.cancel ();
}
@@ -580,18 +583,7 @@ namespace butl
// Copy file access and modification times.
//
- struct stat s;
- if (stat (f, &s) != 0)
- throw_generic_error (errno);
-
- timeval times[2];
- times[0].tv_sec = s.st_atime;
- times[0].tv_usec = ansec<struct stat> (&s, true) / 1000;
- times[1].tv_sec = s.st_mtime;
- times[1].tv_usec = mnsec<struct stat> (&s, true) / 1000;
-
- if (utimes (t, times) != 0)
- throw_generic_error (errno);
+ file_time (t, file_time (f));
// Finally, remove the source file.
//
@@ -633,8 +625,8 @@ namespace butl
//
DWORD ec (GetLastError ());
if ((ec == ERROR_ALREADY_EXISTS || ec == ERROR_ACCESS_DENIED) && td &&
- try_rmdir (path_cast<dir_path> (to)) != rmdir_status::not_empty &&
- MoveFileExA (f, t, mfl))
+ try_rmdir (path_cast<dir_path> (to)) != rmdir_status::not_empty &&
+ MoveFileExA (f, t, mfl))
return;
throw_system_error (ec);
@@ -642,25 +634,34 @@ namespace butl
#endif
}
- timestamp
- file_mtime (const char* p)
+ // Return the modification and access times of a regular file or directory.
+ //
+ static entry_time
+ entry_tm (const char* p, bool dir)
{
#ifndef _WIN32
+
struct stat s;
if (stat (p, &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
- return timestamp_nonexistent;
+ return {timestamp_nonexistent, timestamp_nonexistent};
else
throw_generic_error (errno);
}
- if (!S_ISREG (s.st_mode))
- return timestamp_nonexistent;
+ if (dir ? !S_ISDIR (s.st_mode) : !S_ISREG (s.st_mode))
+ return {timestamp_nonexistent, timestamp_nonexistent};
+
+ auto tm = [] (time_t sec, uint64_t 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 system_clock::from_time_t (s.st_mtime) +
- chrono::duration_cast<duration> (
- chrono::nanoseconds (mnsec<struct stat> (&s, true)));
#else
WIN32_FILE_ATTRIBUTE_DATA s;
@@ -675,32 +676,162 @@ namespace butl
ec == ERROR_INVALID_DRIVE ||
ec == ERROR_BAD_PATHNAME ||
ec == ERROR_BAD_NETPATH)
- return timestamp_nonexistent;
+ return {timestamp_nonexistent, timestamp_nonexistent};
throw_system_error (ec);
}
- if ((s.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
- return timestamp_nonexistent;
+ if ((s.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) !=
+ (dir ? FILE_ATTRIBUTE_DIRECTORY : 0))
+ return {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 {tm (s.ftLastWriteTime), tm (s.ftLastAccessTime)};
+
+#endif
+ }
+
+ entry_time
+ file_time (const char* p)
+ {
+ return entry_tm (p, false);
+ }
+
+ entry_time
+ dir_time (const char* p)
+ {
+ return entry_tm (p, true);
+ }
- // 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.
+ // Set the modification and access times for a regular file or directory.
+ //
+ static void
+ entry_tm (const char* p, const entry_time& t, bool dir)
+ {
+#ifndef _WIN32
+
+ struct stat s;
+ if (stat (p, &s) != 0)
+ throw_generic_error (errno);
+
+ // If the entry is of the wrong type, then let's pretend that it doesn't
+ // exists. In other words, the entry of the required type doesn't exist.
+ //
+ if (dir ? !S_ISDIR (s.st_mode) : !S_ISREG (s.st_mode))
+ return throw_generic_error (ENOENT);
+
+ // Note: timeval has the microsecond resolution.
//
- const FILETIME& t (s.ftLastWriteTime);
+ auto tm = [] (timestamp t, long sec, long nsec) -> timeval
+ {
+ if (t == timestamp_nonexistent)
+ return {sec, nsec / 1000}; // Filesystem entry current time.
+
+ uint64_t msec (chrono::duration_cast<chrono::microseconds> (
+ t.time_since_epoch ()).count ());
+
+ return {static_cast<long> (msec / 1000000), // Seconds.
+ static_cast<long> (msec % 1000000)}; // Microseconds.
+ };
+
+ timeval times[2];
+ times[0] = tm (t.access, s.st_atime, ansec<struct stat> (&s, true));
+ times[1] = tm (t.modification, s.st_mtime, mnsec<struct stat> (&s, true));
- uint64_t ns ((static_cast<uint64_t> (t.dwHighDateTime) << 32) |
- t.dwLowDateTime);
+ if (utimes (p, times) != 0)
+ throw_generic_error (errno);
+
+#else
- ns -= 11644473600ULL * 10000000; // Now in UNIX epoch.
- ns *= 100; // Now in nanoseconds.
+ DWORD attr (GetFileAttributes (p));
+
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ throw_system_error (GetLastError ());
+
+ // If the entry is of the wrong type, then let's pretend that it doesn't
+ // exists.
+ //
+ if ((attr & FILE_ATTRIBUTE_DIRECTORY) !=
+ (dir ? FILE_ATTRIBUTE_DIRECTORY : 0))
+ return throw_generic_error (ENOENT);
+
+ HANDLE h (CreateFile (p,
+ FILE_WRITE_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ dir ? FILE_FLAG_BACKUP_SEMANTICS : 0,
+ NULL));
+
+ if (h == INVALID_HANDLE_VALUE)
+ throw_system_error (GetLastError ());
+
+ unique_ptr<HANDLE, void (*)(HANDLE*)> deleter (
+ &h, [] (HANDLE* h)
+ {
+ bool r (CloseHandle (*h));
+
+ // The valid file handle that has no IO operations being performed on
+ // it should close successfully, unless something is severely damaged.
+ //
+ assert (r);
+ });
+
+ auto tm = [] (timestamp t, FILETIME& ft) -> const FILETIME*
+ {
+ if (t == timestamp_nonexistent)
+ return NULL;
+
+ // 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".
+
+ ft.dwHighDateTime = (ticks >> 32) & 0xFFFFFFFF;
+ ft.dwLowDateTime = ticks & 0xFFFFFFFF;
+ return &ft;
+ };
+
+ FILETIME at;
+ FILETIME mt;
+ if (!SetFileTime (h, NULL, tm (t.access, at), tm (t.modification, mt)))
+ throw_system_error (GetLastError ());
- return timestamp (
- chrono::duration_cast<duration> (
- chrono::nanoseconds (ns)));
#endif
}
+ void
+ file_time (const char* p, const entry_time& t)
+ {
+ entry_tm (p, t, false);
+ }
+
+ void
+ dir_time (const char* p, const entry_time& t)
+ {
+ entry_tm (p, t, true);
+ }
+
permissions
path_permissions (const path& p)
{
diff --git a/libbutl/filesystem.mxx b/libbutl/filesystem.mxx
index a87e57b..7f93739 100644
--- a/libbutl/filesystem.mxx
+++ b/libbutl/filesystem.mxx
@@ -272,8 +272,10 @@ LIBBUTL_MODEXPORT namespace butl
//
enum class cpflags: std::uint16_t
{
- overwrite_content = 0x1,
- overwrite_permissions = 0x2,
+ overwrite_content = 0x1, // Overwrite content of destination.
+ overwrite_permissions = 0x2, // Overwrite permissions of destination.
+
+ copy_timestamps = 0x4, // Copy timestamps from source.
none = 0
};
@@ -283,11 +285,11 @@ LIBBUTL_MODEXPORT namespace butl
inline cpflags operator&= (cpflags&, cpflags);
inline cpflags operator|= (cpflags&, cpflags);
- // Copy a regular file, including its permissions. Throw std::system_error
- // on failure. Fail if the destination file exists and the overwrite_content
- // flag is not set. Leave permissions of an existing destination file intact
- // unless the overwrite_permissions flag is set. Delete incomplete copies
- // before throwing.
+ // Copy a regular file, including its permissions, and optionally timestamps.
+ // Throw std::system_error on failure. Fail if the destination file exists
+ // and the overwrite_content flag is not set. Leave permissions of an
+ // existing destination file intact unless the overwrite_permissions flag is
+ // set. Delete incomplete copies before throwing.
//
// Note that in case of overwriting, the existing destination file gets
// truncated (not deleted) prior to being overwritten. As a side-effect,
@@ -384,17 +386,127 @@ LIBBUTL_MODEXPORT namespace butl
mventry_into (from, into, f);
}
- // Return timestamp_nonexistent if the entry at the specified path
- // does not exist or is not a path. All other errors are reported
- // by throwing std::system_error. Note that this function resolves
- // symlinks.
+ struct entry_time
+ {
+ timestamp modification;
+ timestamp access;
+ };
+
+ // Return timestamp_nonexistent for the modification and access times if the
+ // entry at the specified path does not exist or is not a regular file. All
+ // other errors are reported by throwing std::system_error. Note that these
+ // functions resolves symlinks.
//
- LIBBUTL_SYMEXPORT timestamp
- file_mtime (const char*);
+ LIBBUTL_SYMEXPORT entry_time
+ file_time (const char*);
+
+ inline entry_time
+ file_time (const path& p) {return file_time (p.string ().c_str ());}
+
+ inline timestamp
+ file_mtime (const char* p) {return file_time (p).modification;}
inline timestamp
file_mtime (const path& p) {return file_mtime (p.string ().c_str ());}
+ inline timestamp
+ file_atime (const char* p) {return file_time (p).access;}
+
+ inline timestamp
+ file_atime (const path& p) {return file_atime (p.string ().c_str ());}
+
+ // As above but return the directory times.
+ //
+ LIBBUTL_SYMEXPORT entry_time
+ dir_time (const char*);
+
+ inline entry_time
+ dir_time (const dir_path& p) {return dir_time (p.string ().c_str ());}
+
+ inline timestamp
+ dir_mtime (const char* p) {return dir_time (p).modification;}
+
+ inline timestamp
+ dir_mtime (const dir_path& p) {return dir_mtime (p.string ().c_str ());}
+
+ inline timestamp
+ dir_atime (const char* p) {return dir_time (p).access;}
+
+ inline timestamp
+ dir_atime (const dir_path& p) {return dir_atime (p.string ().c_str ());}
+
+ // Set a regular file modification and access times. If a time value is
+ // timestamp_nonexistent then it is left unchanged. All errors are reported
+ // by throwing std::system_error.
+ //
+ LIBBUTL_SYMEXPORT void
+ file_time (const char*, const entry_time&);
+
+ inline void
+ file_time (const path& p, const entry_time& t)
+ {
+ return file_time (p.string ().c_str (), t);
+ }
+
+ inline void
+ file_mtime (const char* p, timestamp t)
+ {
+ return file_time (p, {t, timestamp_nonexistent});
+ }
+
+ inline void
+ file_mtime (const path& p, timestamp t)
+ {
+ return file_mtime (p.string ().c_str (), t);
+ }
+
+ inline void
+ file_atime (const char* p, timestamp t)
+ {
+ return file_time (p, {timestamp_nonexistent, t});
+ }
+
+ inline void
+ file_atime (const path& p, timestamp t)
+ {
+ return file_atime (p.string ().c_str (), t);
+ }
+
+ // As above but set the directory times.
+ //
+ LIBBUTL_SYMEXPORT void
+ dir_time (const char*, const entry_time&);
+
+ inline void
+ dir_time (const dir_path& p, const entry_time& t)
+ {
+ return dir_time (p.string ().c_str (), t);
+ }
+
+ inline void
+ dir_mtime (const char* p, timestamp t)
+ {
+ return dir_time (p, {t, timestamp_nonexistent});
+ }
+
+ inline void
+ dir_mtime (const dir_path& p, timestamp t)
+ {
+ return dir_mtime (p.string ().c_str (), t);
+ }
+
+ inline void
+ dir_atime (const char* p, timestamp t)
+ {
+ return dir_time (p, {timestamp_nonexistent, t});
+ }
+
+ inline void
+ dir_atime (const dir_path& p, timestamp t)
+ {
+ return dir_atime (p.string ().c_str (), t);
+ }
+
// Path permissions.
//
enum class permissions: std::uint16_t
diff --git a/tests/entry-time/buildfile b/tests/entry-time/buildfile
new file mode 100644
index 0000000..7219149
--- /dev/null
+++ b/tests/entry-time/buildfile
@@ -0,0 +1,8 @@
+# file : tests/entry-time/buildfile
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+libs += $stdmod_lib
+
+exe{driver}: {hxx cxx}{*} $libs test{testscript}
diff --git a/tests/entry-time/driver.cxx b/tests/entry-time/driver.cxx
new file mode 100644
index 0000000..91e9bbc
--- /dev/null
+++ b/tests/entry-time/driver.cxx
@@ -0,0 +1,151 @@
+// file : tests/entry-time/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules
+#include <string>
+#include <chrono>
+#include <iostream>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules
+#ifdef __cpp_lib_modules
+import std.core;
+import std.io;
+#endif
+import butl.path;
+import butl.filesystem;
+
+import butl.optional; // @@ MOD Clang should not be necessary.
+#else
+#include <libbutl/path.mxx>
+#include <libbutl/optional.mxx>
+#include <libbutl/timestamp.mxx>
+#include <libbutl/filesystem.mxx>
+#endif
+
+using namespace std;
+using namespace butl;
+
+// Usage: argv[0] (-p|-s <time>) (-f|-d) (-m|-a) <path>
+//
+// Prints or sets the modification or access time for the specified filesystem
+// entry.
+//
+// -p
+// Print the filesystem entry time as as a number of milliseconds passed
+// since UNIX epoch.
+//
+// -s
+// Set the filesystem entry time that is represented as a number of
+// milliseconds passed since UNIX epoch.
+//
+// -f
+// The specified path is a file.
+//
+// -d
+// The specified path is a directory.
+//
+// -m
+// Print or set the filesystem entry modification time.
+//
+// -a
+// Print or set the filesystem entry access time.
+//
+int
+main (int argc, const char* argv[])
+{
+ using butl::optional;
+
+ optional<bool> set;
+ optional<bool> dir;
+ optional<bool> mod;
+ optional<path> p;
+ timestamp tm;
+
+ assert (argc > 0);
+
+ for (int i (1); i != argc; ++i)
+ {
+ string v (argv[i]);
+
+ if (v == "-p")
+ {
+ assert (!set);
+ set = false;
+ }
+ else if (v == "-s")
+ {
+ assert (!set);
+ set = true;
+
+ assert (i + 1 != argc);
+ tm = timestamp (chrono::duration_cast<duration> (
+ chrono::milliseconds (stoull (argv[++i]))));
+ }
+ else if (v == "-f")
+ {
+ assert (!dir);
+ dir = false;
+ }
+ else if (v == "-d")
+ {
+ assert (!dir);
+ dir = true;
+ }
+ else if (v == "-m")
+ {
+ assert (!mod);
+ mod = true;
+ }
+ else if (v == "-a")
+ {
+ assert (!mod);
+ mod = false;
+ }
+ else
+ {
+ assert (set && dir && mod && !p);
+ p = path (move (v));
+ }
+ }
+
+ assert (set);
+ assert (dir);
+ assert (mod);
+ assert (p);
+
+ if (*set)
+ {
+ if (*dir)
+ {
+ if (*mod)
+ dir_mtime (path_cast<dir_path> (*p), tm);
+ else
+ dir_atime (path_cast<dir_path> (*p), tm);
+ }
+ else
+ {
+ if (*mod)
+ file_mtime (*p, tm);
+ else
+ file_atime (*p, tm);
+ }
+ }
+ else
+ {
+ if (*dir)
+ tm = *mod
+ ? dir_mtime (path_cast<dir_path> (*p))
+ : dir_atime (path_cast<dir_path> (*p));
+ else
+ tm = *mod ? file_mtime (*p) : file_atime (*p);
+
+ cout << chrono::duration_cast<chrono::milliseconds> (
+ tm.time_since_epoch ()).count () << endl;
+ }
+}
diff --git a/tests/entry-time/testscript b/tests/entry-time/testscript
new file mode 100644
index 0000000..933ae3a
--- /dev/null
+++ b/tests/entry-time/testscript
@@ -0,0 +1,63 @@
+# file : tests/entry-time/testscript
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+# Filesystem entry times resolution may vary across OSes and underlying APIs,
+# so the timestamp set for the enty may differ a bit from the one we get for
+# it afterwards. Thus we will compare with the milliseconds resolution which
+# seems to be reasonable to expect being supported on the platforms we are
+# testing on.
+#
+t = 1521456316789 # Some time point since epoch expressed in milliseconds.
+
+: modification
+:
+{
+ test.options += -m
+
+ : dir
+ :
+ {
+ test.options += -d;
+
+ mkdir d;
+ $* -s $t d;
+ $* -p d >"$t"
+ }
+
+ : file
+ :
+ {
+ test.options += -f;
+
+ touch f;
+ $* -s $t f;
+ $* -p f >"$t"
+ }
+}
+
+: access
+:
+{
+ test.options += -a
+
+ : dir
+ :
+ {
+ test.options += -d;
+
+ mkdir d;
+ $* -s $t d;
+ $* -p d >"$t"
+ }
+
+ : file
+ :
+ {
+ test.options += -f;
+
+ touch f;
+ $* -s $t f;
+ $* -p f >"$t"
+ }
+}