[libcxx] r337649 - Implement a better copy_file.

Eric Fiselier via cfe-commits cfe-commits at lists.llvm.org
Sat Jul 21 19:00:54 PDT 2018


Author: ericwf
Date: Sat Jul 21 19:00:53 2018
New Revision: 337649

URL: http://llvm.org/viewvc/llvm-project?rev=337649&view=rev
Log:
Implement a better copy_file.

This patch improves both the performance, and the safety of the
copy_file implementation.

The performance improvements are achieved by using sendfile on
Linux and copyfile on OS X when available.

The TOCTOU hardening is achieved by opening the source and
destination files and then using fstat to check their attributes to
see if we can copy them.

Unfortunately for the destination file, there is no way to open
it without accidentally creating it, so we first have to use
stat to determine if it exists, and if we should copy to it.
Then, once we're sure we should try to copy, we open the dest
file and ensure it names the same entity we previously stat'ed.

Added:
    libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp
Modified:
    libcxx/trunk/include/fstream
    libcxx/trunk/src/experimental/filesystem/operations.cpp
    libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp
    libcxx/trunk/test/support/filesystem_test_helper.hpp
    libcxx/trunk/test/support/rapid-cxx-test.hpp

Modified: libcxx/trunk/include/fstream
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/fstream?rev=337649&r1=337648&r2=337649&view=diff
==============================================================================
--- libcxx/trunk/include/fstream (original)
+++ libcxx/trunk/include/fstream Sat Jul 21 19:00:53 2018
@@ -170,6 +170,7 @@ typedef basic_fstream<wchar_t> wfstream;
 #include <istream>
 #include <__locale>
 #include <cstdio>
+#include <cstdlib>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #pragma GCC system_header
@@ -217,10 +218,17 @@ public:
 #endif
     _LIBCPP_INLINE_VISIBILITY
     basic_filebuf* open(const string& __s, ios_base::openmode __mode);
+
+    _LIBCPP_INLINE_VISIBILITY
+    basic_filebuf* __open(int __fd, ios_base::openmode __mode);
 #endif
     basic_filebuf* close();
 
-protected:
+    _LIBCPP_INLINE_VISIBILITY
+    inline static const char*
+    __make_mdstring(ios_base::openmode __mode) _NOEXCEPT;
+
+  protected:
     // 27.9.1.5 Overridden virtual functions:
     virtual int_type underflow();
     virtual int_type pbackfail(int_type __c = traits_type::eof());
@@ -234,25 +242,25 @@ protected:
     virtual void imbue(const locale& __loc);
 
 private:
-    char*       __extbuf_;
-    const char* __extbufnext_;
-    const char* __extbufend_;
-    char __extbuf_min_[8];
-    size_t __ebs_;
-    char_type* __intbuf_;
-    size_t __ibs_;
-    FILE* __file_;
-    const codecvt<char_type, char, state_type>* __cv_;
-    state_type __st_;
-    state_type __st_last_;
-    ios_base::openmode __om_;
-    ios_base::openmode __cm_;
-    bool __owns_eb_;
-    bool __owns_ib_;
-    bool __always_noconv_;
+  char* __extbuf_;
+  const char* __extbufnext_;
+  const char* __extbufend_;
+  char __extbuf_min_[8];
+  size_t __ebs_;
+  char_type* __intbuf_;
+  size_t __ibs_;
+  FILE* __file_;
+  const codecvt<char_type, char, state_type>* __cv_;
+  state_type __st_;
+  state_type __st_last_;
+  ios_base::openmode __om_;
+  ios_base::openmode __cm_;
+  bool __owns_eb_;
+  bool __owns_ib_;
+  bool __always_noconv_;
 
-    bool __read_mode();
-    void __write_mode();
+  bool __read_mode();
+  void __write_mode();
 };
 
 template <class _CharT, class _Traits>
@@ -473,6 +481,46 @@ basic_filebuf<_CharT, _Traits>::is_open(
     return __file_ != 0;
 }
 
+template <class _CharT, class _Traits>
+const char* basic_filebuf<_CharT, _Traits>::__make_mdstring(
+    ios_base::openmode __mode) _NOEXCEPT {
+  switch (__mode & ~ios_base::ate) {
+  case ios_base::out:
+  case ios_base::out | ios_base::trunc:
+    return "w";
+  case ios_base::out | ios_base::app:
+  case ios_base::app:
+    return "a";
+  case ios_base::in:
+    return "r";
+  case ios_base::in | ios_base::out:
+    return "r+";
+  case ios_base::in | ios_base::out | ios_base::trunc:
+    return "w+";
+  case ios_base::in | ios_base::out | ios_base::app:
+  case ios_base::in | ios_base::app:
+    return "a+";
+  case ios_base::out | ios_base::binary:
+  case ios_base::out | ios_base::trunc | ios_base::binary:
+    return "wb";
+  case ios_base::out | ios_base::app | ios_base::binary:
+  case ios_base::app | ios_base::binary:
+    return "ab";
+  case ios_base::in | ios_base::binary:
+    return "rb";
+  case ios_base::in | ios_base::out | ios_base::binary:
+    return "r+b";
+  case ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary:
+    return "w+b";
+  case ios_base::in | ios_base::out | ios_base::app | ios_base::binary:
+  case ios_base::in | ios_base::app | ios_base::binary:
+    return "a+b";
+  default:
+    return nullptr;
+  }
+  _LIBCPP_UNREACHABLE();
+}
+
 #ifndef _LIBCPP_HAS_NO_GLOBAL_FILESYSTEM_NAMESPACE
 template <class _CharT, class _Traits>
 basic_filebuf<_CharT, _Traits>*
@@ -481,79 +529,49 @@ basic_filebuf<_CharT, _Traits>::open(con
     basic_filebuf<_CharT, _Traits>* __rt = 0;
     if (__file_ == 0)
     {
+      if (const char* __mdstr = __make_mdstring(__mode)) {
         __rt = this;
-        const char* __mdstr;
-        switch (__mode & ~ios_base::ate)
-        {
-        case ios_base::out:
-        case ios_base::out | ios_base::trunc:
-            __mdstr = "w";
-            break;
-        case ios_base::out | ios_base::app:
-        case ios_base::app:
-            __mdstr = "a";
-            break;
-        case ios_base::in:
-            __mdstr = "r";
-            break;
-        case ios_base::in | ios_base::out:
-            __mdstr = "r+";
-            break;
-        case ios_base::in | ios_base::out | ios_base::trunc:
-            __mdstr = "w+";
-            break;
-        case ios_base::in | ios_base::out | ios_base::app:
-        case ios_base::in | ios_base::app:
-            __mdstr = "a+";
-            break;
-        case ios_base::out | ios_base::binary:
-        case ios_base::out | ios_base::trunc | ios_base::binary:
-            __mdstr = "wb";
-            break;
-        case ios_base::out | ios_base::app | ios_base::binary:
-        case ios_base::app | ios_base::binary:
-            __mdstr = "ab";
-            break;
-        case ios_base::in | ios_base::binary:
-            __mdstr = "rb";
-            break;
-        case ios_base::in | ios_base::out | ios_base::binary:
-            __mdstr = "r+b";
-            break;
-        case ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary:
-            __mdstr = "w+b";
-            break;
-        case ios_base::in | ios_base::out | ios_base::app | ios_base::binary:
-        case ios_base::in | ios_base::app | ios_base::binary:
-            __mdstr = "a+b";
-            break;
-        default:
-            __rt = 0;
-            break;
-        }
-        if (__rt)
-        {
-            __file_ = fopen(__s, __mdstr);
-            if (__file_)
-            {
-                __om_ = __mode;
-                if (__mode & ios_base::ate)
-                {
-                    if (fseek(__file_, 0, SEEK_END))
-                    {
-                        fclose(__file_);
-                        __file_ = 0;
-                        __rt = 0;
-                    }
-                }
+        __file_ = fopen(__s, __mdstr);
+        if (__file_) {
+          __om_ = __mode;
+          if (__mode & ios_base::ate) {
+            if (fseek(__file_, 0, SEEK_END)) {
+              fclose(__file_);
+              __file_ = 0;
+              __rt = 0;
             }
-            else
-                __rt = 0;
-        }
+          }
+        } else
+          __rt = 0;
+      }
     }
     return __rt;
 }
 
+template <class _CharT, class _Traits>
+_LIBCPP_INLINE_VISIBILITY basic_filebuf<_CharT, _Traits>*
+basic_filebuf<_CharT, _Traits>::__open(int __fd, ios_base::openmode __mode) {
+  basic_filebuf<_CharT, _Traits>* __rt = 0;
+  if (__file_ == 0) {
+    if (const char* __mdstr = __make_mdstring(__mode)) {
+      __rt = this;
+      __file_ = fdopen(__fd, __mdstr);
+      if (__file_) {
+        __om_ = __mode;
+        if (__mode & ios_base::ate) {
+          if (fseek(__file_, 0, SEEK_END)) {
+            fclose(__file_);
+            __file_ = 0;
+            __rt = 0;
+          }
+        }
+      } else
+        __rt = 0;
+    }
+  }
+  return __rt;
+}
+
 #ifdef _LIBCPP_HAS_OPEN_WITH_WCHAR
 // This is basically the same as the char* overload except that it uses _wfopen
 // and long mode strings.
@@ -1131,6 +1149,9 @@ public:
     void open(const wchar_t* __s, ios_base::openmode __mode = ios_base::in);
 #endif
     void open(const string& __s, ios_base::openmode __mode = ios_base::in);
+
+    _LIBCPP_INLINE_VISIBILITY
+    void __open(int __fd, ios_base::openmode __mode);
 #endif
     _LIBCPP_INLINE_VISIBILITY
     void close();
@@ -1265,6 +1286,15 @@ basic_ifstream<_CharT, _Traits>::open(co
     else
         this->setstate(ios_base::failbit);
 }
+
+template <class _CharT, class _Traits>
+void basic_ifstream<_CharT, _Traits>::__open(int __fd,
+                                             ios_base::openmode __mode) {
+  if (__sb_.__open(__fd, __mode | ios_base::in))
+    this->clear();
+  else
+    this->setstate(ios_base::failbit);
+}
 #endif
 
 template <class _CharT, class _Traits>
@@ -1319,6 +1349,9 @@ public:
     void open(const wchar_t* __s, ios_base::openmode __mode = ios_base::out);
 #endif
     void open(const string& __s, ios_base::openmode __mode = ios_base::out);
+
+    _LIBCPP_INLINE_VISIBILITY
+    void __open(int __fd, ios_base::openmode __mode);
 #endif
     _LIBCPP_INLINE_VISIBILITY
     void close();
@@ -1453,6 +1486,15 @@ basic_ofstream<_CharT, _Traits>::open(co
     else
         this->setstate(ios_base::failbit);
 }
+
+template <class _CharT, class _Traits>
+void basic_ofstream<_CharT, _Traits>::__open(int __fd,
+                                             ios_base::openmode __mode) {
+  if (__sb_.__open(__fd, __mode | ios_base::out))
+    this->clear();
+  else
+    this->setstate(ios_base::failbit);
+}
 #endif
 
 template <class _CharT, class _Traits>

Modified: libcxx/trunk/src/experimental/filesystem/operations.cpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/src/experimental/filesystem/operations.cpp?rev=337649&r1=337648&r2=337649&view=diff
==============================================================================
--- libcxx/trunk/src/experimental/filesystem/operations.cpp (original)
+++ libcxx/trunk/src/experimental/filesystem/operations.cpp Sat Jul 21 19:00:53 2018
@@ -23,7 +23,17 @@
 #include <sys/stat.h>
 #include <sys/statvfs.h>
 #include <fcntl.h>  /* values for fchmodat */
-#include <experimental/filesystem>
+
+#if defined(__linux__)
+# include <linux/version.h>
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33)
+#   include <sys/sendfile.h>
+#   define _LIBCPP_USE_SENDFILE
+# endif
+#elif defined(__APPLE__) || __has_include(<copyfile.h>)
+#include <copyfile.h>
+# define _LIBCPP_USE_COPYFILE
+#endif
 
 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
 
@@ -281,6 +291,63 @@ namespace detail { namespace  {
 using value_type = path::value_type;
 using string_type = path::string_type;
 
+struct FileDescriptor {
+  const path& name;
+  int fd = -1;
+  StatT m_stat;
+  file_status m_status;
+
+  template <class... Args>
+  static FileDescriptor create(const path* p, error_code& ec, Args... args) {
+    ec.clear();
+    int fd;
+    if ((fd = ::open(p->c_str(), args...)) == -1) {
+      ec = capture_errno();
+      return FileDescriptor{p};
+    }
+    return FileDescriptor(p, fd);
+  }
+
+  template <class... Args>
+  static FileDescriptor create_with_status(const path* p, error_code& ec,
+                                           Args... args) {
+    FileDescriptor fd = create(p, ec, args...);
+    if (!ec)
+      fd.refresh_status(ec);
+
+    return fd;
+  }
+
+  file_status get_status() const { return m_status; }
+  StatT const& get_stat() const { return m_stat; }
+
+  bool status_known() const { return _VSTD_FS::status_known(m_status); }
+
+  file_status refresh_status(std::error_code& ec);
+
+  void close() noexcept {
+    if (fd != -1)
+      ::close(fd);
+    fd = -1;
+  }
+
+  FileDescriptor(FileDescriptor&& other)
+      : name(other.name), fd(other.fd), m_stat(other.m_stat),
+        m_status(other.m_status) {
+    other.fd = -1;
+    other.m_status = file_status{};
+  }
+
+  ~FileDescriptor() { close(); }
+
+  FileDescriptor() = default;
+  FileDescriptor(FileDescriptor const&) = delete;
+  FileDescriptor& operator=(FileDescriptor const&) = delete;
+
+private:
+  explicit FileDescriptor(const path* p, int fd = -1) : name(*p), fd(fd) {}
+};
+
 perms posix_get_perms(const struct ::stat& st) noexcept {
   return static_cast<perms>(st.st_mode) & perms::mask;
 }
@@ -290,7 +357,8 @@ perms posix_get_perms(const struct ::sta
 }
 
 file_status create_file_status(std::error_code& m_ec, path const& p,
-                               struct ::stat& path_stat, std::error_code* ec) {
+                               const struct ::stat& path_stat,
+                               std::error_code* ec) {
   if (ec)
     *ec = m_ec;
   if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) {
@@ -350,37 +418,39 @@ file_status posix_lstat(path const& p, s
   return posix_lstat(p, path_stat, ec);
 }
 
-bool stat_equivalent(struct ::stat& st1, struct ::stat& st2) {
-  return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
+bool posix_ftruncate(const FileDescriptor& fd, size_t to_size,
+                     std::error_code& ec) {
+  if (::ftruncate(fd.fd, to_size) == -1) {
+    ec = capture_errno();
+    return false;
+  }
+  ec.clear();
+  return true;
 }
 
-//                           DETAIL::MISC
-
-
-bool copy_file_impl(const path& from, const path& to, perms from_perms,
-                    std::error_code *ec)
-{
-    std::ifstream in(from.c_str(), std::ios::binary);
-    std::ofstream out(to.c_str(),  std::ios::binary);
+bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) {
+  if (::fchmod(fd.fd, st.st_mode) == -1) {
+    ec = capture_errno();
+    return false;
+  }
+  ec.clear();
+  return true;
+}
 
-    if (in.good() && out.good()) {
-        using InIt = std::istreambuf_iterator<char>;
-        using OutIt = std::ostreambuf_iterator<char>;
-        InIt bin(in);
-        InIt ein;
-        OutIt bout(out);
-        std::copy(bin, ein, bout);
-    }
-    if (out.fail() || in.fail()) {
-        set_or_throw(make_error_code(errc::operation_not_permitted),
-                     ec, "copy_file", from, to);
-        return false;
-    }
-    __permissions(to, from_perms, perm_options::replace, ec);
-    // TODO what if permissions fails?
-    return true;
+bool stat_equivalent(const StatT& st1, const StatT& st2) {
+  return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
 }
 
+file_status FileDescriptor::refresh_status(std::error_code& ec) {
+  // FD must be open and good.
+  m_status = file_status{};
+  m_stat = StatT{};
+  std::error_code m_ec;
+  if (::fstat(fd, &m_stat) == -1)
+    m_ec = capture_errno();
+  m_status = create_file_status(m_ec, name, m_stat, &ec);
+  return m_status;
+}
 }} // end namespace detail
 
 using detail::set_or_throw;
@@ -503,64 +573,195 @@ void __copy(const path& from, const path
     }
 }
 
+namespace detail {
+namespace {
+
+
+#ifdef _LIBCPP_USE_SENDFILE
+bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd,
+                             error_code& ec) {
+
+  size_t count = read_fd.get_stat().st_size;
+  do {
+    ssize_t res;
+    if ((res = ::sendfile(write_fd.fd, read_fd.fd, nullptr, count)) == -1) {
+      ec = capture_errno();
+      return false;
+    }
+    count -= res;
+  } while (count > 0);
+
+  ec.clear();
+
+  return true;
+}
+#elif defined(_LIBCPP_USE_COPYFILE)
+bool copy_file_impl_copyfile(FileDescriptor& read_fd, FileDescriptor& write_fd,
+                             error_code& ec) {
+  struct CopyFileState {
+    copyfile_state_t state;
+    CopyFileState() { state = copyfile_state_alloc(); }
+    ~CopyFileState() { copyfile_state_free(state); }
+
+  private:
+    CopyFileState(CopyFileState const&) = delete;
+    CopyFileState& operator=(CopyFileState const&) = delete;
+  };
+
+  CopyFileState cfs;
+  if (fcopyfile(read_fd.fd, write_fd.fd, cfs.state, COPYFILE_DATA) < 0) {
+    ec = capture_errno();
+    return false;
+  }
+
+  ec.clear();
+  return true;
+}
+#endif
+
+// Note: This function isn't guarded by ifdef's even though it may be unused
+// in order to assure it still compiles.
+__attribute__((unused)) bool copy_file_impl_default(FileDescriptor& read_fd,
+                                                    FileDescriptor& write_fd,
+                                                    error_code& ec) {
+  std::ifstream in;
+  in.__open(read_fd.fd, std::ios::binary);
+  if (!in.is_open()) {
+    // This assumes that __open didn't reset the error code.
+    ec = capture_errno();
+    return false;
+  }
+  std::ofstream out;
+  out.__open(write_fd.fd, std::ios::binary);
+  if (!out.is_open()) {
+    ec = capture_errno();
+    return false;
+  }
+
+  if (in.good() && out.good()) {
+    using InIt = std::istreambuf_iterator<char>;
+    using OutIt = std::ostreambuf_iterator<char>;
+    InIt bin(in);
+    InIt ein;
+    OutIt bout(out);
+    std::copy(bin, ein, bout);
+  }
+  if (out.fail() || in.fail()) {
+    ec = make_error_code(errc::io_error);
+    return false;
+  }
+
+  ec.clear();
+  return true;
+}
+
+bool copy_file_impl(FileDescriptor& from, FileDescriptor& to, error_code& ec) {
+#if defined(_LIBCPP_USE_SENDFILE)
+  return copy_file_impl_sendfile(from, to, ec);
+#elif defined(_LIBCPP_USE_COPYFILE)
+  return copy_file_impl_copyfile(from, to, ec);
+#else
+  return copy_file_impl_default(from, to, ec);
+#endif
+}
+
+} // namespace
+} // namespace detail
 
 bool __copy_file(const path& from, const path& to, copy_options options,
                  std::error_code *ec)
 {
-    using StatT = struct ::stat;
-    if (ec)
-      ec->clear();
+  using detail::FileDescriptor;
+  using detail::StatT;
 
-    std::error_code m_ec;
-    StatT from_stat;
-    auto from_st = detail::posix_stat(from, from_stat, &m_ec);
-    if (not is_regular_file(from_st)) {
-      if (not m_ec)
-        m_ec = make_error_code(errc::not_supported);
-      set_or_throw(m_ec, ec, "copy_file", from, to);
-      return false;
-    }
+  if (ec)
+    ec->clear();
 
-    StatT to_stat;
-    auto to_st = detail::posix_stat(to, to_stat, &m_ec);
-    if (!status_known(to_st)) {
-        set_or_throw(m_ec, ec, "copy_file", from, to);
-        return false;
-    }
+  auto Error = [&](const error_code& error_ec) {
+    set_or_throw(error_ec, ec, "copy_file", from, to);
+    return false;
+  };
+
+  std::error_code m_ec;
+  FileDescriptor from_fd =
+      FileDescriptor::create_with_status(&from, m_ec, O_RDONLY | O_NONBLOCK);
+  if (m_ec)
+    return Error(m_ec);
+
+  auto from_st = from_fd.get_status();
+  StatT const& from_stat = from_fd.get_stat();
+  if (!is_regular_file(from_st)) {
+    if (not m_ec)
+      m_ec = make_error_code(errc::not_supported);
+    return Error(m_ec);
+  }
+
+  const bool skip_existing = bool(copy_options::skip_existing & options);
+  const bool update_existing = bool(copy_options::update_existing & options);
+  const bool overwrite_existing =
+      bool(copy_options::overwrite_existing & options);
+
+  StatT to_stat_path;
+  file_status to_st = detail::posix_stat(to, to_stat_path, &m_ec);
+  if (!status_known(to_st))
+    return Error(m_ec);
+
+  const bool to_exists = exists(to_st);
+  if (to_exists && !is_regular_file(to_st))
+    return Error(make_error_code(errc::not_supported));
 
-    const bool to_exists = exists(to_st);
-    if (to_exists && !is_regular_file(to_st)) {
-        set_or_throw(make_error_code(errc::not_supported), ec, "copy_file", from, to);
+  if (to_exists && detail::stat_equivalent(from_stat, to_stat_path))
+    return Error(make_error_code(errc::file_exists));
+
+  if (to_exists && skip_existing)
+    return false;
+
+  auto ShouldCopy = [&]() {
+    if (to_exists && update_existing) {
+      auto from_time = detail::extract_mtime(from_stat);
+      auto to_time = detail::extract_mtime(to_stat_path);
+      if (from_time.tv_sec < to_time.tv_sec)
         return false;
-    }
-    if (to_exists && detail::stat_equivalent(from_stat, to_stat)) {
-      set_or_throw(make_error_code(errc::file_exists), ec, "copy_file", from,
-                   to);
-      return false;
-    }
-    if (to_exists && bool(copy_options::skip_existing & options)) {
+      if (from_time.tv_sec == to_time.tv_sec &&
+          from_time.tv_nsec <= to_time.tv_nsec)
         return false;
+      return true;
     }
-    else if (to_exists && bool(copy_options::update_existing & options)) {
-        auto from_time = __last_write_time(from, ec);
-        if (ec && *ec) { return false; }
-        auto to_time = __last_write_time(to, ec);
-        if (ec && *ec) { return false; }
-        if (from_time <= to_time) {
-            return false;
-        }
-        return detail::copy_file_impl(from, to, from_st.permissions(), ec);
-    }
-    else if (!to_exists || bool(copy_options::overwrite_existing & options)) {
-        return detail::copy_file_impl(from, to, from_st.permissions(), ec);
-    }
-    else {
-      set_or_throw(make_error_code(errc::file_exists), ec, "copy_file", from,
-                   to);
-      return false;
-    }
+    if (!to_exists || overwrite_existing)
+      return true;
+    return Error(make_error_code(errc::file_exists));
+  };
+  if (!ShouldCopy())
+    return false;
+
+  // Don't truncate right away. We may not be opening the file we originally
+  // looked at; we'll check this later.
+  int to_open_flags = O_WRONLY | O_CREAT;
+  FileDescriptor to_fd = FileDescriptor::create_with_status(
+      &to, m_ec, to_open_flags, from_stat.st_mode);
+  if (m_ec)
+    return Error(m_ec);
+
+  if (to_exists) {
+    // Check that the file we initially stat'ed is equivalent to the one
+    // we opened.
+    if (!detail::stat_equivalent(to_stat_path, to_fd.get_stat()))
+      return Error(make_error_code(errc::bad_file_descriptor));
+
+    // Set the permissions and truncate the file we opened.
+    if (!detail::posix_fchmod(to_fd, from_stat, m_ec))
+      return Error(m_ec);
+    if (!detail::posix_ftruncate(to_fd, 0, m_ec))
+      return Error(m_ec);
+  }
+
+  if (!copy_file_impl(from_fd, to_fd, m_ec)) {
+    // FIXME: Remove the dest file if we failed, and it didn't exist previously.
+    return Error(m_ec);
+  }
+
+  return true;
 
-    _LIBCPP_UNREACHABLE();
 }
 
 void __copy_symlink(const path& existing_symlink, const path& new_symlink,
@@ -759,9 +960,9 @@ bool __fs_is_empty(const path& p, std::e
     _LIBCPP_UNREACHABLE();
 }
 
-static file_time_type __extract_last_write_time(path const& p,
+static file_time_type __extract_last_write_time(const path& p,
                                                 const struct ::stat& st,
-                                                error_code *ec) {
+                                                error_code* ec) {
   using detail::FSTime;
   auto ts = detail::extract_mtime(st);
   if (!FSTime::is_representable(ts)) {
@@ -1426,7 +1627,7 @@ error_code directory_entry::__do_refresh
       __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved;
       return error_code{};
     }
-    // Otherwise, we either resolved the link, potentially as not existing.
+    // Otherwise, we resolved the link, potentially as not existing.
     // That's OK.
     __data_.__cache_type_ = directory_entry::_RefreshSymlink;
   }
@@ -1479,7 +1680,6 @@ error_code directory_entry::__do_refresh
       __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved;
       return error_code{};
     }
-    // Otherwise, we resolved the link as not existing. That's OK.
     __data_.__cache_type_ = directory_entry::_RefreshSymlink;
   }
 

Modified: libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp?rev=337649&r1=337648&r2=337649&view=diff
==============================================================================
--- libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp (original)
+++ libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp Sat Jul 21 19:00:53 2018
@@ -26,167 +26,164 @@
 #include "rapid-cxx-test.hpp"
 #include "filesystem_test_helper.hpp"
 
+#include <iostream>
+
 using namespace fs;
 
 using CO = fs::copy_options;
 
 TEST_SUITE(filesystem_copy_file_test_suite)
 
-TEST_CASE(test_signatures)
-{
-    const path p; ((void)p);
-    const copy_options opts{}; ((void)opts);
-    std::error_code ec; ((void)ec);
-    ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p)), bool);
-    ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, opts)), bool);
-    ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, ec)), bool);
-    ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, opts, ec)), bool);
-    ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p));
-    ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, opts));
-    ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, ec));
-    ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, opts, ec));
-}
-
-TEST_CASE(test_error_reporting)
-{
-    auto checkThrow = [](path const& f, path const& t, const std::error_code& ec)
-    {
-#ifndef TEST_HAS_NO_EXCEPTIONS
-        try {
-            fs::copy_file(f, t);
-            return false;
-        } catch (filesystem_error const& err) {
-            return err.path1() == f
-                && err.path2() == t
-                && err.code() == ec;
-        }
-#else
-        ((void)f); ((void)t); ((void)ec);
-        return true;
-#endif
-    };
+TEST_CASE(test_signatures) {
+  const path p;
+  ((void)p);
+  const copy_options opts{};
+  ((void)opts);
+  std::error_code ec;
+  ((void)ec);
+  ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p)), bool);
+  ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, opts)), bool);
+  ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, ec)), bool);
+  ASSERT_SAME_TYPE(decltype(fs::copy_file(p, p, opts, ec)), bool);
+  ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p));
+  ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, opts));
+  ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, ec));
+  ASSERT_NOT_NOEXCEPT(fs::copy_file(p, p, opts, ec));
+}
+
+TEST_CASE(test_error_reporting) {
+
+  scoped_test_env env;
+  const path file = env.create_file("file1", 42);
+  const path file2 = env.create_file("file2", 55);
+  const path non_regular_file = env.create_fifo("non_reg");
+  const path dne = env.make_env_path("dne");
 
-    scoped_test_env env;
-    const path file = env.create_file("file1", 42);
-    const path file2 = env.create_file("file2", 55);
-    const path non_regular_file = env.create_fifo("non_reg");
-    const path dne = env.make_env_path("dne");
-    { // exists(to) && equivalent(to, from)
-        std::error_code ec;
-        TEST_CHECK(fs::copy_file(file, file, copy_options::overwrite_existing,
-                                 ec) == false);
-        TEST_REQUIRE(ec);
-        TEST_CHECK(ec == std::make_error_code(std::errc::file_exists));
-        TEST_CHECK(checkThrow(file, file, ec));
-    }
-    { // exists(to) && !(skip_existing | overwrite_existing | update_existing)
-        std::error_code ec;
-        TEST_CHECK(fs::copy_file(file, file2, ec) == false);
-        TEST_REQUIRE(ec);
-        TEST_CHECK(ec == std::make_error_code(std::errc::file_exists));
-        TEST_CHECK(checkThrow(file, file2, ec));
-    }
-}
-
-TEST_CASE(copy_file)
-{
-    scoped_test_env env;
-    const path file = env.create_file("file1", 42);
-
-    { // !exists(to)
-        const path dest = env.make_env_path("dest1");
-        std::error_code ec;
-        TEST_REQUIRE(fs::copy_file(file, dest, ec) == true);
-        TEST_CHECK(!ec);
-        TEST_CHECK(file_size(dest) == 42);
-    }
-    { // exists(to) && overwrite_existing
-        const path dest = env.create_file("dest2", 55);
-        std::error_code ec;
-        TEST_REQUIRE(fs::copy_file(file, dest,
-                                   copy_options::overwrite_existing, ec) == true);
-        TEST_CHECK(!ec);
-        TEST_CHECK(file_size(dest) == 42);
-    }
-    { // exists(to) && update_existing
-        using Sec = std::chrono::seconds;
-        const path older = env.create_file("older_file", 1);
-
-        SleepFor(Sec(2));
-        const path from = env.create_file("update_from", 55);
-
-        SleepFor(Sec(2));
-        const path newer = env.create_file("newer_file", 2);
-
-        std::error_code ec;
-        TEST_REQUIRE(fs::copy_file(from, older, copy_options::update_existing, ec) == true);
-        TEST_CHECK(!ec);
-        TEST_CHECK(file_size(older) == 55);
-
-        TEST_REQUIRE(fs::copy_file(from, newer, copy_options::update_existing, ec) == false);
-        TEST_CHECK(!ec);
-        TEST_CHECK(file_size(newer) == 2);
-    }
-    { // skip_existing
-        const path file2 = env.create_file("file2", 55);
-        std::error_code ec;
-        TEST_REQUIRE(fs::copy_file(file, file2, copy_options::skip_existing, ec) == false);
-        TEST_CHECK(!ec);
-        TEST_CHECK(file_size(file2) == 55);
-    }
-}
-
-TEST_CASE(test_attributes_get_copied)
-{
-    scoped_test_env env;
-    const path file = env.create_file("file1", 42);
-    const path dest = env.make_env_path("file2");
-    auto st = status(file);
-    perms new_perms = perms::owner_read;
-    permissions(file, new_perms);
+  { // exists(to) && equivalent(to, from)
+    std::error_code ec;
+    TEST_CHECK(fs::copy_file(file, file, copy_options::overwrite_existing,
+                             ec) == false);
+    TEST_CHECK(ErrorIs(ec, std::errc::file_exists));
+    ExceptionChecker Checker(file, file, std::errc::file_exists);
+    TEST_CHECK_THROW_RESULT(filesystem_error, Checker, copy_file(file, file, copy_options::overwrite_existing));
+
+  }
+  { // exists(to) && !(skip_existing | overwrite_existing | update_existing)
     std::error_code ec;
+    TEST_CHECK(fs::copy_file(file, file2, ec) == false);
+    TEST_CHECK(ErrorIs(ec, std::errc::file_exists));
+    ExceptionChecker Checker(file, file, std::errc::file_exists);
+    TEST_CHECK_THROW_RESULT(filesystem_error, Checker, copy_file(file, file, copy_options::overwrite_existing));
+
+  }
+}
+
+TEST_CASE(non_regular_file_test) {
+  scoped_test_env env;
+  const path fifo = env.create_fifo("fifo");
+  const path dest = env.make_env_path("dest");
+  const path file = env.create_file("file", 42);
+
+  {
+    std::error_code ec = GetTestEC();
+    TEST_REQUIRE(fs::copy_file(fifo, dest, ec) == false);
+    TEST_CHECK(ErrorIs(ec, std::errc::not_supported));
+    TEST_CHECK(!exists(dest));
+  }
+  {
+    std::error_code ec = GetTestEC();
+    TEST_REQUIRE(fs::copy_file(file, fifo, copy_options::overwrite_existing,
+                               ec) == false);
+    TEST_CHECK(ErrorIs(ec, std::errc::not_supported));
+    TEST_CHECK(is_fifo(fifo));
+  }
+
+}
+
+TEST_CASE(test_attributes_get_copied) {
+  scoped_test_env env;
+  const path file = env.create_file("file1", 42);
+  const path dest = env.make_env_path("file2");
+  auto st = status(file);
+  perms new_perms = perms::owner_read;
+  permissions(file, new_perms);
+  std::error_code ec = GetTestEC();
+  TEST_REQUIRE(fs::copy_file(file, dest, ec) == true);
+  TEST_CHECK(!ec);
+  auto new_st = status(dest);
+  TEST_CHECK(new_st.permissions() == new_perms);
+}
+
+TEST_CASE(copy_dir_test) {
+  scoped_test_env env;
+  const path file = env.create_file("file1", 42);
+  const path dest = env.create_dir("dir1");
+  std::error_code ec = GetTestEC();
+  TEST_CHECK(fs::copy_file(file, dest, ec) == false);
+  TEST_CHECK(ec);
+  TEST_CHECK(ec != GetTestEC());
+  ec = GetTestEC();
+  TEST_CHECK(fs::copy_file(dest, file, ec) == false);
+  TEST_CHECK(ec);
+  TEST_CHECK(ec != GetTestEC());
+}
+
+TEST_CASE(copy_file) {
+  scoped_test_env env;
+  const path file = env.create_file("file1", 42);
+
+  { // !exists(to)
+    const path dest = env.make_env_path("dest1");
+    std::error_code ec = GetTestEC();
+
     TEST_REQUIRE(fs::copy_file(file, dest, ec) == true);
     TEST_CHECK(!ec);
-    auto new_st = status(dest);
-    TEST_CHECK(new_st.permissions() == new_perms);
-}
+    TEST_CHECK(file_size(dest) == 42);
+  }
+  { // exists(to) && overwrite_existing
+    const path dest = env.create_file("dest2", 55);
+    permissions(dest, perms::all);
+    permissions(file,
+                perms::group_write | perms::owner_write | perms::others_write,
+                perm_options::remove);
+
+    std::error_code ec = GetTestEC();
+    TEST_REQUIRE(fs::copy_file(file, dest, copy_options::overwrite_existing,
+                               ec) == true);
+    TEST_CHECK(!ec);
+    TEST_CHECK(file_size(dest) == 42);
+    TEST_CHECK(status(dest).permissions() == status(file).permissions());
+  }
+  { // exists(to) && update_existing
+    using Sec = std::chrono::seconds;
+    const path older = env.create_file("older_file", 1);
+
+    SleepFor(Sec(2));
+    const path from = env.create_file("update_from", 55);
 
-TEST_CASE(copy_dir_test)
-{
-    scoped_test_env env;
-    const path file = env.create_file("file1", 42);
-    const path dest = env.create_dir("dir1");
-    std::error_code ec = GetTestEC();
-    TEST_CHECK(fs::copy_file(file, dest, ec) == false);
-    TEST_CHECK(ec);
-    TEST_CHECK(ec != GetTestEC());
-    ec = GetTestEC();
-    TEST_CHECK(fs::copy_file(dest, file, ec) == false);
-    TEST_CHECK(ec);
-    TEST_CHECK(ec != GetTestEC());
-}
-
-TEST_CASE(non_regular_file_test)
-{
-    scoped_test_env env;
-    const path fifo = env.create_fifo("fifo");
-    const path dest = env.make_env_path("dest");
-    const path file = env.create_file("file", 42);
-    {
-        std::error_code ec = GetTestEC();
-        TEST_REQUIRE(fs::copy_file(fifo, dest, ec) == false);
-        TEST_CHECK(ec);
-        TEST_CHECK(ec != GetTestEC());
-        TEST_CHECK(!exists(dest));
-    }
-    {
-        std::error_code ec = GetTestEC();
-        TEST_REQUIRE(fs::copy_file(file, fifo, copy_options::overwrite_existing, ec) == false);
-        TEST_CHECK(ec);
-        TEST_CHECK(ec != GetTestEC());
-        TEST_CHECK(ec == std::make_error_code(std::errc::not_supported));
-        TEST_CHECK(is_fifo(fifo));
-    }
+    SleepFor(Sec(2));
+    const path newer = env.create_file("newer_file", 2);
+
+    std::error_code ec = GetTestEC();
+    TEST_REQUIRE(
+        fs::copy_file(from, older, copy_options::update_existing, ec) == true);
+    TEST_CHECK(!ec);
+    TEST_CHECK(file_size(older) == 55);
+
+    TEST_REQUIRE(
+        fs::copy_file(from, newer, copy_options::update_existing, ec) == false);
+    TEST_CHECK(!ec);
+    TEST_CHECK(file_size(newer) == 2);
+  }
+  { // skip_existing
+    const path file2 = env.create_file("file2", 55);
+    std::error_code ec = GetTestEC();
+    TEST_REQUIRE(fs::copy_file(file, file2, copy_options::skip_existing, ec) ==
+                 false);
+    TEST_CHECK(!ec);
+    TEST_CHECK(file_size(file2) == 55);
+  }
 }
 
