diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index b0ce5040..95971747 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -47,6 +47,8 @@ set(SOURCES system/memory_windows.cpp system/time.cpp system/diskspace.cpp + system/filetime_nonwindows.cpp + system/filetime_windows.cpp value_type/ValueType.cpp ) diff --git a/src/cpp-utils/system/filetime.h b/src/cpp-utils/system/filetime.h new file mode 100644 index 00000000..b8dc1cb2 --- /dev/null +++ b/src/cpp-utils/system/filetime.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_SYSTEM_FILETIME_H + +#include + +namespace cpputils { + +int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime); +int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime); + +} + +#endif diff --git a/src/cpp-utils/system/filetime_nonwindows.cpp b/src/cpp-utils/system/filetime_nonwindows.cpp new file mode 100644 index 00000000..f5b8cb7b --- /dev/null +++ b/src/cpp-utils/system/filetime_nonwindows.cpp @@ -0,0 +1,36 @@ +#if !defined(_MSC_VER) + +#include "filetime.h" +#include +#include +#include +#include + +namespace cpputils { + +int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime) { + struct timeval casted_times[2]; + TIMESPEC_TO_TIMEVAL(&casted_times[0], &lastAccessTime); + TIMESPEC_TO_TIMEVAL(&casted_times[1], &lastModificationTime); + int retval = ::utimes(filepath, casted_times); + if (0 == retval) { + return 0; + } else { + return errno; + } +} + +int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime) { + struct ::stat attrib; + int retval = ::stat(filepath, &attrib); + if (retval != 0) { + return errno; + } + *lastAccessTime = attrib.st_atim; + *lastModificationTime = attrib.st_mtim; + return 0; +} + +} + +#endif diff --git a/src/cpp-utils/system/filetime_windows.cpp b/src/cpp-utils/system/filetime_windows.cpp new file mode 100644 index 00000000..b0718f74 --- /dev/null +++ b/src/cpp-utils/system/filetime_windows.cpp @@ -0,0 +1,113 @@ +#if defined(_MSC_VER) + +#include "filetime.h" +#include +#include + +namespace cpputils { + +namespace { +FILETIME to_filetime(timespec value) { + ULARGE_INTEGER ull; + ull.QuadPart = (value.tv_sec * 10000000ULL) + 116444736000000000ULL + (value.tv_nsec / 100); + FILETIME result; + result.dwLowDateTime = ull.LowPart; + result.dwHighDateTime = ull.HighPart; + return result; +} + +timespec to_timespec(FILETIME value) { + // function taken from https://github.com/wishstudio/flinux/blob/afbb7d7509d4f1e0fdd62bf02124e7c4ce20ca6d/src/datetime.c under GPLv3 + constexpr uint64_t NANOSECONDS_PER_TICK = 100ULL; + constexpr uint64_t NANOSECONDS_PER_SECOND = 1000000000ULL; + constexpr uint64_t TICKS_PER_SECOND = 10000000ULL; + constexpr uint64_t SEC_TO_UNIX_EPOCH = 11644473600ULL; + constexpr uint64_t TICKS_TO_UNIX_EPOCH = TICKS_PER_SECOND * SEC_TO_UNIX_EPOCH; + + uint64_t ticks = ((uint64_t)value.dwHighDateTime << 32ULL) + value.dwLowDateTime; + uint64_t nsec = 0; + if (ticks >= TICKS_TO_UNIX_EPOCH) { // otherwise out of range + ticks -= TICKS_TO_UNIX_EPOCH; + nsec = ticks * NANOSECONDS_PER_TICK; + } + + timespec result; + result.tv_sec = nsec / NANOSECONDS_PER_SECOND; + result.tv_nsec = nsec % NANOSECONDS_PER_SECOND; + return result; +} + +struct OpenFileRAII final { + HANDLE handle; + + OpenFileRAII(const char* filepath, DWORD access) + :handle(CreateFileA(filepath, access, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)) { + } + + BOOL close() { + if (INVALID_HANDLE_VALUE == handle) { + return 1; + } + + BOOL success = CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + return success; + } + + ~OpenFileRAII() { + close(); + } +}; + +} + +int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime) { + OpenFileRAII file(filepath, FILE_WRITE_ATTRIBUTES); + if (INVALID_HANDLE_VALUE == file.handle) { + return GetLastError(); + } + + FILETIME atime = to_filetime(lastAccessTime); + FILETIME mtime = to_filetime(lastModificationTime); + + BOOL success = SetFileTime(file.handle, nullptr, &atime, &mtime); + if (!success) { + return GetLastError(); + } + + success = file.close(); + if (!success) { + return GetLastError(); + } + + return 0; +} + +int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime) { + OpenFileRAII file(filepath, FILE_READ_ATTRIBUTES); + if (INVALID_HANDLE_VALUE == file.handle) { + return GetLastError(); + } + + FILETIME atime; + FILETIME mtime; + + BOOL success = GetFileTime(file.handle, nullptr, &atime, &mtime); + if (!success) { + return GetLastError(); + } + + success = file.close(); + if (!success) { + return GetLastError(); + } + + *lastAccessTime = to_timespec(atime); + *lastModificationTime = to_timespec(mtime); + + return 0; +} + +} + +#endif diff --git a/test/cpp-utils/CMakeLists.txt b/test/cpp-utils/CMakeLists.txt index 766ff6e4..8a667787 100644 --- a/test/cpp-utils/CMakeLists.txt +++ b/test/cpp-utils/CMakeLists.txt @@ -48,6 +48,7 @@ set(SOURCES assert/assert_debug_test.cpp system/GetTotalMemoryTest.cpp system/TimeTest.cpp + system/FiletimeTest.cpp system/MemoryTest.cpp system/HomedirTest.cpp value_type/ValueTypeTest.cpp diff --git a/test/cpp-utils/system/FiletimeTest.cpp b/test/cpp-utils/system/FiletimeTest.cpp new file mode 100644 index 00000000..31908330 --- /dev/null +++ b/test/cpp-utils/system/FiletimeTest.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +using cpputils::TempFile; +using cpputils::set_filetime; +using cpputils::get_filetime; + +TEST(FiletimeTest, SetAndGetTime_ReturnsCorrectTime) { + TempFile file; + struct timespec accessTime { 1535965242, 12345000 }; + struct timespec modificationTime { 1435965242, 98765000 }; + int retval = set_filetime(file.path().string().c_str(), accessTime, modificationTime); + EXPECT_EQ(0, retval); + + struct timespec readAccessTime; + struct timespec readModificationTime; + retval = get_filetime(file.path().string().c_str(), &readAccessTime, &readModificationTime); + EXPECT_EQ(0, retval); + + EXPECT_EQ(accessTime.tv_sec, readAccessTime.tv_sec); + EXPECT_EQ(accessTime.tv_nsec, readAccessTime.tv_nsec); + EXPECT_EQ(modificationTime.tv_sec, readModificationTime.tv_sec); + EXPECT_EQ(modificationTime.tv_nsec, readModificationTime.tv_nsec); +} diff --git a/test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp b/test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp index cfd2e020..4ae8368b 100644 --- a/test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp +++ b/test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp @@ -1,7 +1,5 @@ #include "FuseUtimensTest.h" - -#include -#include +#include void FuseUtimensTest::Utimens(const char *filename, timespec lastAccessTime, timespec lastModificationTime) { int error = UtimensReturnError(filename, lastAccessTime, lastModificationTime); @@ -13,15 +11,7 @@ int FuseUtimensTest::UtimensReturnError(const char *filename, timespec lastAcces auto realpath = fs->mountDir() / filename; - struct timeval casted_times[2]; - TIMESPEC_TO_TIMEVAL(&casted_times[0], &lastAccessTime); - TIMESPEC_TO_TIMEVAL(&casted_times[1], &lastModificationTime); - int retval = ::utimes(realpath.string().c_str(), casted_times); - if (0 == retval) { - return 0; - } else { - return errno; - } + return cpputils::set_filetime(realpath.string().c_str(), lastAccessTime, lastModificationTime); } struct timespec FuseUtimensTest::makeTimespec(time_t tv_sec, long tv_nsec) {