#include <istream>
#include <__locale>
#include <cstdio>
+#include <cstdlib>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#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());
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>
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>*
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.
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();
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>
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();
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>
#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
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;
}
}
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)) {
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;
}
}
+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,
_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)) {
__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;
}
__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;
}
#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()
--- /dev/null
+//===----------------------------------------------------------------------===//
+//
+// 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()
#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())
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;
}
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;
}