+
 TEST_SUITE_END()

Added: libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp?rev=337649&view=auto
==============================================================================
--- libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp (added)
+++ libcxx/trunk/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp Sat Jul 21 19:00:53 2018
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++98, c++03
+// REQUIRES: long_tests
+
+// <experimental/filesystem>
+
+// bool copy_file(const path& from, const path& to);
+// bool copy_file(const path& from, const path& to, error_code& ec) noexcept;
+// bool copy_file(const path& from, const path& to, copy_options options);
+// bool copy_file(const path& from, const path& to, copy_options options,
+//           error_code& ec) noexcept;
+
+#include "filesystem_include.hpp"
+#include <type_traits>
+#include <chrono>
+#include <cassert>
+
+#include "test_macros.h"
+#include "rapid-cxx-test.hpp"
+#include "filesystem_test_helper.hpp"
+
+using namespace fs;
+
+TEST_SUITE(filesystem_copy_file_test_suite)
+
+static std::string random_hex_chars(uintmax_t size) {
+  std::string data;
+  data.reserve(size);
+  for (uintmax_t I = 0; I < size; ++I)
+    data.push_back(random_utils::random_hex_char());
+  return data;
+}
+
+// This test is intended to test 'sendfile's 2gb limit for a single call, and
+// to ensure that libc++ correctly copies files larger than that limit.
+// However it requires allocating ~5GB of filesystem space. This might not
+// be acceptable on all systems.
+TEST_CASE(large_file) {
+  using namespace fs;
+  constexpr uintmax_t sendfile_size_limit = 2147479552ull;
+  constexpr uintmax_t additional_size = 1024;
+  constexpr uintmax_t test_file_size = sendfile_size_limit + additional_size;
+  static_assert(test_file_size > sendfile_size_limit, "");
+
+  scoped_test_env env;
+
+  // Check that we have more than sufficient room to create the files needed
+  // to perform the test.
+  if (space(env.test_root).available < 3 * test_file_size) {
+    TEST_UNSUPPORTED();
+  }
+
+  // Use python to create a file right at the size limit.
+  const path file = env.create_file("source", sendfile_size_limit);
+  // Create some random data that looks different than the data before the
+  // size limit.
+  const std::string additional_data = random_hex_chars(additional_size);
+  // Append this known data to the end of the source file.
+  {
+    std::ofstream outf(file.native(), std::ios_base::app);
+    TEST_REQUIRE(outf.good());
+    outf << additional_data;
+    TEST_REQUIRE(outf);
+  }
+  TEST_REQUIRE(file_size(file) == test_file_size);
+  const path dest = env.make_env_path("dest");
+
+  std::error_code ec = GetTestEC();
+  TEST_CHECK(copy_file(file, dest, ec));
+  TEST_CHECK(!ec);
+
+  TEST_REQUIRE(is_regular_file(dest));
+  TEST_CHECK(file_size(dest) == test_file_size);
+
+  // Read the data from the end of the destination file, and ensure it matches
+  // the data at the end of the source file.
+  std::string out_data;
+  out_data.reserve(additional_size);
+  {
+    std::ifstream dest_file(dest.native());
+    TEST_REQUIRE(dest_file);
+    dest_file.seekg(sendfile_size_limit);
+    TEST_REQUIRE(dest_file);
+    dest_file >> out_data;
+    TEST_CHECK(dest_file.eof());
+  }
+  TEST_CHECK(out_data.size() == additional_data.size());
+  TEST_CHECK(out_data == additional_data);
+}
+
+TEST_SUITE_END()

