Check for overflow in filesystem::last_write_time
authorJonathan Wakely <jwakely@redhat.com>
Wed, 28 Sep 2016 18:02:25 +0000 (19:02 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Wed, 28 Sep 2016 18:02:25 +0000 (19:02 +0100)
* 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
libstdc++-v3/include/experimental/bits/fs_fwd.h
libstdc++-v3/src/filesystem/ops.cc
libstdc++-v3/testsuite/experimental/filesystem/operations/last_write_time.cc [new file with mode: 0644]
libstdc++-v3/testsuite/util/testsuite_fs.h

index 8c11aad83e1c539bc769b244232518c2f38314dd..3ce95a1c3eaa5c4a20aa0cea3f1c3d406b0aa302 100644 (file)
@@ -1,5 +1,14 @@
 2016-09-28  Jonathan Wakely  <jwakely@redhat.com>
 
+       * 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.
 
index 57aa4d3ee79fc9459d822ef0345bbe9ad6330ca8..b9cc041c236b174af1d76253fb3bfa5bb6c684bf 100644 (file)
@@ -253,7 +253,7 @@ _GLIBCXX_END_NAMESPACE_CXX11
   operator^=(directory_options& __x, directory_options __y) noexcept
   { return __x = __x ^ __y; }
 
-  typedef chrono::time_point<chrono::system_clock> file_time_type;
+  using file_time_type = std::chrono::system_clock::time_point;
 
   // operational functions
 
index 0ecb8b9f4d871ccb44cb34882e56edf3e7abb1e7..659cfbb6f811f0d92a8356b7b481017776d5092d 100644 (file)
@@ -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 (file)
index 0000000..b1aea20
--- /dev/null
@@ -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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-lstdc++fs" }
+// { dg-do run { target c++11 } }
+// { dg-require-filesystem-ts "" }
+
+// 15.25 Permissions [fs.op.last_write_time]
+
+#include <experimental/filesystem>
+#include <testsuite_fs.h>
+#include <testsuite_hooks.h>
+
+#ifdef _GLIBCXX_HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#if _GLIBCXX_HAVE_UTIME_H
+# include <utime.h>
+#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<std::chrono::seconds>(end_of_time).count();
+  if (last_second > std::numeric_limits<std::time_t>::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<std::time_t>::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<std::time_t>::max() - 1;
+  times.actime = std::numeric_limits<std::time_t>::max() - 1;
+  VERIFY( !::utime(p.c_str(), &times) );
+#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();
+}
index f1e0bfcc252d7b9001bbcdd3527077b2417c24cc..5b36670ed5285d7e809bce33fb6aedd84ac49970 100644 (file)
@@ -23,7 +23,7 @@
 #define _TESTSUITE_FS_H 1
 
 #include <experimental/filesystem>
-#include <iostream>
+#include <fstream>
 #include <string>
 #include <cstdio>
 #include <stdlib.h>
@@ -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