diff options
author | Eric Fiselier <eric@efcs.ca> | 2016-06-17 19:46:40 +0000 |
---|---|---|
committer | Eric Fiselier <eric@efcs.ca> | 2016-06-17 19:46:40 +0000 |
commit | 6e9a694dce70319e60dbdfb09cf055bacb4c948e (patch) | |
tree | 1efe1845c79474ac6791b67647b1d4db5ad75563 /src/experimental | |
parent | a8f47cc80002e17a3c4fea13ba0bd27ba0651326 (diff) | |
download | libcxx-6e9a694dce70319e60dbdfb09cf055bacb4c948e.tar.gz |
Add Filesystem TS -- Complete
Add the completed std::experimental::filesystem implementation and tests.
The implementation supports C++11 or newer.
The TS is built as part of 'libc++experimental.a'. Users of the TS need to
manually link this library. Building and testing the TS can be disabled using
the CMake option '-DLIBCXX_ENABLE_FILESYSTEM=OFF'.
Currently 'libc++experimental.a' is not installed by default. To turn on the
installation of the library use '-DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON'.
git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@273034 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'src/experimental')
-rw-r--r-- | src/experimental/filesystem/directory_iterator.cpp | 256 | ||||
-rw-r--r-- | src/experimental/filesystem/operations.cpp | 754 | ||||
-rw-r--r-- | src/experimental/filesystem/path.cpp | 392 |
3 files changed, 1402 insertions, 0 deletions
diff --git a/src/experimental/filesystem/directory_iterator.cpp b/src/experimental/filesystem/directory_iterator.cpp new file mode 100644 index 000000000..059c4124a --- /dev/null +++ b/src/experimental/filesystem/directory_iterator.cpp @@ -0,0 +1,256 @@ +#include "experimental/filesystem" +#include <dirent.h> +#include <errno.h> + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +namespace { namespace detail { + +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; +} + +template <class ...Args> +inline bool set_or_throw(std::error_code& my_ec, + std::error_code* user_ec, + const char* msg, Args&&... args) +{ + if (user_ec) { + *user_ec = my_ec; + return true; + } + __libcpp_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) { + struct dirent* dir_entry_ptr = nullptr; + errno = 0; // zero errno in order to detect errors + if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) { + ec = capture_errno(); + return {}; + } else { + ec.clear(); + return dir_entry_ptr->d_name; + } +} + +}} // namespace detail + +using detail::set_or_throw; + +class __dir_stream { +public: + __dir_stream() = delete; + __dir_stream& operator=(const __dir_stream&) = delete; + + __dir_stream(__dir_stream&& other) noexcept + : __stream_(other.__stream_), __root_(std::move(other.__root_)), + __entry_(std::move(other.__entry_)) + { + other.__stream_ = nullptr; + } + + + __dir_stream(const path& root, directory_options opts, error_code& ec) + : __stream_(nullptr), + __root_(root) + { + if ((__stream_ = ::opendir(root.c_str())) == nullptr) { + ec = detail::capture_errno(); + const bool allow_eacess = + bool(opts & directory_options::skip_permission_denied); + if (allow_eacess && ec.value() == EACCES) + ec.clear(); + return; + } + advance(ec); + } + + ~__dir_stream() noexcept + { if (__stream_) close(); } + + bool good() const noexcept { return __stream_ != nullptr; } + + bool advance(error_code &ec) { + while (true) { + auto str = detail::posix_readdir(__stream_, ec); + if (str == "." || str == "..") { + continue; + } else if (ec || str.empty()) { + close(); + return false; + } else { + __entry_.assign(__root_ / str); + return true; + } + } + } +private: + std::error_code close() noexcept { + std::error_code m_ec; + if (::closedir(__stream_) == -1) + m_ec = detail::capture_errno(); + __stream_ = nullptr; + return m_ec; + } + + DIR * __stream_{nullptr}; +public: + path __root_; + directory_entry __entry_; +}; + +// directory_iterator + +directory_iterator::directory_iterator(const path& p, error_code *ec, + directory_options opts) +{ + std::error_code m_ec; + __imp_ = make_shared<__dir_stream>(p, opts, m_ec); + if (ec) *ec = m_ec; + if (!__imp_->good()) { + __imp_.reset(); + if (m_ec) + set_or_throw(m_ec, ec, + "directory_iterator::directory_iterator(...)", p); + } +} + +directory_iterator& directory_iterator::__increment(error_code *ec) +{ + _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator"); + std::error_code m_ec; + if (!__imp_->advance(m_ec)) { + __imp_.reset(); + if (m_ec) + set_or_throw(m_ec, ec, "directory_iterator::operator++()"); + } else { + if (ec) ec->clear(); + } + return *this; + +} + +directory_entry const& directory_iterator::__deref() const { + _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator"); + return __imp_->__entry_; +} + +// recursive_directory_iterator + +struct recursive_directory_iterator::__shared_imp { + stack<__dir_stream> __stack_; + directory_options __options_; +}; + +recursive_directory_iterator::recursive_directory_iterator(const path& p, + directory_options opt, error_code *ec) + : __imp_(nullptr), __rec_(true) +{ + std::error_code m_ec; + __dir_stream new_s(p, opt, m_ec); + if (m_ec) set_or_throw(m_ec, ec, "recursive_directory_iterator", p); + if (m_ec || !new_s.good()) return; + + __imp_ = _VSTD::make_shared<__shared_imp>(); + __imp_->__options_ = opt; + __imp_->__stack_.push(_VSTD::move(new_s)); +} + +void recursive_directory_iterator::__pop(error_code* ec) +{ + _LIBCPP_ASSERT(__imp_, "Popping the end iterator"); + __imp_->__stack_.pop(); + if (__imp_->__stack_.size() == 0) { + __imp_.reset(); + if (ec) ec->clear(); + } else { + __advance(ec); + } +} + +directory_options recursive_directory_iterator::options() const { + return __imp_->__options_; +} + +int recursive_directory_iterator::depth() const { + return __imp_->__stack_.size() - 1; +} + +const directory_entry& recursive_directory_iterator::__deref() const { + return __imp_->__stack_.top().__entry_; +} + +recursive_directory_iterator& +recursive_directory_iterator::__increment(error_code *ec) +{ + if (recursion_pending()) { + if (__try_recursion(ec) || (ec && *ec)) + return *this; + } + __rec_ = true; + __advance(ec); + return *this; +} + +void recursive_directory_iterator::__advance(error_code* ec) { + const directory_iterator end_it; + auto& stack = __imp_->__stack_; + std::error_code m_ec; + while (stack.size() > 0) { + if (stack.top().advance(m_ec)) { + if (ec) ec->clear(); + return; + } + if (m_ec) break; + stack.pop(); + } + __imp_.reset(); + if (m_ec) + set_or_throw(m_ec, ec, "recursive_directory_iterator::operator++()"); +} + +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; + __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++()"); + } + } + return false; +} + + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM diff --git a/src/experimental/filesystem/operations.cpp b/src/experimental/filesystem/operations.cpp new file mode 100644 index 000000000..7d382d104 --- /dev/null +++ b/src/experimental/filesystem/operations.cpp @@ -0,0 +1,754 @@ +//===--------------------- filesystem/ops.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 "iterator" +#include "fstream" +#include "type_traits" +#include "random" /* for unique_path */ +#include "cstdlib" +#include "climits" + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <fcntl.h> /* values for fchmodat */ +#if defined(__APPLE__) +#include <sys/time.h> // for ::utimes as used in __last_write_time +#endif + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +filesystem_error::~filesystem_error() {} + + +// POSIX HELPERS + +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; +} + +void set_or_throw(std::error_code const& m_ec, std::error_code* ec, + const char* msg, path const& p = {}, path const& p2 = {}) +{ + if (ec) { + *ec = m_ec; + } else { + string msg_s("std::experimental::filesystem::"); + msg_s += msg; + __libcpp_throw(filesystem_error(msg_s, p, p2, m_ec)); + } +} + +void set_or_throw(std::error_code* ec, const char* msg, + path const& p = {}, path const& p2 = {}) +{ + return set_or_throw(capture_errno(), ec, msg, p, p2); +} + +perms posix_get_perms(const struct ::stat & st) noexcept { + return static_cast<perms>(st.st_mode) & perms::mask; +} + +::mode_t posix_convert_perms(perms prms) { + return static_cast< ::mode_t>(prms & perms::mask); +} + +file_status create_file_status(std::error_code& m_ec, path const& p, + 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 file_status(file_type::not_found); + } + else if (m_ec) { + set_or_throw(m_ec, ec, "posix_stat", p); + return file_status(file_type::none); + } + // else + + file_status fs_tmp; + auto const mode = path_stat.st_mode; + if (S_ISLNK(mode)) fs_tmp.type(file_type::symlink); + else if (S_ISREG(mode)) fs_tmp.type(file_type::regular); + else if (S_ISDIR(mode)) fs_tmp.type(file_type::directory); + else if (S_ISBLK(mode)) fs_tmp.type(file_type::block); + else if (S_ISCHR(mode)) fs_tmp.type(file_type::character); + else if (S_ISFIFO(mode)) fs_tmp.type(file_type::fifo); + else if (S_ISSOCK(mode)) fs_tmp.type(file_type::socket); + else fs_tmp.type(file_type::unknown); + + fs_tmp.permissions(detail::posix_get_perms(path_stat)); + return fs_tmp; +} + +file_status posix_stat(path const & p, struct ::stat& path_stat, + std::error_code* ec) +{ + std::error_code m_ec; + if (::stat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); +} + +file_status posix_stat(path const & p, std::error_code* ec) { + struct ::stat path_stat; + return posix_stat(p, path_stat, ec); +} + +file_status posix_lstat(path const & p, struct ::stat & path_stat, + std::error_code* ec) +{ + std::error_code m_ec; + if (::lstat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); +} + +file_status posix_lstat(path const & p, std::error_code* ec) { + struct ::stat path_stat; + 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); +} + +// DETAIL::MISC + + +bool copy_file_impl(const path& from, const path& to, perms from_perms, + std::error_code *ec) +{ + std::ifstream in(from.c_str(), std::ios::binary); + std::ofstream out(to.c_str(), std::ios::binary); + + 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, ec); + // TODO what if permissions fails? + return true; +} + +}} // end namespace detail + +using detail::set_or_throw; + +path __canonical(path const & orig_p, const path& base, std::error_code *ec) +{ + path p = absolute(orig_p, base); + char buff[PATH_MAX + 1]; + char *ret; + if ((ret = ::realpath(p.c_str(), buff)) == nullptr) { + set_or_throw(ec, "canonical", orig_p, base); + return {}; + } + if (ec) ec->clear(); + return {ret}; +} + +void __copy(const path& from, const path& to, copy_options options, + std::error_code *ec) +{ + const bool sym_status = bool(options & + (copy_options::create_symlinks | copy_options::skip_symlinks)); + + const bool sym_status2 = bool(options & + copy_options::copy_symlinks); + + std::error_code m_ec; + 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); + + 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); + + if (not status_known(t)) + return set_or_throw(m_ec, ec, "copy", from, to); + + if (!exists(f) || is_other(f) || is_other(t) + || (is_directory(f) && is_regular_file(t)) + || detail::stat_equivalent(f_st, t_st)) + { + return set_or_throw(make_error_code(errc::function_not_supported), + ec, "copy", from, to); + } + + if (ec) ec->clear(); + + if (is_symlink(f)) { + if (bool(copy_options::skip_symlinks & options)) { + // do nothing + } else if (not exists(t)) { + __copy_symlink(from, to, ec); + } else { + set_or_throw(make_error_code(errc::file_exists), + ec, "copy", from, to); + } + return; + } + else if (is_regular_file(f)) { + if (bool(copy_options::directories_only & options)) { + // do nothing + } + else if (bool(copy_options::create_symlinks & options)) { + __create_symlink(from, to, ec); + } + else if (bool(copy_options::create_hard_links & options)) { + __create_hard_link(from, to, ec); + } + else if (is_directory(t)) { + __copy_file(from, to / from.filename(), options, ec); + } else { + __copy_file(from, to, options, ec); + } + return; + } + else if (is_directory(f)) { + if (not bool(copy_options::recursive & options) && + bool(copy_options::__in_recursive_copy & options)) + { + return; + } + + if (!exists(t)) { + // create directory to with attributes from 'from'. + __create_directory(to, from, ec); + if (ec && *ec) { return; } + } + 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); + __copy(it->path(), to / it->path().filename(), + options | copy_options::__in_recursive_copy, ec); + if (ec && *ec) { return; } + } + } +} + + +bool __copy_file(const path& from, const path& to, copy_options options, + std::error_code *ec) +{ + if (ec) ec->clear(); + + std::error_code m_ec; + auto from_st = detail::posix_stat(from, &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; + } + + auto to_st = detail::posix_stat(to, &m_ec); + if (!status_known(to_st)) { + set_or_throw(m_ec, ec, "copy_file", from, to); + return false; + } + + const bool to_exists = exists(to_st); + if (to_exists && bool(copy_options::skip_existing & options)) { + return false; + } + 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", from, to); + return false; + } +} + +void __copy_symlink(const path& existing_symlink, const path& new_symlink, + std::error_code *ec) +{ + const path real_path(__read_symlink(existing_symlink, ec)); + if (ec && *ec) { return; } + // NOTE: proposal says you should detect if you should call + // create_symlink or create_directory_symlink. I don't think this + // is needed with POSIX + __create_symlink(real_path, new_symlink, ec); +} + + +bool __create_directories(const path& p, std::error_code *ec) +{ + std::error_code m_ec; + auto const st = detail::posix_stat(p, &m_ec); + if (!status_known(st)) { + set_or_throw(m_ec, ec, "create_directories", p); + return false; + } + else if (is_directory(st)) { + if (ec) ec->clear(); + return false; + } + else if (exists(st)) { + set_or_throw(make_error_code(errc::file_exists), + ec, "create_directories", p); + return false; + } + + const path parent = p.parent_path(); + if (!parent.empty()) { + const file_status parent_st = status(parent, m_ec); + if (not status_known(parent_st)) { + set_or_throw(m_ec, ec, "create_directories", p); + return false; + } + if (not exists(parent_st)) { + __create_directories(parent, ec); + if (ec && *ec) { return false; } + } + } + return __create_directory(p, ec); +} + +bool __create_directory(const path& p, std::error_code *ec) +{ + if (ec) ec->clear(); + if (::mkdir(p.c_str(), static_cast<int>(perms::all)) == 0) + return true; + if (errno != EEXIST || !is_directory(p)) + set_or_throw(ec, "create_directory", p); + return false; +} + +bool __create_directory(path const & p, path const & attributes, + std::error_code *ec) +{ + struct ::stat attr_stat; + std::error_code mec; + auto st = detail::posix_stat(attributes, attr_stat, &mec); + if (!status_known(st)) { + set_or_throw(mec, ec, "create_directory", p, attributes); + return false; + } + if (ec) ec->clear(); + if (::mkdir(p.c_str(), attr_stat.st_mode) == 0) + return true; + if (errno != EEXIST || !is_directory(p)) + set_or_throw(ec, "create_directory", p, attributes); + return false; +} + +void __create_directory_symlink(path const & from, path const & to, + std::error_code *ec){ + if (::symlink(from.c_str(), to.c_str()) != 0) + set_or_throw(ec, "create_directory_symlink", from, to); + else if (ec) + ec->clear(); +} + +void __create_hard_link(const path& from, const path& to, std::error_code *ec){ + if (::link(from.c_str(), to.c_str()) == -1) + set_or_throw(ec, "create_hard_link", from, to); + else if (ec) + ec->clear(); +} + +void __create_symlink(path const & from, path const & to, std::error_code *ec) { + + if (::symlink(from.c_str(), to.c_str()) == -1) + set_or_throw(ec, "create_symlink", from, to); + else if (ec) + ec->clear(); +} + +path __current_path(std::error_code *ec) { + auto size = ::pathconf(".", _PC_PATH_MAX); + _LIBCPP_ASSERT(size >= 0, "pathconf returned a 0 as max size"); + + auto buff = std::unique_ptr<char[]>(new char[size + 1]); + char* ret; + if ((ret = ::getcwd(buff.get(), static_cast<size_t>(size))) == nullptr) { + set_or_throw(ec, "current_path"); + return {}; + } + if (ec) ec->clear(); + return {buff.get()}; +} + +void __current_path(const path& p, std::error_code *ec) { + if (::chdir(p.c_str()) == -1) + set_or_throw(ec, "current_path", p); + else if (ec) + ec->clear(); +} + +bool __equivalent(const path& p1, const path& p2, std::error_code *ec) +{ + std::error_code ec1, ec2; + struct ::stat st1 = {}; + struct ::stat st2 = {}; + auto s1 = detail::posix_stat(p1.native(), st1, &ec1); + 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 (ec) ec->clear(); + return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); +} + + +std::uintmax_t __file_size(const path& p, std::error_code *ec) +{ + std::error_code m_ec; + struct ::stat st; + file_status fst = detail::posix_stat(p, st, &m_ec); + if (!exists(fst) || !is_regular_file(fst)) { + if (!m_ec) + m_ec = make_error_code(errc::not_supported); + set_or_throw(m_ec, ec, "file_size", p); + return static_cast<uintmax_t>(-1); + } + // is_regular_file(p) == true + if (ec) ec->clear(); + return static_cast<std::uintmax_t>(st.st_size); +} + +std::uintmax_t __hard_link_count(const path& p, std::error_code *ec) +{ + std::error_code m_ec; + struct ::stat st; + detail::posix_stat(p, st, &m_ec); + if (m_ec) { + set_or_throw(m_ec, ec, "hard_link_count", p); + return static_cast<std::uintmax_t>(-1); + } + if (ec) ec->clear(); + return static_cast<std::uintmax_t>(st.st_nlink); +} + + +bool __fs_is_empty(const path& p, std::error_code *ec) +{ + if (ec) ec->clear(); + 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{}; + 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; +} + + +file_time_type __last_write_time(const path& p, std::error_code *ec) +{ + using Clock = file_time_type::clock; + std::error_code m_ec; + struct ::stat st; + detail::posix_stat(p, st, &m_ec); + if (m_ec) { + set_or_throw(m_ec, ec, "last_write_time", p); + return file_time_type::min(); + } + if (ec) ec->clear(); + return Clock::from_time_t(static_cast<std::time_t>(st.st_mtime)); +} + + +void __last_write_time(const path& p, file_time_type new_time, + std::error_code *ec) +{ + using namespace std::chrono; + std::error_code m_ec; + +#if defined(__APPLE__) + // FIXME: Use utimensat when it becomes available on OS X. + // This implementation has a race condition between determining the + // last access time and attempting to set it to the same value using + // ::utimes + using Clock = file_time_type::clock; + struct ::stat st; + file_status fst = detail::posix_stat(p, st, &m_ec); + if (m_ec && !status_known(fst)) { + set_or_throw(m_ec, ec, "last_write_time", p); + return; + } + auto write_dur = new_time.time_since_epoch(); + auto write_sec = duration_cast<seconds>(write_dur); + auto access_dur = Clock::from_time_t(st.st_atime).time_since_epoch(); + auto access_sec = duration_cast<seconds>(access_dur); + struct ::timeval tbuf[2]; + tbuf[0].tv_sec = access_sec.count(); + tbuf[0].tv_usec = duration_cast<microseconds>(access_dur - access_sec).count(); + tbuf[1].tv_sec = write_sec.count(); + tbuf[1].tv_usec = duration_cast<microseconds>(write_dur - write_sec).count(); + if (::utimes(p.c_str(), tbuf) == -1) { + m_ec = detail::capture_errno(); + } +#else + auto dur_since_epoch = new_time.time_since_epoch(); + auto sec_since_epoch = duration_cast<seconds>(dur_since_epoch); + auto ns_since_epoch = duration_cast<nanoseconds>(dur_since_epoch - sec_since_epoch); + struct ::timespec tbuf[2]; + tbuf[0].tv_sec = 0; + tbuf[0].tv_nsec = UTIME_OMIT; + tbuf[1].tv_sec = sec_since_epoch.count(); + tbuf[1].tv_nsec = ns_since_epoch.count(); + if (::utimensat(AT_FDCWD, p.c_str(), tbuf, 0) == -1) { + m_ec = detail::capture_errno(); + } +#endif + if (m_ec) + set_or_throw(m_ec, ec, "last_write_time", p); + else if (ec) + ec->clear(); +} + + +void __permissions(const path& p, perms prms, std::error_code *ec) +{ + + const bool resolve_symlinks = bool(perms::resolve_symlinks & prms); + const bool add_perms = bool(perms::add_perms & prms); + const bool remove_perms = bool(perms::remove_perms & prms); + + _LIBCPP_ASSERT(!(add_perms && remove_perms), + "Both add_perms and remove_perms are set"); + + std::error_code m_ec; + file_status st = detail::posix_lstat(p, &m_ec); + if (m_ec) return set_or_throw(m_ec, ec, "permissions", p); + + const bool set_sym_perms = is_symlink(st) && !resolve_symlinks; + + if ((resolve_symlinks && is_symlink(st)) && (add_perms || remove_perms)) { + st = detail::posix_stat(p, &m_ec); + if (m_ec) return set_or_throw(m_ec, ec, "permissions", p); + } + + prms = prms & perms::mask; + if (add_perms) + prms |= st.permissions(); + else if (remove_perms) + prms = st.permissions() & ~prms; + auto real_perms = detail::posix_convert_perms(prms); + +# if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) + const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0; + if (::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { +# else + if (set_sym_perms) + return set_or_throw(make_error_code(errc::operation_not_supported), + ec, "permissions", p); + if (::chmod(p.c_str(), real_perms) == -1) { +# endif + return set_or_throw(ec, "permissions", p); + } + if (ec) ec->clear(); +} + + +path __read_symlink(const path& p, std::error_code *ec) { + char buff[PATH_MAX + 1]; + std::error_code m_ec; + ::ssize_t ret; + if ((ret = ::readlink(p.c_str(), buff, PATH_MAX)) == -1) { + set_or_throw(ec, "read_symlink", p); + return {}; + } + _LIBCPP_ASSERT(ret <= PATH_MAX, "TODO"); + _LIBCPP_ASSERT(ret > 0, "TODO"); + if (ec) ec->clear(); + buff[ret] = 0; + return {buff}; +} + + +bool __remove(const path& p, std::error_code *ec) { + if (ec) ec->clear(); + if (::remove(p.c_str()) == -1) { + set_or_throw(ec, "remove", p); + return false; + } + return true; +} + +namespace { + +std::uintmax_t remove_all_impl(path const & p, std::error_code& ec) +{ + const auto npos = static_cast<std::uintmax_t>(-1); + const file_status st = __symlink_status(p, &ec); + if (ec) return npos; + std::uintmax_t count = 1; + if (is_directory(st)) { + for (directory_iterator it(p, ec); !ec && it != directory_iterator(); + it.increment(ec)) { + auto other_count = remove_all_impl(it->path(), ec); + if (ec) return npos; + count += other_count; + } + if (ec) return npos; + } + if (!__remove(p, &ec)) return npos; + return count; +} + +} // end namespace + +std::uintmax_t __remove_all(const path& p, std::error_code *ec) { + std::error_code mec; + auto count = remove_all_impl(p, mec); + if (mec) { + set_or_throw(mec, ec, "remove_all", p); + return static_cast<std::uintmax_t>(-1); + } + if (ec) ec->clear(); + return count; +} + +void __rename(const path& from, const path& to, std::error_code *ec) { + if (::rename(from.c_str(), to.c_str()) == -1) + set_or_throw(ec, "rename", from, to); + else if (ec) + ec->clear(); +} + +void __resize_file(const path& p, std::uintmax_t size, std::error_code *ec) { + if (::truncate(p.c_str(), static_cast<long>(size)) == -1) + set_or_throw(ec, "resize_file", p); + else if (ec) + ec->clear(); +} + +space_info __space(const path& p, std::error_code *ec) { + space_info si; + struct statvfs m_svfs; + if (::statvfs(p.c_str(), &m_svfs) == -1) { + set_or_throw(ec, "space", p); + si.capacity = si.free = si.available = + static_cast<std::uintmax_t>(-1); + return si; + } + if (ec) ec->clear(); + // Multiply with overflow checking. + auto do_mult = [&](std::uintmax_t& out, fsblkcnt_t other) { + out = other * m_svfs.f_frsize; + if (out / other != m_svfs.f_frsize || other == 0) + out = static_cast<std::uintmax_t>(-1); + }; + do_mult(si.capacity, m_svfs.f_blocks); + do_mult(si.free, m_svfs.f_bfree); + do_mult(si.available, m_svfs.f_bavail); + return si; +} + +file_status __status(const path& p, std::error_code *ec) { + return detail::posix_stat(p, ec); +} + +file_status __symlink_status(const path& p, std::error_code *ec) { + return detail::posix_lstat(p, ec); +} + +path __system_complete(const path& p, std::error_code *ec) { + if (ec) ec->clear(); + 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; + } + if (!m_ec || m_ec == make_error_code(errc::no_such_file_or_directory)) + m_ec = make_error_code(errc::not_a_directory); + set_or_throw(m_ec, ec, "temp_directory_path"); + return {}; +} + +// An absolute path is composed according to the table in [fs.op.absolute]. +path absolute(const path& p, const path& base) { + auto root_name = p.root_name(); + auto root_dir = p.root_directory(); + + if (!root_name.empty() && !root_dir.empty()) + return p; + + auto abs_base = base.is_absolute() ? base : absolute(base); + + /* !has_root_name && !has_root_dir */ + if (root_name.empty() && root_dir.empty()) + { + return abs_base / p; + } + else if (!root_name.empty()) /* has_root_name && !has_root_dir */ + { + return root_name / abs_base.root_directory() + / + abs_base.relative_path() / p.relative_path(); + } + else /* !has_root_name && has_root_dir */ + { + if (abs_base.has_root_name()) + return abs_base.root_name() / p; + // else p is absolute, return outside of block + } + return p; +} + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM diff --git a/src/experimental/filesystem/path.cpp b/src/experimental/filesystem/path.cpp new file mode 100644 index 000000000..9c72abd7c --- /dev/null +++ b/src/experimental/filesystem/path.cpp @@ -0,0 +1,392 @@ +//===--------------------- filesystem/path.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 "experimental/string_view" +#include "utility" + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +_LIBCPP_CONSTEXPR path::value_type path::preferred_separator; + + +namespace { namespace parser +{ + +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 &); + +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); + +inline bool is_separator(const string_type& s, size_t pos) { + return (pos < s.size() && s[pos] == preferred_separator); +} + +inline bool is_root_name(const string_type& s, size_t pos) { + return good(pos) && pos == 0 ? root_name_start(s) == pos : false; +} + +inline bool is_root_directory(const string_type& s, size_t pos) { + return good(pos) ? root_directory_start(s) == pos : false; +} + +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)); +} + +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; +} + +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; +} + +inline size_t root_name_start(const string_type& s) { + return good(root_name_end(s)) ? 0 : npos; +} + +size_t root_name_end(const string_type& s) { + if (s.size() < 2 || s[0] != preferred_separator + || s[1] != preferred_separator) { + return npos; + } + if (s.size() == 2) { + return 1; + } + size_t index = 2; // current position + if (s[index] == preferred_separator) { + return npos; + } + while (index + 1 < s.size() && s[index+1] != preferred_separator) { + ++index; + } + 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) {} + + string_view operator*() const { + return parser::extract_preferred(__s_, __pos_); + } + + path_view_iterator& operator++() { + increment(); + return *this; + } + + path_view_iterator& operator--() { + decrement(); + return *this; + } + + void increment() { + if (__pos_ == parser::npos) return; + while (! set_position(parser::end_of(__s_, __pos_)+1)) + ; + return; + } + + 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))) + ; + } + } + + bool set_position(size_t pos) { + if (pos >= __s_.size()) { + __pos_ = parser::npos; + } else { + __pos_ = pos; + } + return valid_iterator_position(); + } + + 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_)); + } + + bool is_end() const { return __pos_ == parser::npos; } + + inline bool operator==(path_view_iterator const& __p) { + return __pos_ == __p.__pos_; + } +}; + +path_view_iterator pbegin(path const& p) { + return path_view_iterator(p.native()); +} + +path_view_iterator pend(path const& p) { + path_view_iterator __p(p.native()); + __p.__pos_ = parser::npos; + return __p; +} + +} // end namespace +/////////////////////////////////////////////////////////////////////////////// +// path definitions +/////////////////////////////////////////////////////////////////////////////// + +path & path::replace_extension(path const & replacement) +{ + path p = extension(); + if (not p.empty()) { + __pn_.erase(__pn_.size() - p.native().size()); + } + if (!replacement.empty()) { + if (replacement.native()[0] != '.') { + __pn_ += "."; + } + __pn_.append(replacement.__pn_); + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// +// path.decompose + +string_view path::__root_name() const +{ + return parser::is_root_name(__pn_, 0) + ? parser::extract_preferred(__pn_, 0) + : string_view{}; +} + +string_view 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); +} + +string_view path::__relative_path() 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_); + } + if (not parser::good(end_i)) { + return {__pn_}; + } + return string_view(__pn_).substr(end_i+1); +} + +string_view path::__parent_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); +} + +string_view path::__filename() const +{ + return empty() ? string_view{} : *--pend(*this); +} + +string_view path::__stem() const +{ + return parser::separate_filename(__filename()).first; +} + +string_view 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); + if (res != 0) return res; + ++thisIter; ++sIter; + } + if (thisIter.is_end() && sIter.is_end()) + return 0; + if (thisIter.is_end()) + 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; + } + return hp.first; +} + +//////////////////////////////////////////////////////////////////////////// +// path.itr +path::iterator path::begin() const +{ + path_view_iterator pit = pbegin(*this); + iterator it; + it.__path_ptr_ = this; + it.__pos_ = pit.__pos_; + it.__elem_.__assign_view(*pit); + return it; +} + +path::iterator path::end() const +{ + iterator it{}; + 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); + return *this; +} + +path::iterator& path::iterator::__decrement() { + path_view_iterator it(__path_ptr_->native(), __pos_); + it.decrement(); + __pos_ = it.__pos_; + __elem_.__assign_view(*it); + return *this; +} + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM |