From fd5effb17e5f800fcaf54fc1223b77fa764b7f72 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Wed, 28 Sep 2016 19:02:25 +0100 Subject: [PATCH] Check for overflow in filesystem::last_write_time * include/experimental/bits/fs_fwd.h (file_time_type): Simplify definition. * src/filesystem/ops.cc (file_time): Take error_code parameter and check for overflow. (do_copy_file, last_write_time): Pass error_code in file_time calls. * testsuite/experimental/filesystem/operations/last_write_time.cc: New. * testsuite/util/testsuite_fs.h (scoped_file): Define RAII helper. From-SVN: r240587 --- libstdc++-v3/ChangeLog | 9 ++ .../include/experimental/bits/fs_fwd.h | 2 +- libstdc++-v3/src/filesystem/ops.cc | 30 +++-- .../filesystem/operations/last_write_time.cc | 111 ++++++++++++++++++ libstdc++-v3/testsuite/util/testsuite_fs.h | 21 +++- 5 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 8c11aad83e1..3ce95a1c3ea 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,5 +1,14 @@ 2016-09-28 Jonathan Wakely + * include/experimental/bits/fs_fwd.h (file_time_type): Simplify + definition. + * src/filesystem/ops.cc (file_time): Take error_code parameter and + check for overflow. + (do_copy_file, last_write_time): Pass error_code in file_time calls. + * testsuite/experimental/filesystem/operations/last_write_time.cc: + New. + * testsuite/util/testsuite_fs.h (scoped_file): Define RAII helper. + PR libstdc++/77686 * include/std/functional (_Any_data): Add may_alias attribute. diff --git a/libstdc++-v3/include/experimental/bits/fs_fwd.h b/libstdc++-v3/include/experimental/bits/fs_fwd.h index 57aa4d3ee79..b9cc041c236 100644 --- a/libstdc++-v3/include/experimental/bits/fs_fwd.h +++ b/libstdc++-v3/include/experimental/bits/fs_fwd.h @@ -253,7 +253,7 @@ _GLIBCXX_END_NAMESPACE_CXX11 operator^=(directory_options& __x, directory_options __y) noexcept { return __x = __x ^ __y; } - typedef chrono::time_point file_time_type; + using file_time_type = std::chrono::system_clock::time_point; // operational functions diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc index 0ecb8b9f4d8..659cfbb6f81 100644 --- a/libstdc++-v3/src/filesystem/ops.cc +++ b/libstdc++-v3/src/filesystem/ops.cc @@ -288,16 +288,24 @@ namespace } inline fs::file_time_type - file_time(const stat_type& st) noexcept + file_time(const stat_type& st, std::error_code& ec) noexcept { using namespace std::chrono; - return fs::file_time_type{ #ifdef _GLIBCXX_USE_ST_MTIM - seconds{st.st_mtim.tv_sec} + nanoseconds{st.st_mtim.tv_nsec} + time_t s = st.st_mtim.tv_sec; + nanoseconds ns{st.st_mtim.tv_nsec}; #else - seconds{st.st_mtime} + time_t s = st.st_mtime; + nanoseconds ns{}; #endif - }; + + if (s >= (nanoseconds::max().count() / 1e9)) + { + ec = std::make_error_code(std::errc::value_too_large); // EOVERFLOW + return fs::file_time_type::min(); + } + ec.clear(); + return fs::file_time_type{seconds{s} + ns}; } // Returns true if the file descriptor was successfully closed, @@ -373,11 +381,11 @@ namespace } else if (is_set(option, opts::update_existing)) { - if (file_time(*from_st) <= file_time(*to_st)) - { - ec.clear(); - return false; - } + const auto from_mtime = file_time(*from_st, ec); + if (ec) + return false; + if ((from_mtime <= file_time(*to_st, ec)) || ec) + return false; } else if (!is_set(option, opts::overwrite_existing)) { @@ -1036,7 +1044,7 @@ fs::last_write_time(const path& p) fs::file_time_type fs::last_write_time(const path& p, error_code& ec) noexcept { - return do_stat(p, ec, [](const auto& st) { return file_time(st); }, + return do_stat(p, ec, [&ec](const auto& st) { return file_time(st, ec); }, file_time_type::min()); } diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc new file mode 100644 index 00000000000..b1aea2012e9 --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc @@ -0,0 +1,111 @@ +// Copyright (C) 2016 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-lstdc++fs" } +// { dg-do run { target c++11 } } +// { dg-require-filesystem-ts "" } + +// 15.25 Permissions [fs.op.last_write_time] + +#include +#include +#include + +#ifdef _GLIBCXX_HAVE_FCNTL_H +# include +#endif +#if _GLIBCXX_HAVE_UTIME_H +# include +#endif + +void +test01() +{ + bool test __attribute__((unused)) = true; + using time_type = std::experimental::filesystem::file_time_type; + + auto p = __gnu_test::nonexistent_path(); + std::error_code ec; + time_type mtime = last_write_time(p, ec); + VERIFY( ec ); + VERIFY( ec == std::make_error_code(std::errc::no_such_file_or_directory) ); +#if __cpp_exceptions + bool caught = false; + try { + mtime = last_write_time(p); + } catch (std::system_error const& e) { + caught = true; + ec = e.code(); + } + VERIFY( caught ); + VERIFY( ec ); + VERIFY( ec == std::make_error_code(std::errc::no_such_file_or_directory) ); +#endif + + __gnu_test::scoped_file file(p); + VERIFY( exists(p) ); + mtime = last_write_time(p, ec); + VERIFY( !ec ); + VERIFY( mtime <= time_type::clock::now() ); + VERIFY( mtime == last_write_time(p) ); + + auto end_of_time = time_type::duration::max(); + auto last_second + = std::chrono::duration_cast(end_of_time).count(); + if (last_second > std::numeric_limits::max()) + return; // can't test overflow + +#if _GLIBCXX_USE_UTIMENSAT + struct ::timespec ts[2]; + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_NOW; + ts[1].tv_sec = std::numeric_limits::max() - 1; + ts[1].tv_nsec = 0; + VERIFY( !::utimensat(AT_FDCWD, p.c_str(), ts, 0) ); +#elif _GLIBCXX_HAVE_UTIME_H + ::utimbuf times; + times.modtime = std::numeric_limits::max() - 1; + times.actime = std::numeric_limits::max() - 1; + VERIFY( !::utime(p.c_str(), ×) ); +#else + return; +#endif + + mtime = last_write_time(p, ec); + VERIFY( ec ); + VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); + VERIFY( mtime == time_type::min() ); + +#if __cpp_exceptions + caught = false; + try { + mtime = last_write_time(p); + } catch (std::system_error const& e) { + caught = true; + ec = e.code(); + } + VERIFY( caught ); + VERIFY( ec ); + VERIFY( ec == std::make_error_code(std::errc::value_too_large) ); +#endif +} + +int +main() +{ + test01(); +} diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h b/libstdc++-v3/testsuite/util/testsuite_fs.h index f1e0bfcc252..5b36670ed52 100644 --- a/libstdc++-v3/testsuite/util/testsuite_fs.h +++ b/libstdc++-v3/testsuite/util/testsuite_fs.h @@ -23,7 +23,7 @@ #define _TESTSUITE_FS_H 1 #include -#include +#include #include #include #include @@ -40,7 +40,6 @@ namespace __gnu_test compare_paths(const std::experimental::filesystem::path& p1, const std::experimental::filesystem::path& p2) { - // std::cout << "Comparing " << p1 << " and " << p2 << std::endl; PATH_CHK( p1, p2, string ); PATH_CHK( p1, p2, empty ); PATH_CHK( p1, p2, has_root_path ); @@ -95,5 +94,23 @@ namespace __gnu_test return p; } + // RAII helper to remove a file on scope exit. + struct scoped_file + { + using path_type = std::experimental::filesystem::path; + + enum adopt_file_t { adopt_file }; + + explicit + scoped_file(const path_type& p = nonexistent_path()) : path(p) + { std::ofstream{p.native()}; } + + scoped_file(path_type p, adopt_file_t) : path(p) { } + + ~scoped_file() { remove(path); } + + path_type path; + }; + } // namespace __gnu_test #endif -- 2.30.2