diff options
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; } |