summaryrefslogtreecommitdiffstats
path: root/libcxx
diff options
context:
space:
mode:
authorEric Fiselier <eric@efcs.ca>2018-07-22 02:00:53 +0000
committerEric Fiselier <eric@efcs.ca>2018-07-22 02:00:53 +0000
commit7c0ed44db018d123115f629fccbc307cdc078c98 (patch)
treef7f025c3ef91ae6ed32b7f7d1a0780f41a060806 /libcxx
parentf2603c07678b60a1745f1746e96c562c804f76fa (diff)
downloadbcm5719-llvm-7c0ed44db018d123115f629fccbc307cdc078c98.tar.gz
bcm5719-llvm-7c0ed44db018d123115f629fccbc307cdc078c98.zip
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. llvm-svn: 337649
Diffstat (limited to 'libcxx')
-rw-r--r--libcxx/include/fstream216
-rw-r--r--libcxx/src/experimental/filesystem/operations.cpp358
-rw-r--r--libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp285
-rw-r--r--libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp99
-rw-r--r--libcxx/test/support/filesystem_test_helper.hpp28
-rw-r--r--libcxx/test/support/rapid-cxx-test.hpp4
6 files changed, 666 insertions, 324 deletions
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 2ef4cf31820..8b9aefaaca3 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -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_;
-
- bool __read_mode();
- void __write_mode();
+ 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();
};
template <class _CharT, class _Traits>
@@ -473,6 +481,46 @@ basic_filebuf<_CharT, _Traits>::is_open() const
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(const char* __s, ios_base::openmode __mode)
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(const string& __s, ios_base::openmode __mo
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(const string& __s, ios_base::openmode __mo
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>
diff --git a/libcxx/src/experimental/filesystem/operations.cpp b/libcxx/src/experimental/filesystem/operations.cpp
index 28851028aba..be876ad7787 100644
--- a/libcxx/src/experimental/filesystem/operations.cpp
+++ b/libcxx/src/experimental/filesystem/operations.cpp
@@ -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 ::stat& st) noexcept {
}
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, std::error_code* ec) {
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 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;
+}
-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);
-
- 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& to, copy_options options,
}
}
+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;
+ };
- 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);
+ 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));
+
+ 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::error_code *ec)
_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() noexcept {
__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() noexcept {
__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;
}
diff --git a/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp b/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp
index 33b3ba9b586..3c2543ab942 100644
--- a/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp
+++ b/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file.pass.cpp
@@ -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_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
- };
-
- 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(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");
+
+ { // 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(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);
+ 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(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;
+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);
-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_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);
+
+ SleepFor(Sec(2));
+ const path newer = env.create_file("newer_file", 2);
-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));
- }
+ 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()
diff --git a/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp b/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp
new file mode 100644
index 00000000000..46b5f2a6702
--- /dev/null
+++ b/libcxx/test/std/experimental/filesystem/fs.op.funcs/fs.op.copy_file/copy_file_large.pass.cpp
@@ -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()
diff --git a/libcxx/test/support/filesystem_test_helper.hpp b/libcxx/test/support/filesystem_test_helper.hpp
index e3f46a16af3..de06868c014 100644
--- a/libcxx/test/support/filesystem_test_helper.hpp
+++ b/libcxx/test/support/filesystem_test_helper.hpp
@@ -105,6 +105,20 @@ static const fs::path RecDirFollowSymlinksIterationList[] = {
#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;
}
diff --git a/libcxx/test/support/rapid-cxx-test.hpp b/libcxx/test/support/rapid-cxx-test.hpp
index eb24ac25976..bf027407a16 100644
--- a/libcxx/test/support/rapid-cxx-test.hpp
+++ b/libcxx/test/support/rapid-cxx-test.hpp
@@ -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;
}
OpenPOWER on IntegriCloud