From a51a546c1704cd572c35c11e539568c04d99e7d1 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Thu, 27 Feb 2020 16:38:00 +0000 Subject: [PATCH] libstdc++: Fix FS-dependent filesystem tests These tests were failing on XFS because it doesn't support setting file timestamps past 2038, so the expected overflow when reading back a huge timestamp into a file_time_type didn't happen. Additionally, the std::filesystem::file_time_type::clock has an epoch that is out of range of 32-bit time_t so testing times around that epoch may also fail. This fixes the tests to give up gracefully if the filesystem doesn't support times that can't be represented in 32-bit time_t. * testsuite/27_io/filesystem/operations/last_write_time.cc: Fixes for filesystems that silently truncate timestamps. * testsuite/experimental/filesystem/operations/last_write_time.cc: Likewise. --- libstdc++-v3/ChangeLog | 5 ++ .../filesystem/operations/last_write_time.cc | 77 ++++++++++++++----- .../filesystem/operations/last_write_time.cc | 58 ++++++++++---- 3 files changed, 104 insertions(+), 36 deletions(-) diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 24ce2a140db..9fe2fd2caa8 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,5 +1,10 @@ 2020-02-28 Jonathan Wakely + * testsuite/27_io/filesystem/operations/last_write_time.cc: Fixes for + filesystems that silently truncate timestamps. + * testsuite/experimental/filesystem/operations/last_write_time.cc: + Likewise. + * testsuite/21_strings/basic_string/cons/char/1.cc: Disable -Wstringop-overflow warnings. diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc b/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc index a6be926143c..2bba02f6899 100644 --- a/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc +++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/last_write_time.cc @@ -32,9 +32,12 @@ #if _GLIBCXX_HAVE_UTIME_H # include #endif +#include using time_type = std::filesystem::file_time_type; +namespace chrono = std::chrono; + void test01() { @@ -67,10 +70,15 @@ test01() auto end_of_time = time_type::duration::max(); auto last_second - = std::chrono::duration_cast(end_of_time).count(); + = chrono::duration_cast(end_of_time).count(); if (last_second > std::numeric_limits::max()) - return; // can't test overflow + { + puts("Range of time_t is smaller than range of chrono::file_clock, " + "can't test for overflow on this target."); + return; + } + // Set mtime to a date past the maximum possible file_time_type: #if _GLIBCXX_USE_UTIMENSAT struct ::timespec ts[2]; ts[0].tv_sec = 0; @@ -84,25 +92,34 @@ test01() times.actime = std::numeric_limits::max() - 1; VERIFY( !::utime(p.string().c_str(), ×) ); #else + puts("No utimensat or utime, giving up."); return; #endif + // Try to read back the impossibly-large mtime: mtime = last_write_time(p, ec); - VERIFY( ec ); - VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); - VERIFY( mtime == time_type::min() ); + // Some filesystems (e.g. XFS) silently truncate distant times to + // the time_t epochalypse, Jan 19 2038, so we won't get an error when + // reading it back: + if (ec) + { + VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); + VERIFY( mtime == time_type::min() ); + } + else + puts("No overflow error, filesystem may not support 64-bit time_t."); #if __cpp_exceptions - caught = false; + // Once more, with exceptions: try { - mtime = last_write_time(p); - } catch (std::system_error const& e) { - caught = true; - ec = e.code(); + auto mtime2 = last_write_time(p); + // If it didn't throw, expect to have read back the same value: + VERIFY( mtime2 == mtime ); + } catch (std::filesystem::filesystem_error const& e) { + // If it did throw, expect the error_code to be the same: + VERIFY( e.code() == ec ); + VERIFY( e.path1() == p ); } - VERIFY( caught ); - VERIFY( ec ); - VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); #endif } @@ -111,7 +128,7 @@ bool approx_equal(time_type file_time, time_type expected) auto delta = expected - file_time; if (delta < delta.zero()) delta = -delta; - return delta < std::chrono::seconds(1); + return delta < chrono::seconds(1); } void @@ -124,20 +141,20 @@ test02() std::error_code ec; time_type time; - time = last_write_time(f.path); ec = bad_ec; + time = last_write_time(f.path); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); ec = bad_ec; - time -= std::chrono::milliseconds(1000 * 60 * 10 + 15); + time -= chrono::milliseconds(1000 * 60 * 10 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); ec = bad_ec; - time += std::chrono::milliseconds(1000 * 60 * 20 + 15); + time += chrono::milliseconds(1000 * 60 * 20 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); @@ -146,6 +163,28 @@ test02() < std::numeric_limits::max()) return; // file clock's epoch is out of range for 32-bit time_t + using sys_time_32b + = chrono::time_point>; + auto duration_until_2038 = sys_time_32b::max() - sys_time_32b::clock::now(); + auto file_time_2038 = time_type::clock::now() + duration_until_2038; + + ec = bad_ec; + time = file_time_2038 - chrono::seconds(1); + // Assume all filesystems can store times that fit in 32-bit time_t + // (i.e. up to Jan 19 2038) + last_write_time(f.path, time, ec); + VERIFY( !ec ); + VERIFY( approx_equal(last_write_time(f.path), time) ); + + // Check whether the filesystem supports times larger than 32-bit time_t: + time += chrono::seconds(60); + last_write_time(f.path, time, ec); + if (ec || !approx_equal(last_write_time(f.path), time)) + { + puts("Filesystem seems to truncate times past Jan 19 2038, giving up."); + return; // Tests below will fail on this filesystem + } + ec = bad_ec; // The file clock's epoch: time = time_type(); @@ -155,14 +194,14 @@ test02() ec = bad_ec; // A time after the epoch - time += std::chrono::milliseconds(1000 * 60 * 10 + 15); + time += chrono::milliseconds(1000 * 60 * 10 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); ec = bad_ec; // A time before than the epoch - time -= std::chrono::milliseconds(1000 * 60 * 20 + 15); + time -= chrono::milliseconds(1000 * 60 * 20 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc index 4e3ea6754f9..13313a9a640 100644 --- a/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc @@ -22,6 +22,7 @@ // 15.25 Permissions [fs.op.last_write_time] #include +#include #include #include @@ -31,9 +32,12 @@ #if _GLIBCXX_HAVE_UTIME_H # include #endif +#include using time_type = std::experimental::filesystem::file_time_type; +namespace chrono = std::chrono; + void test01() { @@ -66,10 +70,15 @@ test01() auto end_of_time = time_type::duration::max(); auto last_second - = std::chrono::duration_cast(end_of_time).count(); + = chrono::duration_cast(end_of_time).count(); if (last_second > std::numeric_limits::max()) - return; // can't test overflow + { + puts("Range of time_t is smaller than range of chrono::file_clock, " + "can't test for overflow on this target."); + return; + } + // Set mtime to a date past the maximum possible file_time_type: #if _GLIBCXX_USE_UTIMENSAT struct ::timespec ts[2]; ts[0].tv_sec = 0; @@ -83,25 +92,34 @@ test01() times.actime = std::numeric_limits::max() - 1; VERIFY( !::utime(p.string().c_str(), ×) ); #else + puts("No utimensat or utime, giving up."); return; #endif + // Try to read back the impossibly-large mtime: mtime = last_write_time(p, ec); - VERIFY( ec ); - VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); - VERIFY( mtime == time_type::min() ); + // Some filesystems (e.g. XFS) silently truncate distant times to + // the time_t epochalypse, Jan 19 2038, so we won't get an error when + // reading it back: + if (ec) + { + VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); + VERIFY( mtime == time_type::min() ); + } + else + puts("No overflow error, filesystem may not support 64-bit time_t."); #if __cpp_exceptions - caught = false; + // Once more, with exceptions: try { - mtime = last_write_time(p); - } catch (std::system_error const& e) { - caught = true; - ec = e.code(); + auto mtime2 = last_write_time(p); + // If it didn't throw, expect to have read back the same value: + VERIFY( mtime2 == mtime ); + } catch (std::experimental::filesystem::filesystem_error const& e) { + // If it did throw, expect the error_code to be the same: + VERIFY( e.code() == ec ); + VERIFY( e.path1() == p ); } - VERIFY( caught ); - VERIFY( ec ); - VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); #endif } @@ -110,7 +128,7 @@ bool approx_equal(time_type file_time, time_type expected) auto delta = expected - file_time; if (delta < delta.zero()) delta = -delta; - return delta < std::chrono::seconds(1); + return delta < chrono::seconds(1); } void @@ -118,31 +136,37 @@ test02() { // write times + const std::error_code bad_ec = make_error_code(std::errc::invalid_argument); __gnu_test::scoped_file f; std::error_code ec; time_type time; + ec = bad_ec; time = last_write_time(f.path); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); - time -= std::chrono::milliseconds(1000 * 60 * 10 + 15); + ec = bad_ec; + time -= chrono::milliseconds(1000 * 60 * 10 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); - time += std::chrono::milliseconds(1000 * 60 * 20 + 15); + ec = bad_ec; + time += chrono::milliseconds(1000 * 60 * 20 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); + ec = bad_ec; time = time_type(); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); - time -= std::chrono::milliseconds(1000 * 60 * 10 + 15); + ec = bad_ec; + time -= chrono::milliseconds(1000 * 60 * 10 + 15); last_write_time(f.path, time, ec); VERIFY( !ec ); VERIFY( approx_equal(last_write_time(f.path), time) ); -- 2.30.2