Modified: libcxx/trunk/test/support/filesystem_test_helper.hpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/support/filesystem_test_helper.hpp?rev=337649&r1=337648&r2=337649&view=diff
==============================================================================
--- libcxx/trunk/test/support/filesystem_test_helper.hpp (original)
+++ libcxx/trunk/test/support/filesystem_test_helper.hpp Sat Jul 21 19:00:53 2018
@@ -105,6 +105,20 @@ static const fs::path RecDirFollowSymlin
 #error LIBCXX_FILESYSTEM_DYNAMIC_TEST_HELPER must be defined
 #endif
 
+namespace random_utils {
+inline char to_hex(int ch) {
+  return ch < 10 ? static_cast<char>('0' + ch)
+                 : static_cast<char>('a' + (ch - 10));
+}
+
+inline char random_hex_char() {
+  static std::mt19937 rd{std::random_device{}()};
+  static std::uniform_int_distribution<int> mrand{0, 15};
+  return to_hex(mrand(rd));
+}
+
+} // namespace random_utils
+
 struct scoped_test_env
 {
     scoped_test_env() : test_root(random_env_path())
@@ -179,21 +193,11 @@ struct scoped_test_env
     fs::path const test_root;
 
 private:
-    static char to_hex(int ch) {
-        return ch < 10 ? static_cast<char>('0' + ch)
-                       : static_cast<char>('a' + (ch - 10));
-    }
-
-    static char random_hex_char() {
-        static std::mt19937 rd { std::random_device{}() };
-        static std::uniform_int_distribution<int> mrand{0, 15};
-        return to_hex( mrand(rd) );
-    }
-
     static std::string unique_path_suffix() {
         std::string model = "test.%%%%%%";
         for (auto & ch :  model) {
-            if (ch == '%') ch = random_hex_char();
+          if (ch == '%')
+            ch = random_utils::random_hex_char();
         }
         return model;
     }

Modified: libcxx/trunk/test/support/rapid-cxx-test.hpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/support/rapid-cxx-test.hpp?rev=337649&r1=337648&r2=337649&view=diff
==============================================================================
--- libcxx/trunk/test/support/rapid-cxx-test.hpp (original)
+++ libcxx/trunk/test/support/rapid-cxx-test.hpp Sat Jul 21 19:00:53 2018
@@ -826,8 +826,8 @@ namespace rapid_cxx_test
                 get_reporter().test_case_end();
             }
             auto exit_code = get_reporter().failure_count() ? EXIT_FAILURE : EXIT_SUCCESS;
-            if (exit_code == EXIT_FAILURE)
-                get_reporter().print_summary(m_ts.name());
+            if (exit_code == EXIT_FAILURE || get_reporter().unsupported_count())
+              get_reporter().print_summary(m_ts.name());
             return exit_code;
         }
 




More information about the cfe-commits mailing list