diff options
Diffstat (limited to 'lib/libcxx/src/experimental/filesystem')
4 files changed, 696 insertions, 366 deletions
diff --git a/lib/libcxx/src/experimental/filesystem/directory_iterator.cpp b/lib/libcxx/src/experimental/filesystem/directory_iterator.cpp index fa217ba7a12..a552fdc4461 100644 --- a/lib/libcxx/src/experimental/filesystem/directory_iterator.cpp +++ b/lib/libcxx/src/experimental/filesystem/directory_iterator.cpp @@ -1,28 +1,32 @@ +//===------------------ directory_iterator.cpp ----------------------------===// +// +// 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. +// +//===----------------------------------------------------------------------===// + #include "experimental/filesystem" +#include "__config" +#if defined(_LIBCPP_WIN32API) +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#else #include <dirent.h> +#endif #include <errno.h> _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM namespace { namespace detail { +#if !defined(_LIBCPP_WIN32API) inline error_code capture_errno() { _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); return error_code{errno, std::generic_category()}; } - -template <class ...Args> -inline bool capture_error_or_throw(std::error_code* user_ec, - const char* msg, Args&&... args) -{ - std::error_code my_ec = capture_errno(); - if (user_ec) { - *user_ec = my_ec; - return true; - } - __libcpp_throw(filesystem_error(msg, std::forward<Args>(args)..., my_ec)); - return false; -} +#endif template <class ...Args> inline bool set_or_throw(std::error_code& my_ec, @@ -33,29 +37,91 @@ inline bool set_or_throw(std::error_code& my_ec, *user_ec = my_ec; return true; } - __libcpp_throw(filesystem_error(msg, std::forward<Args>(args)..., my_ec)); + __throw_filesystem_error(msg, std::forward<Args>(args)..., my_ec); return false; } -typedef path::string_type string_type; - - -inline string_type posix_readdir(DIR *dir_stream, error_code& ec) { +#if !defined(_LIBCPP_WIN32API) +inline path::string_type posix_readdir(DIR *dir_stream, error_code& ec) { struct dirent* dir_entry_ptr = nullptr; errno = 0; // zero errno in order to detect errors + ec.clear(); if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) { - ec = capture_errno(); + if (errno) + ec = capture_errno(); return {}; } else { - ec.clear(); return dir_entry_ptr->d_name; } } +#endif }} // namespace detail using detail::set_or_throw; +#if defined(_LIBCPP_WIN32API) +class __dir_stream { +public: + __dir_stream() = delete; + __dir_stream& operator=(const __dir_stream&) = delete; + + __dir_stream(__dir_stream&& __ds) noexcept + : __stream_(__ds.__stream_), __root_(std::move(__ds.__root_)), + __entry_(std::move(__ds.__entry_)) { + __ds.__stream_ = INVALID_HANDLE_VALUE; + } + + __dir_stream(const path& root, directory_options opts, error_code& ec) + : __stream_(INVALID_HANDLE_VALUE), __root_(root) { + __stream_ = ::FindFirstFile(root.c_str(), &__data_); + if (__stream_ == INVALID_HANDLE_VALUE) { + ec = error_code(::GetLastError(), std::generic_category()); + const bool ignore_permission_denied = + bool(opts & directory_options::skip_permission_denied); + if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED) + ec.clear(); + return; + } + } + + ~__dir_stream() noexcept { + if (__stream_ == INVALID_HANDLE_VALUE) + return; + close(); + } + + bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; } + + bool advance(error_code& ec) { + while (::FindNextFile(__stream_, &__data_)) { + if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, "..")) + continue; + __entry_.assign(__root_ / __data_.cFileName); + return true; + } + ec = error_code(::GetLastError(), std::generic_category()); + close(); + return false; + } + +private: + std::error_code close() noexcept { + std::error_code ec; + if (!::FindClose(__stream_)) + ec = error_code(::GetLastError(), std::generic_category()); + __stream_ = INVALID_HANDLE_VALUE; + return ec; + } + + HANDLE __stream_{INVALID_HANDLE_VALUE}; + WIN32_FIND_DATA __data_; + +public: + path __root_; + directory_entry __entry_; +}; +#else class __dir_stream { public: __dir_stream() = delete; @@ -117,6 +183,7 @@ public: path __root_; directory_entry __entry_; }; +#endif // directory_iterator @@ -149,7 +216,7 @@ directory_iterator& directory_iterator::__increment(error_code *ec) } -directory_entry const& directory_iterator::__deref() const { +directory_entry const& directory_iterator::__dereference() const { _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator"); return __imp_->__entry_; } @@ -195,7 +262,7 @@ int recursive_directory_iterator::depth() const { return __imp_->__stack_.size() - 1; } -const directory_entry& recursive_directory_iterator::__deref() const { +const directory_entry& recursive_directory_iterator::__dereference() const { return __imp_->__stack_.top().__entry_; } @@ -229,24 +296,43 @@ void recursive_directory_iterator::__advance(error_code* ec) { } bool recursive_directory_iterator::__try_recursion(error_code *ec) { - bool rec_sym = bool(options() & directory_options::follow_directory_symlink); + auto& curr_it = __imp_->__stack_.top(); - if (is_directory(curr_it.__entry_.status()) && - (!is_symlink(curr_it.__entry_.symlink_status()) || rec_sym)) - { - std::error_code m_ec; + bool skip_rec = false; + std::error_code m_ec; + if (!rec_sym) { + file_status st = curr_it.__entry_.symlink_status(m_ec); + if (m_ec && status_known(st)) + m_ec.clear(); + if (m_ec || is_symlink(st) || !is_directory(st)) + skip_rec = true; + } else { + file_status st = curr_it.__entry_.status(m_ec); + if (m_ec && status_known(st)) + m_ec.clear(); + if (m_ec || !is_directory(st)) + skip_rec = true; + } + + if (!skip_rec) { __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec); if (new_it.good()) { __imp_->__stack_.push(_VSTD::move(new_it)); return true; } - if (m_ec) { - __imp_.reset(); - set_or_throw(m_ec, ec, - "recursive_directory_iterator::operator++()"); + } + if (m_ec) { + const bool allow_eacess = bool(__imp_->__options_ + & directory_options::skip_permission_denied); + if (m_ec.value() == EACCES && allow_eacess) { + if (ec) ec->clear(); + } else { + __imp_.reset(); + set_or_throw(m_ec, ec, + "recursive_directory_iterator::operator++()"); } } return false; diff --git a/lib/libcxx/src/experimental/filesystem/filesystem_time_helper.h b/lib/libcxx/src/experimental/filesystem/filesystem_time_helper.h new file mode 100644 index 00000000000..a60fdef5f0d --- /dev/null +++ b/lib/libcxx/src/experimental/filesystem/filesystem_time_helper.h @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===//// +// +// 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. +// +//===----------------------------------------------------------------------===//// + +#ifndef FILESYSTEM_TIME_HELPER_H +#define FILESYSTEM_TIME_HELPER_H + +#include "experimental/__config" +#include "chrono" +#include "cstdlib" +#include "climits" + +#include <unistd.h> +#include <sys/stat.h> +#if !defined(UTIME_OMIT) +#include <sys/time.h> // for ::utimes as used in __last_write_time +#endif + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +namespace time_detail { namespace { + +using namespace chrono; + +template <class FileTimeT, + bool IsFloat = is_floating_point<typename FileTimeT::rep>::value> +struct fs_time_util_base { + static constexpr auto max_seconds = + duration_cast<seconds>(FileTimeT::duration::max()).count(); + + static constexpr auto max_nsec = + duration_cast<nanoseconds>(FileTimeT::duration::max() - + seconds(max_seconds)) + .count(); + + static constexpr auto min_seconds = + duration_cast<seconds>(FileTimeT::duration::min()).count(); + + static constexpr auto min_nsec_timespec = + duration_cast<nanoseconds>( + (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) + .count(); + + // Static assert that these values properly round trip. + static_assert((seconds(min_seconds) + + duration_cast<microseconds>(nanoseconds(min_nsec_timespec))) - + duration_cast<microseconds>(seconds(1)) == + FileTimeT::duration::min(), + ""); +}; + +template <class FileTimeT> +struct fs_time_util_base<FileTimeT, true> { + static const long long max_seconds; + static const long long max_nsec; + static const long long min_seconds; + static const long long min_nsec_timespec; +}; + +template <class FileTimeT> +const long long fs_time_util_base<FileTimeT, true>::max_seconds = + duration_cast<seconds>(FileTimeT::duration::max()).count(); + +template <class FileTimeT> +const long long fs_time_util_base<FileTimeT, true>::max_nsec = + duration_cast<nanoseconds>(FileTimeT::duration::max() - + seconds(max_seconds)) + .count(); + +template <class FileTimeT> +const long long fs_time_util_base<FileTimeT, true>::min_seconds = + duration_cast<seconds>(FileTimeT::duration::min()).count(); + +template <class FileTimeT> +const long long fs_time_util_base<FileTimeT, true>::min_nsec_timespec = + duration_cast<nanoseconds>((FileTimeT::duration::min() - + seconds(min_seconds)) + + seconds(1)) + .count(); + +template <class FileTimeT, class TimeT, class TimeSpecT> +struct fs_time_util : fs_time_util_base<FileTimeT> { + using Base = fs_time_util_base<FileTimeT>; + using Base::max_nsec; + using Base::max_seconds; + using Base::min_nsec_timespec; + using Base::min_seconds; + +public: + template <class CType, class ChronoType> + static bool checked_set(CType* out, ChronoType time) { + using Lim = numeric_limits<CType>; + if (time > Lim::max() || time < Lim::min()) + return false; + *out = static_cast<CType>(time); + return true; + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) { + if (tm.tv_sec >= 0) { + return (tm.tv_sec < max_seconds) || + (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec); + } else if (tm.tv_sec == (min_seconds - 1)) { + return tm.tv_nsec >= min_nsec_timespec; + } else { + return (tm.tv_sec >= min_seconds); + } + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) { + auto secs = duration_cast<seconds>(tm.time_since_epoch()); + auto nsecs = duration_cast<nanoseconds>(tm.time_since_epoch() - secs); + if (nsecs.count() < 0) { + secs = secs + seconds(1); + nsecs = nsecs + seconds(1); + } + using TLim = numeric_limits<TimeT>; + if (secs.count() >= 0) + return secs.count() <= TLim::max(); + return secs.count() >= TLim::min(); + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT + convert_timespec(TimeSpecT tm) { + auto adj_msec = duration_cast<microseconds>(nanoseconds(tm.tv_nsec)); + if (tm.tv_sec >= 0) { + auto Dur = seconds(tm.tv_sec) + microseconds(adj_msec); + return FileTimeT(Dur); + } else if (duration_cast<microseconds>(nanoseconds(tm.tv_nsec)).count() == + 0) { + return FileTimeT(seconds(tm.tv_sec)); + } else { // tm.tv_sec < 0 + auto adj_subsec = + duration_cast<microseconds>(seconds(1) - nanoseconds(tm.tv_nsec)); + auto Dur = seconds(tm.tv_sec + 1) - adj_subsec; + return FileTimeT(Dur); + } + } + + template <class SubSecDurT, class SubSecT> + static bool set_times_checked(TimeT* sec_out, SubSecT* subsec_out, + FileTimeT tp) { + using namespace chrono; + auto dur = tp.time_since_epoch(); + auto sec_dur = duration_cast<seconds>(dur); + auto subsec_dur = duration_cast<SubSecDurT>(dur - sec_dur); + // The tv_nsec and tv_usec fields must not be negative so adjust accordingly + if (subsec_dur.count() < 0) { + if (sec_dur.count() > min_seconds) { + sec_dur -= seconds(1); + subsec_dur += seconds(1); + } else { + subsec_dur = SubSecDurT::zero(); + } + } + return checked_set(sec_out, sec_dur.count()) && + checked_set(subsec_out, subsec_dur.count()); + } +}; + +} // end namespace +} // end namespace time_detail + +using time_detail::fs_time_util; + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +#endif // FILESYSTEM_TIME_HELPER_H diff --git a/lib/libcxx/src/experimental/filesystem/operations.cpp b/lib/libcxx/src/experimental/filesystem/operations.cpp index 369996fcbe6..662fa7b8626 100644 --- a/lib/libcxx/src/experimental/filesystem/operations.cpp +++ b/lib/libcxx/src/experimental/filesystem/operations.cpp @@ -15,6 +15,8 @@ #include "cstdlib" #include "climits" +#include "filesystem_time_helper.h" + #include <unistd.h> #include <sys/stat.h> #include <sys/statvfs.h> @@ -35,12 +37,9 @@ namespace detail { namespace { using value_type = path::value_type; using string_type = path::string_type; - - inline std::error_code capture_errno() { - _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); - std::error_code m_ec(errno, std::generic_category()); - return m_ec; + _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); + return std::error_code(errno, std::generic_category()); } void set_or_throw(std::error_code const& m_ec, std::error_code* ec, @@ -51,7 +50,7 @@ void set_or_throw(std::error_code const& m_ec, std::error_code* ec, } else { string msg_s("std::experimental::filesystem::"); msg_s += msg; - __libcpp_throw(filesystem_error(msg_s, p, p2, m_ec)); + __throw_filesystem_error(msg_s, p, p2, m_ec); } } @@ -183,20 +182,20 @@ void __copy(const path& from, const path& to, copy_options options, const bool sym_status2 = bool(options & copy_options::copy_symlinks); - std::error_code m_ec; + std::error_code m_ec1; struct ::stat f_st = {}; const file_status f = sym_status || sym_status2 - ? detail::posix_lstat(from, f_st, &m_ec) - : detail::posix_stat(from, f_st, &m_ec); - if (m_ec) - return set_or_throw(m_ec, ec, "copy", from, to); + ? detail::posix_lstat(from, f_st, &m_ec1) + : detail::posix_stat(from, f_st, &m_ec1); + if (m_ec1) + return set_or_throw(m_ec1, ec, "copy", from, to); struct ::stat t_st = {}; - const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec) - : detail::posix_stat(to, t_st, &m_ec); + const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec1) + : detail::posix_stat(to, t_st, &m_ec1); if (not status_known(t)) - return set_or_throw(m_ec, ec, "copy", from, to); + return set_or_throw(m_ec1, ec, "copy", from, to); if (!exists(f) || is_other(f) || is_other(t) || (is_directory(f) && is_regular_file(t)) @@ -236,12 +235,11 @@ void __copy(const path& from, const path& to, copy_options options, } return; } - else if (is_directory(f)) { - if (not bool(copy_options::recursive & options) && - bool(copy_options::__in_recursive_copy & options)) - { - return; - } + else if (is_directory(f) && bool(copy_options::create_symlinks & options)) { + return set_or_throw(make_error_code(errc::is_a_directory), ec, "copy"); + } + else if (is_directory(f) && (bool(copy_options::recursive & options) || + copy_options::none == options)) { if (!exists(t)) { // create directory to with attributes from 'from'. @@ -251,9 +249,9 @@ void __copy(const path& from, const path& to, copy_options options, directory_iterator it = ec ? directory_iterator(from, *ec) : directory_iterator(from); if (ec && *ec) { return; } - std::error_code m_ec; - for (; it != directory_iterator(); it.increment(m_ec)) { - if (m_ec) return set_or_throw(m_ec, ec, "copy", from, to); + std::error_code m_ec2; + for (; it != directory_iterator(); it.increment(m_ec2)) { + if (m_ec2) return set_or_throw(m_ec2, ec, "copy", from, to); __copy(it->path(), to / it->path().filename(), options | copy_options::__in_recursive_copy, ec); if (ec && *ec) { return; } @@ -283,6 +281,10 @@ bool __copy_file(const path& from, const path& to, copy_options options, } 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); + return false; + } if (to_exists && bool(copy_options::skip_existing & options)) { return false; } @@ -303,6 +305,8 @@ bool __copy_file(const path& from, const path& to, copy_options options, set_or_throw(make_error_code(errc::file_exists), ec, "copy", from, to); return false; } + + _LIBCPP_UNREACHABLE(); } void __copy_symlink(const path& existing_symlink, const path& new_symlink, @@ -424,17 +428,20 @@ void __current_path(const path& p, std::error_code *ec) { bool __equivalent(const path& p1, const path& p2, std::error_code *ec) { + auto make_unsupported_error = [&]() { + set_or_throw(make_error_code(errc::not_supported), ec, + "equivalent", p1, p2); + return false; + }; std::error_code ec1, ec2; struct ::stat st1 = {}; struct ::stat st2 = {}; auto s1 = detail::posix_stat(p1.native(), st1, &ec1); + if (!exists(s1)) + return make_unsupported_error(); auto s2 = detail::posix_stat(p2.native(), st2, &ec2); - - if ((!exists(s1) && !exists(s2)) || (is_other(s1) && is_other(s2))) { - set_or_throw(make_error_code(errc::not_supported), ec, - "equivalent", p1, p2); - return false; - } + if (!exists(s2)) + return make_unsupported_error(); if (ec) ec->clear(); return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); } @@ -476,55 +483,50 @@ bool __fs_is_empty(const path& p, std::error_code *ec) std::error_code m_ec; struct ::stat pst; auto st = detail::posix_stat(p, pst, &m_ec); - if (is_directory(st)) - return directory_iterator(p) == directory_iterator{}; + if (m_ec) { + set_or_throw(m_ec, ec, "is_empty", p); + return false; + } + else if (!is_directory(st) && !is_regular_file(st)) { + m_ec = make_error_code(errc::not_supported); + set_or_throw(m_ec, ec, "is_empty"); + return false; + } + else if (is_directory(st)) { + auto it = ec ? directory_iterator(p, *ec) : directory_iterator(p); + if (ec && *ec) + return false; + return it == directory_iterator{}; + } else if (is_regular_file(st)) return static_cast<std::uintmax_t>(pst.st_size) == 0; - // else - set_or_throw(m_ec, ec, "is_empty", p); - return false; + + _LIBCPP_UNREACHABLE(); } namespace detail { namespace { -template <class CType, class ChronoType> -bool checked_set(CType* out, ChronoType time) { - using Lim = numeric_limits<CType>; - if (time > Lim::max() || time < Lim::min()) - return false; - *out = static_cast<CType>(time); - return true; -} - -constexpr long long min_seconds = file_time_type::duration::min().count() - / file_time_type::period::den; - -template <class SubSecDurT, class SubSecT> -bool set_times_checked(time_t* sec_out, SubSecT* subsec_out, file_time_type tp) { - using namespace chrono; - auto dur = tp.time_since_epoch(); - auto sec_dur = duration_cast<seconds>(dur); - auto subsec_dur = duration_cast<SubSecDurT>(dur - sec_dur); - // The tv_nsec and tv_usec fields must not be negative so adjust accordingly - if (subsec_dur.count() < 0) { - if (sec_dur.count() > min_seconds) { +using TimeSpec = struct timespec; +using StatT = struct stat; - sec_dur -= seconds(1); - subsec_dur += seconds(1); - } else { - subsec_dur = SubSecDurT::zero(); - } - } - return checked_set(sec_out, sec_dur.count()) - && checked_set(subsec_out, subsec_dur.count()); -} +#if defined(__APPLE__) +TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } +__attribute__((unused)) // Suppress warning +TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } +#else +TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } +__attribute__((unused)) // Suppress warning +TimeSpec extract_atime(StatT const& st) { return st.st_atim; } +#endif }} // end namespace detail +using FSTime = fs_time_util<file_time_type, time_t, struct timespec>; file_time_type __last_write_time(const path& p, std::error_code *ec) { + using namespace ::std::chrono; std::error_code m_ec; struct ::stat st; detail::posix_stat(p, st, &m_ec); @@ -533,7 +535,13 @@ file_time_type __last_write_time(const path& p, std::error_code *ec) return file_time_type::min(); } if (ec) ec->clear(); - return file_time_type::clock::from_time_t(st.st_mtime); + auto ts = detail::extract_mtime(st); + if (!FSTime::is_representable(ts)) { + set_or_throw(error_code(EOVERFLOW, generic_category()), ec, + "last_write_time", p); + return file_time_type::min(); + } + return FSTime::convert_timespec(ts); } void __last_write_time(const path& p, file_time_type new_time, @@ -554,10 +562,11 @@ void __last_write_time(const path& p, file_time_type new_time, set_or_throw(m_ec, ec, "last_write_time", p); return; } + auto atime = detail::extract_atime(st); struct ::timeval tbuf[2]; - tbuf[0].tv_sec = st.st_atime; - tbuf[0].tv_usec = 0; - const bool overflowed = !detail::set_times_checked<microseconds>( + tbuf[0].tv_sec = atime.tv_sec; + tbuf[0].tv_usec = duration_cast<microseconds>(nanoseconds(atime.tv_nsec)).count(); + const bool overflowed = !FSTime::set_times_checked<microseconds>( &tbuf[1].tv_sec, &tbuf[1].tv_usec, new_time); if (overflowed) { @@ -573,7 +582,7 @@ void __last_write_time(const path& p, file_time_type new_time, tbuf[0].tv_sec = 0; tbuf[0].tv_nsec = UTIME_OMIT; - const bool overflowed = !detail::set_times_checked<nanoseconds>( + const bool overflowed = !FSTime::set_times_checked<nanoseconds>( &tbuf[1].tv_sec, &tbuf[1].tv_nsec, new_time); if (overflowed) { set_or_throw(make_error_code(errc::invalid_argument), @@ -701,7 +710,7 @@ void __rename(const path& from, const path& to, std::error_code *ec) { } void __resize_file(const path& p, std::uintmax_t size, std::error_code *ec) { - if (::truncate(p.c_str(), static_cast<long>(size)) == -1) + if (::truncate(p.c_str(), static_cast<::off_t>(size)) == -1) set_or_throw(ec, "resize_file", p); else if (ec) ec->clear(); @@ -720,7 +729,7 @@ space_info __space(const path& p, std::error_code *ec) { // Multiply with overflow checking. auto do_mult = [&](std::uintmax_t& out, std::uintmax_t other) { out = other * m_svfs.f_frsize; - if (out / other != m_svfs.f_frsize || other == 0) + if (other == 0 || out / other != m_svfs.f_frsize) out = static_cast<std::uintmax_t>(-1); }; do_mult(si.capacity, m_svfs.f_blocks); @@ -742,23 +751,28 @@ path __system_complete(const path& p, std::error_code *ec) { return absolute(p, current_path()); } -path __temp_directory_path(std::error_code *ec) { - const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; - const char* ret = nullptr; - for (auto & ep : env_paths) { - if ((ret = std::getenv(ep))) - break; - } - path p(ret ? ret : "/tmp"); - std::error_code m_ec; - if (is_directory(p, m_ec)) { - if (ec) ec->clear(); - return p; - } +path __temp_directory_path(std::error_code* ec) { + const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; + const char* ret = nullptr; + + for (auto& ep : env_paths) + if ((ret = std::getenv(ep))) + break; + if (ret == nullptr) + ret = "/tmp"; + + path p(ret); + std::error_code m_ec; + if (!exists(p, m_ec) || !is_directory(p, m_ec)) { if (!m_ec || m_ec == make_error_code(errc::no_such_file_or_directory)) - m_ec = make_error_code(errc::not_a_directory); + m_ec = make_error_code(errc::not_a_directory); set_or_throw(m_ec, ec, "temp_directory_path"); return {}; + } + + if (ec) + ec->clear(); + return p; } // An absolute path is composed according to the table in [fs.op.absolute]. diff --git a/lib/libcxx/src/experimental/filesystem/path.cpp b/lib/libcxx/src/experimental/filesystem/path.cpp index 38c449832f6..dd4026cfe13 100644 --- a/lib/libcxx/src/experimental/filesystem/path.cpp +++ b/lib/libcxx/src/experimental/filesystem/path.cpp @@ -7,241 +7,284 @@ // //===----------------------------------------------------------------------===// #include "experimental/filesystem" -#include "experimental/string_view" +#include "string_view" #include "utility" -_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM - -_LIBCPP_CONSTEXPR path::value_type path::preferred_separator; - namespace { namespace parser { +using namespace std; +using namespace std::experimental::filesystem; + +using string_view_t = path::__string_view; +using string_view_pair = pair<string_view_t, string_view_t>; +using PosPtr = path::value_type const*; + +struct PathParser { + enum ParserState : unsigned char { + // Zero is a special sentinel value used by default constructed iterators. + PS_BeforeBegin = 1, + PS_InRootName, + PS_InRootDir, + PS_InFilenames, + PS_InTrailingSep, + PS_AtEnd + }; -using string_type = string_view; -using value_type = path::value_type; - -using string_view_pair = pair<string_view, string_view>; - -// status reporting -constexpr size_t npos = static_cast<size_t>(-1); - -inline bool good(size_t pos) { return pos != npos; } - -// lexical elements -constexpr value_type preferred_separator = path::preferred_separator; -constexpr value_type const * preferred_separator_str = "/"; -constexpr value_type const * dot = "."; - -// forward // -bool is_separator(string_type const &, size_t); -bool is_root_name(const string_type&, size_t); -bool is_root_directory(string_type const &, size_t); -bool is_trailing_separator(string_type const &, size_t); - -size_t start_of(string_type const &, size_t); -size_t end_of(string_type const &, size_t); - -size_t root_name_start(const string_type& s); -size_t root_name_end(const string_type&); - -size_t root_directory_start(string_type const &); -size_t root_directory_end(string_type const &); + const string_view_t Path; + string_view_t RawEntry; + ParserState State; -string_view_pair separate_filename(string_type const &); -string_view extract_raw(string_type const &, size_t); -string_view extract_preferred(string_type const &, size_t); +private: + PathParser(string_view_t P, ParserState State) noexcept + : Path(P), State(State) {} -inline bool is_separator(const string_type& s, size_t pos) { - return (pos < s.size() && s[pos] == preferred_separator); -} +public: + PathParser(string_view_t P, string_view_t E, unsigned char S) + : Path(P), RawEntry(E), State(static_cast<ParserState>(S)) { + // S cannot be '0' or PS_BeforeBegin. + } -inline bool is_root_name(const string_type& s, size_t pos) { - return good(pos) && pos == 0 ? root_name_start(s) == pos : false; -} + static PathParser CreateBegin(string_view_t P) noexcept { + PathParser PP(P, PS_BeforeBegin); + PP.increment(); + return PP; + } -inline bool is_root_directory(const string_type& s, size_t pos) { - return good(pos) ? root_directory_start(s) == pos : false; -} + static PathParser CreateEnd(string_view_t P) noexcept { + PathParser PP(P, PS_AtEnd); + return PP; + } -inline bool is_trailing_separator(const string_type& s, size_t pos) { - return (pos < s.size() && is_separator(s, pos) && - end_of(s, pos) == s.size()-1 && - !is_root_directory(s, pos) && !is_root_name(s, pos)); -} + PosPtr peek() const noexcept { + auto TkEnd = getNextTokenStartPos(); + auto End = getAfterBack(); + return TkEnd == End ? nullptr : TkEnd; + } -size_t start_of(const string_type& s, size_t pos) { - if (pos >= s.size()) return npos; - bool in_sep = (s[pos] == preferred_separator); - while (pos - 1 < s.size() && - (s[pos-1] == preferred_separator) == in_sep) - { --pos; } - if (pos == 2 && !in_sep && s[0] == preferred_separator && - s[1] == preferred_separator) - { return 0; } - return pos; -} + void increment() noexcept { + const PosPtr End = getAfterBack(); + const PosPtr Start = getNextTokenStartPos(); + if (Start == End) + return makeState(PS_AtEnd); + + switch (State) { + case PS_BeforeBegin: { + PosPtr TkEnd = consumeSeparator(Start, End); + // If we consumed exactly two separators we have a root name. + if (TkEnd && TkEnd == Start + 2) { + // FIXME Do we need to consume a name or is '//' a root name on its own? + // what about '//.', '//..', '//...'? + auto NameEnd = consumeName(TkEnd, End); + if (NameEnd) + TkEnd = NameEnd; + return makeState(PS_InRootName, Start, TkEnd); + } + else if (TkEnd) + return makeState(PS_InRootDir, Start, TkEnd); + else + return makeState(PS_InFilenames, Start, consumeName(Start, End)); + } -size_t end_of(const string_type& s, size_t pos) { - if (pos >= s.size()) return npos; - // special case for root name - if (pos == 0 && is_root_name(s, pos)) return root_name_end(s); - bool in_sep = (s[pos] == preferred_separator); - while (pos + 1 < s.size() && (s[pos+1] == preferred_separator) == in_sep) - { ++pos; } - return pos; -} + case PS_InRootName: + return makeState(PS_InRootDir, Start, consumeSeparator(Start, End)); + case PS_InRootDir: + return makeState(PS_InFilenames, Start, consumeName(Start, End)); + + case PS_InFilenames: { + PosPtr SepEnd = consumeSeparator(Start, End); + if (SepEnd != End) { + PosPtr TkEnd = consumeName(SepEnd, End); + if (TkEnd) + return makeState(PS_InFilenames, SepEnd, TkEnd); + } + return makeState(PS_InTrailingSep, Start, SepEnd); + } -inline size_t root_name_start(const string_type& s) { - return good(root_name_end(s)) ? 0 : npos; -} + case PS_InTrailingSep: + return makeState(PS_AtEnd); -size_t root_name_end(const string_type& s) { - if (s.size() < 2 || s[0] != preferred_separator - || s[1] != preferred_separator) { - return npos; + case PS_AtEnd: + _LIBCPP_UNREACHABLE(); } - if (s.size() == 2) { - return 1; + } + + void decrement() noexcept { + const PosPtr REnd = getBeforeFront(); + const PosPtr RStart = getCurrentTokenStartPos() - 1; + + switch (State) { + case PS_AtEnd: { + // Try to consume a trailing separator or root directory first. + if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) { + if (SepEnd == REnd) + return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir, + Path.data(), RStart + 1); + // Check if we're seeing the root directory separator + auto PP = CreateBegin(Path); + bool InRootDir = PP.State == PS_InRootName && + &PP.RawEntry.back() == SepEnd; + return makeState(InRootDir ? PS_InRootDir : PS_InTrailingSep, + SepEnd + 1, RStart + 1); + } else { + PosPtr TkStart = consumeName(RStart, REnd); + if (TkStart == REnd + 2 && consumeSeparator(TkStart, REnd) == REnd) + return makeState(PS_InRootName, Path.data(), RStart + 1); + else + return makeState(PS_InFilenames, TkStart + 1, RStart + 1); + } } - size_t index = 2; // current position - if (s[index] == preferred_separator) { - return npos; + case PS_InTrailingSep: + return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1); + case PS_InFilenames: { + PosPtr SepEnd = consumeSeparator(RStart, REnd); + if (SepEnd == REnd) + return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir, + Path.data(), RStart + 1); + PosPtr TkEnd = consumeName(SepEnd, REnd); + if (TkEnd == REnd + 2 && consumeSeparator(TkEnd, REnd) == REnd) + return makeState(PS_InRootDir, SepEnd + 1, RStart + 1); + return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1); } - while (index + 1 < s.size() && s[index+1] != preferred_separator) { - ++index; + case PS_InRootDir: + return makeState(PS_InRootName, Path.data(), RStart + 1); + case PS_InRootName: + case PS_BeforeBegin: + _LIBCPP_UNREACHABLE(); } - return index; -} - -size_t root_directory_start(const string_type& s) { - size_t e = root_name_end(s); - if (!good(e)) - return is_separator(s, 0) ? 0 : npos; - return is_separator(s, e + 1) ? e + 1 : npos; -} - -size_t root_directory_end(const string_type& s) { - size_t st = root_directory_start(s); - if (!good(st)) return npos; - size_t index = st; - while (index + 1 < s.size() && s[index + 1] == preferred_separator) - { ++index; } - return index; -} - -string_view_pair separate_filename(string_type const & s) { - if (s == "." || s == ".." || s.empty()) return string_view_pair{s, ""}; - auto pos = s.find_last_of('.'); - if (pos == string_type::npos) return string_view_pair{s, string_view{}}; - return string_view_pair{s.substr(0, pos), s.substr(pos)}; -} - -inline string_view extract_raw(const string_type& s, size_t pos) { - size_t end_i = end_of(s, pos); - if (!good(end_i)) return string_view{}; - return string_view(s).substr(pos, end_i - pos + 1); -} - -string_view extract_preferred(const string_type& s, size_t pos) { - string_view raw = extract_raw(s, pos); - if (raw.empty()) - return raw; - if (is_trailing_separator(s, pos)) - return string_view{dot}; - if (is_separator(s, pos) && !is_root_name(s, pos)) - return string_view(preferred_separator_str); - return raw; -} - -}} // namespace parser - - -//////////////////////////////////////////////////////////////////////////////// -// path_view_iterator -//////////////////////////////////////////////////////////////////////////////// -namespace { - -struct path_view_iterator { - const string_view __s_; - size_t __pos_; + } - explicit path_view_iterator(string_view const& __s) : __s_(__s), __pos_(__s_.empty() ? parser::npos : 0) {} - explicit path_view_iterator(string_view const& __s, size_t __p) : __s_(__s), __pos_(__p) {} + /// \brief Return a view with the "preferred representation" of the current + /// element. For example trailing separators are represented as a '.' + string_view_t operator*() const noexcept { + switch (State) { + case PS_BeforeBegin: + case PS_AtEnd: + return ""; + case PS_InRootDir: + return "/"; + case PS_InTrailingSep: + return "."; + case PS_InRootName: + case PS_InFilenames: + return RawEntry; + } + _LIBCPP_UNREACHABLE(); + } - string_view operator*() const { - return parser::extract_preferred(__s_, __pos_); + explicit operator bool() const noexcept { + return State != PS_BeforeBegin && State != PS_AtEnd; } - path_view_iterator& operator++() { + PathParser& operator++() noexcept { increment(); return *this; } - path_view_iterator& operator--() { + PathParser& operator--() noexcept { decrement(); return *this; } - void increment() { - if (__pos_ == parser::npos) return; - while (! set_position(parser::end_of(__s_, __pos_)+1)) - ; - return; +private: + void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept { + State = NewState; + RawEntry = string_view_t(Start, End - Start); + } + void makeState(ParserState NewState) noexcept { + State = NewState; + RawEntry = {}; } - void decrement() { - if (__pos_ == 0) { - set_position(0); - } - else if (__pos_ == parser::npos) { - auto const str_size = __s_.size(); - set_position(parser::start_of( - __s_, str_size != 0 ? str_size - 1 : str_size)); - } else { - while (!set_position(parser::start_of(__s_, __pos_-1))) - ; - } + PosPtr getAfterBack() const noexcept { + return Path.data() + Path.size(); + } + + PosPtr getBeforeFront() const noexcept { + return Path.data() - 1; } - bool set_position(size_t pos) { - if (pos >= __s_.size()) { - __pos_ = parser::npos; - } else { - __pos_ = pos; + /// \brief Return a pointer to the first character after the currently + /// lexed element. + PosPtr getNextTokenStartPos() const noexcept { + switch (State) { + case PS_BeforeBegin: + return Path.data(); + case PS_InRootName: + case PS_InRootDir: + case PS_InFilenames: + return &RawEntry.back() + 1; + case PS_InTrailingSep: + case PS_AtEnd: + return getAfterBack(); } - return valid_iterator_position(); + _LIBCPP_UNREACHABLE(); } - bool valid_iterator_position() const { - if (__pos_ == parser::npos) return true; // end position is valid - return (!parser::is_separator (__s_, __pos_) || - parser::is_root_directory (__s_, __pos_) || - parser::is_trailing_separator(__s_, __pos_) || - parser::is_root_name (__s_, __pos_)); + /// \brief Return a pointer to the first character in the currently lexed + /// element. + PosPtr getCurrentTokenStartPos() const noexcept { + switch (State) { + case PS_BeforeBegin: + case PS_InRootName: + return &Path.front(); + case PS_InRootDir: + case PS_InFilenames: + case PS_InTrailingSep: + return &RawEntry.front(); + case PS_AtEnd: + return &Path.back() + 1; + } + _LIBCPP_UNREACHABLE(); } - bool is_end() const { return __pos_ == parser::npos; } + PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept { + if (P == End || *P != '/') + return nullptr; + const int Inc = P < End ? 1 : -1; + P += Inc; + while (P != End && *P == '/') + P += Inc; + return P; + } - inline bool operator==(path_view_iterator const& __p) { - return __pos_ == __p.__pos_; + PosPtr consumeName(PosPtr P, PosPtr End) const noexcept { + if (P == End || *P == '/') + return nullptr; + const int Inc = P < End ? 1 : -1; + P += Inc; + while (P != End && *P != '/') + P += Inc; + return P; } }; -path_view_iterator pbegin(path const& p) { - return path_view_iterator(p.native()); +string_view_pair separate_filename(string_view_t const & s) { + if (s == "." || s == ".." || s.empty()) return string_view_pair{s, ""}; + auto pos = s.find_last_of('.'); + if (pos == string_view_t::npos) + return string_view_pair{s, string_view_t{}}; + return string_view_pair{s.substr(0, pos), s.substr(pos)}; } -path_view_iterator pend(path const& p) { - path_view_iterator __p(p.native()); - __p.__pos_ = parser::npos; - return __p; +string_view_t createView(PosPtr S, PosPtr E) noexcept { + return {S, static_cast<size_t>(E - S) + 1}; } -} // end namespace +}} // namespace parser + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +using parser::string_view_t; +using parser::string_view_pair; +using parser::PathParser; +using parser::createView; + /////////////////////////////////////////////////////////////////////////////// // path definitions /////////////////////////////////////////////////////////////////////////////// +constexpr path::value_type path::preferred_separator; + path & path::replace_extension(path const & replacement) { path p = extension(); @@ -260,131 +303,145 @@ path & path::replace_extension(path const & replacement) /////////////////////////////////////////////////////////////////////////////// // path.decompose -string_view path::__root_name() const +string_view_t path::__root_name() const { - return parser::is_root_name(__pn_, 0) - ? parser::extract_preferred(__pn_, 0) - : string_view{}; + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) + return *PP; + return {}; } -string_view path::__root_directory() const +string_view_t path::__root_directory() const { - auto start_i = parser::root_directory_start(__pn_); - if(!parser::good(start_i)) { - return {}; - } - return parser::extract_preferred(__pn_, start_i); + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) + ++PP; + if (PP.State == PathParser::PS_InRootDir) + return *PP; + return {}; } -string_view path::__relative_path() const +string_view_t path::__root_path_raw() const { - if (empty()) { - return {__pn_}; - } - auto end_i = parser::root_directory_end(__pn_); - if (not parser::good(end_i)) { - end_i = parser::root_name_end(__pn_); + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) { + auto NextCh = PP.peek(); + if (NextCh && *NextCh == '/') { + ++PP; + return createView(__pn_.data(), &PP.RawEntry.back()); + } + return PP.RawEntry; } - if (not parser::good(end_i)) { - return {__pn_}; - } - return string_view(__pn_).substr(end_i+1); + if (PP.State == PathParser::PS_InRootDir) + return *PP; + return {}; } -string_view path::__parent_path() const +string_view_t path::__relative_path() const { - if (empty() || pbegin(*this) == --pend(*this)) { - return {}; - } - auto end_it = --(--pend(*this)); - auto end_i = parser::end_of(__pn_, end_it.__pos_); - return string_view(__pn_).substr(0, end_i+1); + auto PP = PathParser::CreateBegin(__pn_); + while (PP.State <= PathParser::PS_InRootDir) + ++PP; + if (PP.State == PathParser::PS_AtEnd) + return {}; + return createView(PP.RawEntry.data(), &__pn_.back()); } -string_view path::__filename() const +string_view_t path::__parent_path() const { - return empty() ? string_view{} : *--pend(*this); + if (empty()) + return {}; + auto PP = PathParser::CreateEnd(__pn_); + --PP; + if (PP.RawEntry.data() == __pn_.data()) + return {}; + --PP; + return createView(__pn_.data(), &PP.RawEntry.back()); } -string_view path::__stem() const +string_view_t path::__filename() const +{ + if (empty()) return {}; + return *(--PathParser::CreateEnd(__pn_)); +} + +string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; } -string_view path::__extension() const +string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; } //////////////////////////////////////////////////////////////////////////// // path.comparisons -int path::__compare(const value_type* __s) const { - path_view_iterator thisIter(this->native()); - path_view_iterator sIter(__s); - while (!thisIter.is_end() && !sIter.is_end()) { - int res = (*thisIter).compare(*sIter); +int path::__compare(string_view_t __s) const { + auto PP = PathParser::CreateBegin(__pn_); + auto PP2 = PathParser::CreateBegin(__s); + while (PP && PP2) { + int res = (*PP).compare(*PP2); if (res != 0) return res; - ++thisIter; ++sIter; + ++PP; ++PP2; } - if (thisIter.is_end() && sIter.is_end()) + if (PP.State == PP2.State && PP.State == PathParser::PS_AtEnd) return 0; - if (thisIter.is_end()) + if (PP.State == PathParser::PS_AtEnd) return -1; return 1; } //////////////////////////////////////////////////////////////////////////// // path.nonmembers -size_t hash_value(const path& __p) _NOEXCEPT { - path_view_iterator thisIter(__p.native()); - struct HashPairT { - size_t first; - size_t second; - }; - HashPairT hp = {0, 0}; - std::hash<string_view> hasher; - std::__scalar_hash<decltype(hp)> pair_hasher; - while (!thisIter.is_end()) { - hp.second = hasher(*thisIter); - hp.first = pair_hasher(hp); - ++thisIter; +size_t hash_value(const path& __p) noexcept { + auto PP = PathParser::CreateBegin(__p.native()); + size_t hash_value = 0; + std::hash<string_view_t> hasher; + while (PP) { + hash_value = __hash_combine(hash_value, hasher(*PP)); + ++PP; } - return hp.first; + return hash_value; } //////////////////////////////////////////////////////////////////////////// // path.itr path::iterator path::begin() const { - path_view_iterator pit = pbegin(*this); + auto PP = PathParser::CreateBegin(__pn_); iterator it; it.__path_ptr_ = this; - it.__pos_ = pit.__pos_; - it.__elem_.__assign_view(*pit); + it.__state_ = PP.State; + it.__entry_ = PP.RawEntry; + it.__stashed_elem_.__assign_view(*PP); return it; } path::iterator path::end() const { iterator it{}; + it.__state_ = PathParser::PS_AtEnd; it.__path_ptr_ = this; - it.__pos_ = parser::npos; return it; } path::iterator& path::iterator::__increment() { - path_view_iterator it(__path_ptr_->native(), __pos_); - it.increment(); - __pos_ = it.__pos_; - __elem_.__assign_view(*it); + static_assert(__at_end == PathParser::PS_AtEnd, ""); + PathParser PP(__path_ptr_->native(), __entry_, __state_); + ++PP; + __state_ = PP.State; + __entry_ = PP.RawEntry; + __stashed_elem_.__assign_view(*PP); return *this; } path::iterator& path::iterator::__decrement() { - path_view_iterator it(__path_ptr_->native(), __pos_); - it.decrement(); - __pos_ = it.__pos_; - __elem_.__assign_view(*it); + PathParser PP(__path_ptr_->native(), __entry_, __state_); + --PP; + __state_ = PP.State; + __entry_ = PP.RawEntry; + __stashed_elem_.__assign_view(*PP); return *this; } |