mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-26 22:38:30 +00:00
Release Cosmopolitan v3.6.0
This release is an atomic upgrade to GCC 14.1.0 with C23 and C++23
This commit is contained in:
parent
62ace3623a
commit
5660ec4741
1585 changed files with 117353 additions and 271644 deletions
73
third_party/libcxx/fs/directory_entry.cpp
vendored
Normal file
73
third_party/libcxx/fs/directory_entry.cpp
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__config>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
|
||||
#include "file_descriptor.h"
|
||||
#include "posix_compat.h"
|
||||
#include "time_utils.h"
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
error_code directory_entry::__do_refresh() noexcept {
|
||||
__data_.__reset();
|
||||
error_code failure_ec;
|
||||
|
||||
detail::StatT full_st;
|
||||
file_status st = detail::posix_lstat(__p_, full_st, &failure_ec);
|
||||
if (!status_known(st)) {
|
||||
__data_.__reset();
|
||||
return failure_ec;
|
||||
}
|
||||
|
||||
if (!filesystem::exists(st) || !filesystem::is_symlink(st)) {
|
||||
__data_.__cache_type_ = directory_entry::_RefreshNonSymlink;
|
||||
__data_.__type_ = st.type();
|
||||
__data_.__non_sym_perms_ = st.permissions();
|
||||
} else { // we have a symlink
|
||||
__data_.__sym_perms_ = st.permissions();
|
||||
// Get the information about the linked entity.
|
||||
// Ignore errors from stat, since we don't want errors regarding symlink
|
||||
// resolution to be reported to the user.
|
||||
error_code ignored_ec;
|
||||
st = detail::posix_stat(__p_, full_st, &ignored_ec);
|
||||
|
||||
__data_.__type_ = st.type();
|
||||
__data_.__non_sym_perms_ = st.permissions();
|
||||
|
||||
// If we failed to resolve the link, then only partially populate the
|
||||
// cache.
|
||||
if (!status_known(st)) {
|
||||
__data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved;
|
||||
return error_code{};
|
||||
}
|
||||
// Otherwise, we resolved the link, potentially as not existing.
|
||||
// That's OK.
|
||||
__data_.__cache_type_ = directory_entry::_RefreshSymlink;
|
||||
}
|
||||
|
||||
if (filesystem::is_regular_file(st))
|
||||
__data_.__size_ = static_cast<uintmax_t>(full_st.st_size);
|
||||
|
||||
if (filesystem::exists(st)) {
|
||||
__data_.__nlink_ = static_cast<uintmax_t>(full_st.st_nlink);
|
||||
|
||||
// Attempt to extract the mtime, and fail if it's not representable using
|
||||
// file_time_type. For now we ignore the error, as we'll report it when
|
||||
// the value is actually used.
|
||||
error_code ignored_ec;
|
||||
__data_.__write_time_ = detail::__extract_last_write_time(__p_, full_st, &ignored_ec);
|
||||
}
|
||||
|
||||
return failure_ec;
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
322
third_party/libcxx/fs/directory_iterator.cpp
vendored
Normal file
322
third_party/libcxx/fs/directory_iterator.cpp
vendored
Normal file
|
@ -0,0 +1,322 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__assert>
|
||||
#include <__config>
|
||||
#include <errno.h>
|
||||
#include <filesystem>
|
||||
#include <stack>
|
||||
#include <utility>
|
||||
|
||||
#include "error.h"
|
||||
#include "file_descriptor.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <dirent.h> // for DIR & friends
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
using detail::ErrorHandler;
|
||||
|
||||
#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) {
|
||||
if (root.native().empty()) {
|
||||
ec = make_error_code(errc::no_such_file_or_directory);
|
||||
return;
|
||||
}
|
||||
__stream_ = ::FindFirstFileW((root / "*").c_str(), &__data_);
|
||||
if (__stream_ == INVALID_HANDLE_VALUE) {
|
||||
ec = detail::make_windows_error(GetLastError());
|
||||
const bool ignore_permission_denied = bool(opts & directory_options::skip_permission_denied);
|
||||
if (ignore_permission_denied && ec.value() == static_cast<int>(errc::permission_denied))
|
||||
ec.clear();
|
||||
return;
|
||||
}
|
||||
if (!assign())
|
||||
advance(ec);
|
||||
}
|
||||
|
||||
~__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 (::FindNextFileW(__stream_, &__data_)) {
|
||||
if (assign())
|
||||
return true;
|
||||
}
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool assign() {
|
||||
if (!wcscmp(__data_.cFileName, L".") || !wcscmp(__data_.cFileName, L".."))
|
||||
return false;
|
||||
// FIXME: Cache more of this
|
||||
// directory_entry::__cached_data cdata;
|
||||
// cdata.__type_ = get_file_type(__data_);
|
||||
// cdata.__size_ = get_file_size(__data_);
|
||||
// cdata.__write_time_ = get_write_time(__data_);
|
||||
__entry_.__assign_iter_entry(
|
||||
__root_ / __data_.cFileName, directory_entry::__create_iter_result(detail::get_file_type(__data_)));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
error_code close() noexcept {
|
||||
error_code ec;
|
||||
if (!::FindClose(__stream_))
|
||||
ec = detail::make_windows_error(GetLastError());
|
||||
__stream_ = INVALID_HANDLE_VALUE;
|
||||
return ec;
|
||||
}
|
||||
|
||||
HANDLE __stream_{INVALID_HANDLE_VALUE};
|
||||
WIN32_FIND_DATAW __data_;
|
||||
|
||||
public:
|
||||
path __root_;
|
||||
directory_entry __entry_;
|
||||
};
|
||||
#else
|
||||
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_eacces = bool(opts & directory_options::skip_permission_denied);
|
||||
if (allow_eacces && 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_type_pair = detail::posix_readdir(__stream_, ec);
|
||||
auto& str = str_type_pair.first;
|
||||
if (str == "." || str == "..") {
|
||||
continue;
|
||||
} else if (ec || str.empty()) {
|
||||
close();
|
||||
return false;
|
||||
} else {
|
||||
__entry_.__assign_iter_entry(__root_ / str, directory_entry::__create_iter_result(str_type_pair.second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
error_code close() noexcept {
|
||||
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_;
|
||||
};
|
||||
#endif
|
||||
|
||||
// directory_iterator
|
||||
|
||||
directory_iterator::directory_iterator(const path& p, error_code* ec, directory_options opts) {
|
||||
ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
|
||||
|
||||
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)
|
||||
err.report(m_ec);
|
||||
}
|
||||
}
|
||||
|
||||
directory_iterator& directory_iterator::__increment(error_code* ec) {
|
||||
_LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Attempting to increment an invalid iterator");
|
||||
ErrorHandler<void> err("directory_iterator::operator++()", ec);
|
||||
|
||||
error_code m_ec;
|
||||
if (!__imp_->advance(m_ec)) {
|
||||
path root = std::move(__imp_->__root_);
|
||||
__imp_.reset();
|
||||
if (m_ec)
|
||||
err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_entry const& directory_iterator::__dereference() const {
|
||||
_LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "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) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
__dir_stream new_s(p, opt, m_ec);
|
||||
if (m_ec)
|
||||
err.report(m_ec);
|
||||
if (m_ec || !new_s.good())
|
||||
return;
|
||||
|
||||
__imp_ = make_shared<__shared_imp>();
|
||||
__imp_->__options_ = opt;
|
||||
__imp_->__stack_.push(std::move(new_s));
|
||||
}
|
||||
|
||||
void recursive_directory_iterator::__pop(error_code* ec) {
|
||||
_LIBCPP_ASSERT_NON_NULL(__imp_ != nullptr, "Popping the end iterator");
|
||||
if (ec)
|
||||
ec->clear();
|
||||
__imp_->__stack_.pop();
|
||||
if (__imp_->__stack_.size() == 0)
|
||||
__imp_.reset();
|
||||
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::__dereference() const { return __imp_->__stack_.top().__entry_; }
|
||||
|
||||
recursive_directory_iterator& recursive_directory_iterator::__increment(error_code* ec) {
|
||||
if (ec)
|
||||
ec->clear();
|
||||
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) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
|
||||
|
||||
const directory_iterator end_it;
|
||||
auto& stack = __imp_->__stack_;
|
||||
error_code m_ec;
|
||||
while (stack.size() > 0) {
|
||||
if (stack.top().advance(m_ec))
|
||||
return;
|
||||
if (m_ec)
|
||||
break;
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (m_ec) {
|
||||
path root = std::move(stack.top().__root_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str());
|
||||
} else {
|
||||
__imp_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool recursive_directory_iterator::__try_recursion(error_code* ec) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
|
||||
|
||||
bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
|
||||
|
||||
auto& curr_it = __imp_->__stack_.top();
|
||||
|
||||
bool skip_rec = false;
|
||||
error_code m_ec;
|
||||
if (!rec_sym) {
|
||||
file_status st(curr_it.__entry_.__get_sym_ft(&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_.__get_ft(&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(std::move(new_it));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
path at_ent = std::move(curr_it.__entry_.__p_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "attempting recursion into " PATH_CSTR_FMT, at_ent.c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
232
third_party/libcxx/fs/error.h
vendored
Normal file
232
third_party/libcxx/fs/error.h
vendored
Normal file
|
@ -0,0 +1,232 @@
|
|||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
|
||||
#ifndef FILESYSTEM_ERROR_H
|
||||
#define FILESYSTEM_ERROR_H
|
||||
|
||||
#include <__assert>
|
||||
#include <__config>
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility> // __libcpp_unreachable
|
||||
|
||||
#include "format_string.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h> // ERROR_* macros
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
|
||||
inline errc __win_err_to_errc(int err) {
|
||||
constexpr struct {
|
||||
DWORD win;
|
||||
errc errc;
|
||||
} win_error_mapping[] = {
|
||||
{ERROR_ACCESS_DENIED, errc::permission_denied},
|
||||
{ERROR_ALREADY_EXISTS, errc::file_exists},
|
||||
{ERROR_BAD_NETPATH, errc::no_such_file_or_directory},
|
||||
{ERROR_BAD_PATHNAME, errc::no_such_file_or_directory},
|
||||
{ERROR_BAD_UNIT, errc::no_such_device},
|
||||
{ERROR_BROKEN_PIPE, errc::broken_pipe},
|
||||
{ERROR_BUFFER_OVERFLOW, errc::filename_too_long},
|
||||
{ERROR_BUSY, errc::device_or_resource_busy},
|
||||
{ERROR_BUSY_DRIVE, errc::device_or_resource_busy},
|
||||
{ERROR_CANNOT_MAKE, errc::permission_denied},
|
||||
{ERROR_CANTOPEN, errc::io_error},
|
||||
{ERROR_CANTREAD, errc::io_error},
|
||||
{ERROR_CANTWRITE, errc::io_error},
|
||||
{ERROR_CURRENT_DIRECTORY, errc::permission_denied},
|
||||
{ERROR_DEV_NOT_EXIST, errc::no_such_device},
|
||||
{ERROR_DEVICE_IN_USE, errc::device_or_resource_busy},
|
||||
{ERROR_DIR_NOT_EMPTY, errc::directory_not_empty},
|
||||
{ERROR_DIRECTORY, errc::invalid_argument},
|
||||
{ERROR_DISK_FULL, errc::no_space_on_device},
|
||||
{ERROR_FILE_EXISTS, errc::file_exists},
|
||||
{ERROR_FILE_NOT_FOUND, errc::no_such_file_or_directory},
|
||||
{ERROR_HANDLE_DISK_FULL, errc::no_space_on_device},
|
||||
{ERROR_INVALID_ACCESS, errc::permission_denied},
|
||||
{ERROR_INVALID_DRIVE, errc::no_such_device},
|
||||
{ERROR_INVALID_FUNCTION, errc::function_not_supported},
|
||||
{ERROR_INVALID_HANDLE, errc::invalid_argument},
|
||||
{ERROR_INVALID_NAME, errc::no_such_file_or_directory},
|
||||
{ERROR_INVALID_PARAMETER, errc::invalid_argument},
|
||||
{ERROR_LOCK_VIOLATION, errc::no_lock_available},
|
||||
{ERROR_LOCKED, errc::no_lock_available},
|
||||
{ERROR_NEGATIVE_SEEK, errc::invalid_argument},
|
||||
{ERROR_NOACCESS, errc::permission_denied},
|
||||
{ERROR_NOT_ENOUGH_MEMORY, errc::not_enough_memory},
|
||||
{ERROR_NOT_READY, errc::resource_unavailable_try_again},
|
||||
{ERROR_NOT_SAME_DEVICE, errc::cross_device_link},
|
||||
{ERROR_NOT_SUPPORTED, errc::not_supported},
|
||||
{ERROR_OPEN_FAILED, errc::io_error},
|
||||
{ERROR_OPEN_FILES, errc::device_or_resource_busy},
|
||||
{ERROR_OPERATION_ABORTED, errc::operation_canceled},
|
||||
{ERROR_OUTOFMEMORY, errc::not_enough_memory},
|
||||
{ERROR_PATH_NOT_FOUND, errc::no_such_file_or_directory},
|
||||
{ERROR_READ_FAULT, errc::io_error},
|
||||
{ERROR_REPARSE_TAG_INVALID, errc::invalid_argument},
|
||||
{ERROR_RETRY, errc::resource_unavailable_try_again},
|
||||
{ERROR_SEEK, errc::io_error},
|
||||
{ERROR_SHARING_VIOLATION, errc::permission_denied},
|
||||
{ERROR_TOO_MANY_OPEN_FILES, errc::too_many_files_open},
|
||||
{ERROR_WRITE_FAULT, errc::io_error},
|
||||
{ERROR_WRITE_PROTECT, errc::permission_denied},
|
||||
};
|
||||
|
||||
for (const auto& pair : win_error_mapping)
|
||||
if (pair.win == static_cast<DWORD>(err))
|
||||
return pair.errc;
|
||||
return errc::invalid_argument;
|
||||
}
|
||||
|
||||
#endif // _LIBCPP_WIN32API
|
||||
|
||||
inline error_code capture_errno() {
|
||||
_LIBCPP_ASSERT_INTERNAL(errno != 0, "Expected errno to be non-zero");
|
||||
return error_code(errno, generic_category());
|
||||
}
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
inline error_code make_windows_error(int err) { return make_error_code(__win_err_to_errc(err)); }
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
T error_value();
|
||||
template <>
|
||||
inline constexpr void error_value<void>() {}
|
||||
template <>
|
||||
inline bool error_value<bool>() {
|
||||
return false;
|
||||
}
|
||||
#if __SIZEOF_SIZE_T__ != __SIZEOF_LONG_LONG__
|
||||
template <>
|
||||
inline size_t error_value<size_t>() {
|
||||
return size_t(-1);
|
||||
}
|
||||
#endif
|
||||
template <>
|
||||
inline uintmax_t error_value<uintmax_t>() {
|
||||
return uintmax_t(-1);
|
||||
}
|
||||
template <>
|
||||
inline constexpr file_time_type error_value<file_time_type>() {
|
||||
return file_time_type::min();
|
||||
}
|
||||
template <>
|
||||
inline path error_value<path>() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct ErrorHandler {
|
||||
const char* func_name_;
|
||||
error_code* ec_ = nullptr;
|
||||
const path* p1_ = nullptr;
|
||||
const path* p2_ = nullptr;
|
||||
|
||||
ErrorHandler(const char* fname, error_code* ec, const path* p1 = nullptr, const path* p2 = nullptr)
|
||||
: func_name_(fname), ec_(ec), p1_(p1), p2_(p2) {
|
||||
if (ec_)
|
||||
ec_->clear();
|
||||
}
|
||||
|
||||
T report(const error_code& ec) const {
|
||||
if (ec_) {
|
||||
*ec_ = ec;
|
||||
return error_value<T>();
|
||||
}
|
||||
string what = string("in ") + func_name_;
|
||||
switch (bool(p1_) + bool(p2_)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1_, ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1_, *p2_, ec);
|
||||
}
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
|
||||
_LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 0)
|
||||
void report_impl(const error_code& ec, const char* msg, va_list ap) const {
|
||||
if (ec_) {
|
||||
*ec_ = ec;
|
||||
return;
|
||||
}
|
||||
string what = string("in ") + func_name_ + ": " + detail::vformat_string(msg, ap);
|
||||
switch (bool(p1_) + bool(p2_)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1_, ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1_, *p2_, ec);
|
||||
}
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
|
||||
_LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 4)
|
||||
T report(const error_code& ec, const char* msg, ...) const {
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
try {
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
report_impl(ec, msg, ap);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
} catch (...) {
|
||||
va_end(ap);
|
||||
throw;
|
||||
}
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
va_end(ap);
|
||||
return error_value<T>();
|
||||
}
|
||||
|
||||
T report(errc const& err) const { return report(make_error_code(err)); }
|
||||
|
||||
_LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 4)
|
||||
T report(errc const& err, const char* msg, ...) const {
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
try {
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
report_impl(make_error_code(err), msg, ap);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
} catch (...) {
|
||||
va_end(ap);
|
||||
throw;
|
||||
}
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
va_end(ap);
|
||||
return error_value<T>();
|
||||
}
|
||||
|
||||
private:
|
||||
ErrorHandler(ErrorHandler const&) = delete;
|
||||
ErrorHandler& operator=(ErrorHandler const&) = delete;
|
||||
};
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // FILESYSTEM_ERROR_H
|
291
third_party/libcxx/fs/file_descriptor.h
vendored
Normal file
291
third_party/libcxx/fs/file_descriptor.h
vendored
Normal file
|
@ -0,0 +1,291 @@
|
|||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
|
||||
#ifndef FILESYSTEM_FILE_DESCRIPTOR_H
|
||||
#define FILESYSTEM_FILE_DESCRIPTOR_H
|
||||
|
||||
#include <__config>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "error.h"
|
||||
#include "posix_compat.h"
|
||||
#include "time_utils.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <dirent.h> // for DIR & friends
|
||||
# include <fcntl.h> // values for fchmodat
|
||||
# include <sys/stat.h>
|
||||
# include <sys/statvfs.h>
|
||||
# include <unistd.h>
|
||||
#endif // defined(_LIBCPP_WIN32API)
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if !defined(_LIBCPP_WIN32API)
|
||||
|
||||
# if defined(DT_BLK)
|
||||
template <class DirEntT, class = decltype(DirEntT::d_type)>
|
||||
file_type get_file_type(DirEntT* ent, int) {
|
||||
switch (ent->d_type) {
|
||||
case DT_BLK:
|
||||
return file_type::block;
|
||||
case DT_CHR:
|
||||
return file_type::character;
|
||||
case DT_DIR:
|
||||
return file_type::directory;
|
||||
case DT_FIFO:
|
||||
return file_type::fifo;
|
||||
case DT_LNK:
|
||||
return file_type::symlink;
|
||||
case DT_REG:
|
||||
return file_type::regular;
|
||||
case DT_SOCK:
|
||||
return file_type::socket;
|
||||
// Unlike in lstat, hitting "unknown" here simply means that the underlying
|
||||
// filesystem doesn't support d_type. Report is as 'none' so we correctly
|
||||
// set the cache to empty.
|
||||
case DT_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
return file_type::none;
|
||||
}
|
||||
# endif // defined(DT_BLK)
|
||||
|
||||
template <class DirEntT>
|
||||
file_type get_file_type(DirEntT*, long) {
|
||||
return file_type::none;
|
||||
}
|
||||
|
||||
inline pair<string_view, file_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) {
|
||||
if (errno)
|
||||
ec = capture_errno();
|
||||
return {};
|
||||
} else {
|
||||
return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
|
||||
}
|
||||
}
|
||||
|
||||
#else // _LIBCPP_WIN32API
|
||||
|
||||
inline file_type get_file_type(const WIN32_FIND_DATAW& data) {
|
||||
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
|
||||
return file_type::symlink;
|
||||
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
return file_type::directory;
|
||||
return file_type::regular;
|
||||
}
|
||||
inline uintmax_t get_file_size(const WIN32_FIND_DATAW& data) {
|
||||
return (static_cast<uint64_t>(data.nFileSizeHigh) << 32) + data.nFileSizeLow;
|
||||
}
|
||||
inline file_time_type get_write_time(const WIN32_FIND_DATAW& data) {
|
||||
ULARGE_INTEGER tmp;
|
||||
const FILETIME& time = data.ftLastWriteTime;
|
||||
tmp.u.LowPart = time.dwLowDateTime;
|
||||
tmp.u.HighPart = time.dwHighDateTime;
|
||||
return file_time_type(file_time_type::duration(tmp.QuadPart));
|
||||
}
|
||||
|
||||
#endif // !_LIBCPP_WIN32API
|
||||
|
||||
// POSIX HELPERS
|
||||
|
||||
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;
|
||||
#ifdef _LIBCPP_WIN32API
|
||||
// TODO: most of the filesystem implementation uses native Win32 calls
|
||||
// (mostly via posix_compat.h). However, here we use the C-runtime APIs to
|
||||
// open a file, because we subsequently pass the C-runtime fd to
|
||||
// `std::[io]fstream::__open(int fd)` in order to implement copy_file.
|
||||
//
|
||||
// Because we're calling the windows C-runtime, win32 error codes are
|
||||
// translated into C error numbers by the C runtime, and returned in errno,
|
||||
// rather than being accessible directly via GetLastError.
|
||||
//
|
||||
// Ideally copy_file should be calling the Win32 CopyFile2 function, which
|
||||
// works on paths, not open files -- at which point this FileDescriptor type
|
||||
// will no longer be needed on windows at all.
|
||||
fd = ::_wopen(p->c_str(), args...);
|
||||
#else
|
||||
fd = open(p->c_str(), args...);
|
||||
#endif
|
||||
|
||||
if (fd == -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 filesystem::status_known(m_status); }
|
||||
|
||||
file_status refresh_status(error_code& ec);
|
||||
|
||||
void close() noexcept {
|
||||
if (fd != -1) {
|
||||
#ifdef _LIBCPP_WIN32API
|
||||
::_close(fd);
|
||||
#else
|
||||
::close(fd);
|
||||
#endif
|
||||
// FIXME: shouldn't this return an error_code?
|
||||
}
|
||||
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(FileDescriptor const&) = delete;
|
||||
FileDescriptor& operator=(FileDescriptor const&) = delete;
|
||||
|
||||
private:
|
||||
explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {}
|
||||
};
|
||||
|
||||
inline perms posix_get_perms(const StatT& st) noexcept { return static_cast<perms>(st.st_mode) & perms::mask; }
|
||||
|
||||
inline file_status create_file_status(error_code& m_ec, path const& p, const StatT& path_stat, 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) {
|
||||
ErrorHandler<void> err("posix_stat", ec, &p);
|
||||
err.report(m_ec, "failed to determine attributes for the specified path");
|
||||
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;
|
||||
}
|
||||
|
||||
inline file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) {
|
||||
error_code m_ec;
|
||||
if (detail::stat(p.c_str(), &path_stat) == -1)
|
||||
m_ec = detail::capture_errno();
|
||||
return create_file_status(m_ec, p, path_stat, ec);
|
||||
}
|
||||
|
||||
inline file_status posix_stat(path const& p, error_code* ec) {
|
||||
StatT path_stat;
|
||||
return posix_stat(p, path_stat, ec);
|
||||
}
|
||||
|
||||
inline file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) {
|
||||
error_code m_ec;
|
||||
if (detail::lstat(p.c_str(), &path_stat) == -1)
|
||||
m_ec = detail::capture_errno();
|
||||
return create_file_status(m_ec, p, path_stat, ec);
|
||||
}
|
||||
|
||||
inline file_status posix_lstat(path const& p, error_code* ec) {
|
||||
StatT path_stat;
|
||||
return posix_lstat(p, path_stat, ec);
|
||||
}
|
||||
|
||||
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
|
||||
inline bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) {
|
||||
if (detail::ftruncate(fd.fd, to_size) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
ec.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) {
|
||||
if (detail::fchmod(fd.fd, st.st_mode) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
ec.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool stat_equivalent(const StatT& st1, const StatT& st2) {
|
||||
return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
|
||||
}
|
||||
|
||||
inline file_status FileDescriptor::refresh_status(error_code& ec) {
|
||||
// FD must be open and good.
|
||||
m_status = file_status{};
|
||||
m_stat = {};
|
||||
error_code m_ec;
|
||||
if (detail::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
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // FILESYSTEM_FILE_DESCRIPTOR_H
|
62
third_party/libcxx/fs/filesystem_clock.cpp
vendored
Normal file
62
third_party/libcxx/fs/filesystem_clock.cpp
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__config>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# include "time_utils.h"
|
||||
#endif
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#if __has_include(<unistd.h>)
|
||||
# include <unistd.h> // _POSIX_TIMERS
|
||||
#endif
|
||||
|
||||
#if __has_include(<sys/time.h>)
|
||||
# include <sys/time.h> // for gettimeofday and timeval
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__gnu_hurd__) || (defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0)
|
||||
# define _LIBCPP_HAS_CLOCK_GETTIME
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
const bool _FilesystemClock::is_steady;
|
||||
|
||||
_FilesystemClock::time_point _FilesystemClock::now() noexcept {
|
||||
typedef chrono::duration<rep> __secs;
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
typedef chrono::duration<rep, nano> __nsecs;
|
||||
FILETIME time;
|
||||
GetSystemTimeAsFileTime(&time);
|
||||
detail::TimeSpec tp = detail::filetime_to_timespec(time);
|
||||
return time_point(__secs(tp.tv_sec) + chrono::duration_cast<duration>(__nsecs(tp.tv_nsec)));
|
||||
#elif defined(_LIBCPP_HAS_CLOCK_GETTIME)
|
||||
typedef chrono::duration<rep, nano> __nsecs;
|
||||
struct timespec tp;
|
||||
if (0 != clock_gettime(CLOCK_REALTIME, &tp))
|
||||
__throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed");
|
||||
return time_point(__secs(tp.tv_sec) + chrono::duration_cast<duration>(__nsecs(tp.tv_nsec)));
|
||||
#else
|
||||
typedef chrono::duration<rep, micro> __microsecs;
|
||||
timeval tv;
|
||||
gettimeofday(&tv, 0);
|
||||
return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec));
|
||||
#endif
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
39
third_party/libcxx/fs/filesystem_error.cpp
vendored
Normal file
39
third_party/libcxx/fs/filesystem_error.cpp
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__config>
|
||||
#include <__utility/unreachable.h>
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
|
||||
#include "format_string.h"
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
filesystem_error::~filesystem_error() {}
|
||||
|
||||
void filesystem_error::__create_what(int __num_paths) {
|
||||
const char* derived_what = system_error::what();
|
||||
__storage_->__what_ = [&]() -> string {
|
||||
switch (__num_paths) {
|
||||
case 0:
|
||||
return detail::format_string("filesystem error: %s", derived_what);
|
||||
case 1:
|
||||
return detail::format_string("filesystem error: %s [" PATH_CSTR_FMT "]", derived_what, path1().c_str());
|
||||
case 2:
|
||||
return detail::format_string(
|
||||
"filesystem error: %s [" PATH_CSTR_FMT "] [" PATH_CSTR_FMT "]",
|
||||
derived_what,
|
||||
path1().c_str(),
|
||||
path2().c_str());
|
||||
}
|
||||
__libcpp_unreachable();
|
||||
}();
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
77
third_party/libcxx/fs/format_string.h
vendored
Normal file
77
third_party/libcxx/fs/format_string.h
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
|
||||
#ifndef FILESYSTEM_FORMAT_STRING_H
|
||||
#define FILESYSTEM_FORMAT_STRING_H
|
||||
|
||||
#include <__assert>
|
||||
#include <__config>
|
||||
#include <array>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define PATHSTR(x) (L##x)
|
||||
# define PATH_CSTR_FMT "\"%ls\""
|
||||
#else
|
||||
# define PATHSTR(x) (x)
|
||||
# define PATH_CSTR_FMT "\"%s\""
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) string vformat_string(const char* msg, va_list ap) {
|
||||
array<char, 256> buf;
|
||||
|
||||
va_list apcopy;
|
||||
va_copy(apcopy, ap);
|
||||
int ret = ::vsnprintf(buf.data(), buf.size(), msg, apcopy);
|
||||
va_end(apcopy);
|
||||
|
||||
string result;
|
||||
if (static_cast<size_t>(ret) < buf.size()) {
|
||||
result.assign(buf.data(), static_cast<size_t>(ret));
|
||||
} else {
|
||||
// we did not provide a long enough buffer on our first attempt. The
|
||||
// return value is the number of bytes (excluding the null byte) that are
|
||||
// needed for formatting.
|
||||
size_t size_with_null = static_cast<size_t>(ret) + 1;
|
||||
result.__resize_default_init(size_with_null - 1);
|
||||
ret = ::vsnprintf(&result[0], size_with_null, msg, ap);
|
||||
_LIBCPP_ASSERT_INTERNAL(static_cast<size_t>(ret) == (size_with_null - 1), "TODO");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 2) string format_string(const char* msg, ...) {
|
||||
string ret;
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
try {
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
ret = detail::vformat_string(msg, ap);
|
||||
#ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
} catch (...) {
|
||||
va_end(ap);
|
||||
throw;
|
||||
}
|
||||
#endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // FILESYSTEM_FORMAT_STRING_H
|
54
third_party/libcxx/fs/int128_builtins.cpp
vendored
Normal file
54
third_party/libcxx/fs/int128_builtins.cpp
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*===-- int128_builtins.cpp - Implement __muloti4 --------------------------===
|
||||
*
|
||||
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
* See https://llvm.org/LICENSE.txt for license information.
|
||||
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
*
|
||||
* ===----------------------------------------------------------------------===
|
||||
*
|
||||
* This file implements __muloti4, and is stolen from the compiler_rt library.
|
||||
*
|
||||
* FIXME: we steal and re-compile it into filesystem, which uses __int128_t,
|
||||
* and requires this builtin when sanitized. See llvm.org/PR30643
|
||||
*
|
||||
* ===----------------------------------------------------------------------===
|
||||
*/
|
||||
#include <__config>
|
||||
#include <climits>
|
||||
|
||||
#if !defined(_LIBCPP_HAS_NO_INT128)
|
||||
|
||||
extern "C" __attribute__((no_sanitize("undefined"))) _LIBCPP_EXPORTED_FROM_ABI __int128_t
|
||||
__muloti4(__int128_t a, __int128_t b, int* overflow) {
|
||||
const int N = (int)(sizeof(__int128_t) * CHAR_BIT);
|
||||
const __int128_t MIN = (__int128_t)1 << (N - 1);
|
||||
const __int128_t MAX = ~MIN;
|
||||
*overflow = 0;
|
||||
__int128_t result = a * b;
|
||||
if (a == MIN) {
|
||||
if (b != 0 && b != 1)
|
||||
*overflow = 1;
|
||||
return result;
|
||||
}
|
||||
if (b == MIN) {
|
||||
if (a != 0 && a != 1)
|
||||
*overflow = 1;
|
||||
return result;
|
||||
}
|
||||
__int128_t sa = a >> (N - 1);
|
||||
__int128_t abs_a = (a ^ sa) - sa;
|
||||
__int128_t sb = b >> (N - 1);
|
||||
__int128_t abs_b = (b ^ sb) - sb;
|
||||
if (abs_a < 2 || abs_b < 2)
|
||||
return result;
|
||||
if (sa == sb) {
|
||||
if (abs_a > MAX / abs_b)
|
||||
*overflow = 1;
|
||||
} else {
|
||||
if (abs_a > MIN / -abs_b)
|
||||
*overflow = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
965
third_party/libcxx/fs/operations.cpp
vendored
Normal file
965
third_party/libcxx/fs/operations.cpp
vendored
Normal file
|
@ -0,0 +1,965 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__assert>
|
||||
#include <__config>
|
||||
#include <__utility/unreachable.h>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "error.h"
|
||||
#include "file_descriptor.h"
|
||||
#include "path_parser.h"
|
||||
#include "posix_compat.h"
|
||||
#include "time_utils.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <dirent.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/statvfs.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <fcntl.h> /* values for fchmodat */
|
||||
#include <time.h>
|
||||
|
||||
#if __has_include(<sys/sendfile.h>)
|
||||
# include <sys/sendfile.h>
|
||||
# define _LIBCPP_FILESYSTEM_USE_SENDFILE
|
||||
#elif defined(__APPLE__) || __has_include(<copyfile.h>)
|
||||
# include <copyfile.h>
|
||||
# define _LIBCPP_FILESYSTEM_USE_COPYFILE
|
||||
#else
|
||||
# include <fstream>
|
||||
# define _LIBCPP_FILESYSTEM_USE_FSTREAM
|
||||
#endif
|
||||
|
||||
#if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB)
|
||||
# pragma comment(lib, "rt")
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
using detail::capture_errno;
|
||||
using detail::ErrorHandler;
|
||||
using detail::StatT;
|
||||
using detail::TimeSpec;
|
||||
using parser::createView;
|
||||
using parser::PathParser;
|
||||
using parser::string_view_t;
|
||||
|
||||
static path __do_absolute(const path& p, path* cwd, error_code* ec) {
|
||||
if (ec)
|
||||
ec->clear();
|
||||
if (p.is_absolute())
|
||||
return p;
|
||||
*cwd = __current_path(ec);
|
||||
if (ec && *ec)
|
||||
return {};
|
||||
return (*cwd) / p;
|
||||
}
|
||||
|
||||
path __absolute(const path& p, error_code* ec) {
|
||||
path cwd;
|
||||
return __do_absolute(p, &cwd, ec);
|
||||
}
|
||||
|
||||
path __canonical(path const& orig_p, error_code* ec) {
|
||||
path cwd;
|
||||
ErrorHandler<path> err("canonical", ec, &orig_p, &cwd);
|
||||
|
||||
path p = __do_absolute(orig_p, &cwd, ec);
|
||||
#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || defined(_LIBCPP_WIN32API)
|
||||
std::unique_ptr<path::value_type, decltype(&::free)> hold(detail::realpath(p.c_str(), nullptr), &::free);
|
||||
if (hold.get() == nullptr)
|
||||
return err.report(capture_errno());
|
||||
return {hold.get()};
|
||||
#else
|
||||
# if defined(__MVS__) && !defined(PATH_MAX)
|
||||
path::value_type buff[_XOPEN_PATH_MAX + 1];
|
||||
# else
|
||||
path::value_type buff[PATH_MAX + 1];
|
||||
# endif
|
||||
path::value_type* ret;
|
||||
if ((ret = detail::realpath(p.c_str(), buff)) == nullptr)
|
||||
return err.report(capture_errno());
|
||||
return {ret};
|
||||
#endif
|
||||
}
|
||||
|
||||
void __copy(const path& from, const path& to, copy_options options, error_code* ec) {
|
||||
ErrorHandler<void> err("copy", ec, &from, &to);
|
||||
|
||||
const bool sym_status = bool(options & (copy_options::create_symlinks | copy_options::skip_symlinks));
|
||||
|
||||
const bool sym_status2 = bool(options & copy_options::copy_symlinks);
|
||||
|
||||
error_code m_ec1;
|
||||
StatT f_st;
|
||||
const file_status f =
|
||||
sym_status || sym_status2 ? detail::posix_lstat(from, f_st, &m_ec1) : detail::posix_stat(from, f_st, &m_ec1);
|
||||
if (m_ec1)
|
||||
return err.report(m_ec1);
|
||||
|
||||
StatT t_st;
|
||||
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 err.report(m_ec1);
|
||||
|
||||
if (!exists(f) || is_other(f) || is_other(t) || (is_directory(f) && is_regular_file(t)) ||
|
||||
(exists(t) && detail::stat_equivalent(f_st, t_st))) {
|
||||
return err.report(errc::function_not_supported);
|
||||
}
|
||||
|
||||
if (is_symlink(f)) {
|
||||
if (bool(copy_options::skip_symlinks & options)) {
|
||||
// do nothing
|
||||
} else if (not exists(t)) {
|
||||
__copy_symlink(from, to, ec);
|
||||
} else {
|
||||
return err.report(errc::file_exists);
|
||||
}
|
||||
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) && bool(copy_options::create_symlinks & options)) {
|
||||
return err.report(errc::is_a_directory);
|
||||
} else if (is_directory(f) && (bool(copy_options::recursive & options) || copy_options::none == options)) {
|
||||
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;
|
||||
}
|
||||
error_code m_ec2;
|
||||
for (; !m_ec2 && it != directory_iterator(); it.increment(m_ec2)) {
|
||||
__copy(it->path(), to / it->path().filename(), options | copy_options::__in_recursive_copy, ec);
|
||||
if (ec && *ec) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_ec2) {
|
||||
return err.report(m_ec2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
|
||||
bool copy_file_impl(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_FILESYSTEM_USE_COPYFILE)
|
||||
bool copy_file_impl(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;
|
||||
}
|
||||
#elif defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
|
||||
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
|
||||
ifstream in;
|
||||
in.__open(read_fd.fd, ios::binary);
|
||||
if (!in.is_open()) {
|
||||
// This assumes that __open didn't reset the error code.
|
||||
ec = capture_errno();
|
||||
return false;
|
||||
}
|
||||
read_fd.fd = -1;
|
||||
ofstream out;
|
||||
out.__open(write_fd.fd, ios::binary);
|
||||
if (!out.is_open()) {
|
||||
ec = capture_errno();
|
||||
return false;
|
||||
}
|
||||
write_fd.fd = -1;
|
||||
|
||||
if (in.good() && out.good()) {
|
||||
using InIt = istreambuf_iterator<char>;
|
||||
using OutIt = ostreambuf_iterator<char>;
|
||||
InIt bin(in);
|
||||
InIt ein;
|
||||
OutIt bout(out);
|
||||
copy(bin, ein, bout);
|
||||
}
|
||||
if (out.fail() || in.fail()) {
|
||||
ec = make_error_code(errc::io_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
ec.clear();
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
# error "Unknown implementation for copy_file_impl"
|
||||
#endif // copy_file_impl implementation
|
||||
|
||||
} // end anonymous namespace
|
||||
} // end namespace detail
|
||||
|
||||
bool __copy_file(const path& from, const path& to, copy_options options, error_code* ec) {
|
||||
using detail::FileDescriptor;
|
||||
ErrorHandler<bool> err("copy_file", ec, &to, &from);
|
||||
|
||||
error_code m_ec;
|
||||
FileDescriptor from_fd = FileDescriptor::create_with_status(&from, m_ec, O_RDONLY | O_NONBLOCK | O_BINARY);
|
||||
if (m_ec)
|
||||
return err.report(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 err.report(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 err.report(m_ec);
|
||||
|
||||
const bool to_exists = exists(to_st);
|
||||
if (to_exists && !is_regular_file(to_st))
|
||||
return err.report(errc::not_supported);
|
||||
|
||||
if (to_exists && detail::stat_equivalent(from_stat, to_stat_path))
|
||||
return err.report(errc::file_exists);
|
||||
|
||||
if (to_exists && skip_existing)
|
||||
return false;
|
||||
|
||||
bool 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 (from_time.tv_sec == to_time.tv_sec && from_time.tv_nsec <= to_time.tv_nsec)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (!to_exists || overwrite_existing)
|
||||
return true;
|
||||
return err.report(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_BINARY;
|
||||
if (!to_exists)
|
||||
to_open_flags |= O_CREAT;
|
||||
FileDescriptor to_fd = FileDescriptor::create_with_status(&to, m_ec, to_open_flags, from_stat.st_mode);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
|
||||
if (to_exists) {
|
||||
// Check that the file we initially stat'ed is equivalent to the one
|
||||
// we opened.
|
||||
// FIXME: report this better.
|
||||
if (!detail::stat_equivalent(to_stat_path, to_fd.get_stat()))
|
||||
return err.report(errc::bad_file_descriptor);
|
||||
|
||||
// Set the permissions and truncate the file we opened.
|
||||
if (detail::posix_fchmod(to_fd, from_stat, m_ec))
|
||||
return err.report(m_ec);
|
||||
if (detail::posix_ftruncate(to_fd, 0, m_ec))
|
||||
return err.report(m_ec);
|
||||
}
|
||||
|
||||
if (!detail::copy_file_impl(from_fd, to_fd, m_ec)) {
|
||||
// FIXME: Remove the dest file if we failed, and it didn't exist previously.
|
||||
return err.report(m_ec);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void __copy_symlink(const path& existing_symlink, const path& new_symlink, error_code* ec) {
|
||||
const path real_path(__read_symlink(existing_symlink, ec));
|
||||
if (ec && *ec) {
|
||||
return;
|
||||
}
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
error_code local_ec;
|
||||
if (is_directory(real_path, local_ec))
|
||||
__create_directory_symlink(real_path, new_symlink, ec);
|
||||
else
|
||||
#endif
|
||||
__create_symlink(real_path, new_symlink, ec);
|
||||
}
|
||||
|
||||
bool __create_directories(const path& p, error_code* ec) {
|
||||
ErrorHandler<bool> err("create_directories", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
auto const st = detail::posix_stat(p, &m_ec);
|
||||
if (!status_known(st))
|
||||
return err.report(m_ec);
|
||||
else if (is_directory(st))
|
||||
return false;
|
||||
else if (exists(st))
|
||||
return err.report(errc::file_exists);
|
||||
|
||||
const path parent = p.parent_path();
|
||||
if (!parent.empty()) {
|
||||
const file_status parent_st = status(parent, m_ec);
|
||||
if (not status_known(parent_st))
|
||||
return err.report(m_ec);
|
||||
if (not exists(parent_st)) {
|
||||
if (parent == p)
|
||||
return err.report(errc::invalid_argument);
|
||||
__create_directories(parent, ec);
|
||||
if (ec && *ec) {
|
||||
return false;
|
||||
}
|
||||
} else if (not is_directory(parent_st))
|
||||
return err.report(errc::not_a_directory);
|
||||
}
|
||||
bool ret = __create_directory(p, &m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool __create_directory(const path& p, error_code* ec) {
|
||||
ErrorHandler<bool> err("create_directory", ec, &p);
|
||||
|
||||
if (detail::mkdir(p.c_str(), static_cast<int>(perms::all)) == 0)
|
||||
return true;
|
||||
|
||||
if (errno != EEXIST)
|
||||
return err.report(capture_errno());
|
||||
error_code mec = capture_errno();
|
||||
error_code ignored_ec;
|
||||
const file_status st = status(p, ignored_ec);
|
||||
if (!is_directory(st))
|
||||
return err.report(mec);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool __create_directory(path const& p, path const& attributes, error_code* ec) {
|
||||
ErrorHandler<bool> err("create_directory", ec, &p, &attributes);
|
||||
|
||||
StatT attr_stat;
|
||||
error_code mec;
|
||||
file_status st = detail::posix_stat(attributes, attr_stat, &mec);
|
||||
if (!status_known(st))
|
||||
return err.report(mec);
|
||||
if (!is_directory(st))
|
||||
return err.report(errc::not_a_directory, "the specified attribute path is invalid");
|
||||
|
||||
if (detail::mkdir(p.c_str(), attr_stat.st_mode) == 0)
|
||||
return true;
|
||||
|
||||
if (errno != EEXIST)
|
||||
return err.report(capture_errno());
|
||||
|
||||
mec = capture_errno();
|
||||
error_code ignored_ec;
|
||||
st = status(p, ignored_ec);
|
||||
if (!is_directory(st))
|
||||
return err.report(mec);
|
||||
return false;
|
||||
}
|
||||
|
||||
void __create_directory_symlink(path const& from, path const& to, error_code* ec) {
|
||||
ErrorHandler<void> err("create_directory_symlink", ec, &from, &to);
|
||||
if (detail::symlink_dir(from.c_str(), to.c_str()) == -1)
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
|
||||
void __create_hard_link(const path& from, const path& to, error_code* ec) {
|
||||
ErrorHandler<void> err("create_hard_link", ec, &from, &to);
|
||||
if (detail::link(from.c_str(), to.c_str()) == -1)
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
|
||||
void __create_symlink(path const& from, path const& to, error_code* ec) {
|
||||
ErrorHandler<void> err("create_symlink", ec, &from, &to);
|
||||
if (detail::symlink_file(from.c_str(), to.c_str()) == -1)
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
|
||||
path __current_path(error_code* ec) {
|
||||
ErrorHandler<path> err("current_path", ec);
|
||||
|
||||
#if defined(_LIBCPP_WIN32API) || defined(__GLIBC__) || defined(__APPLE__)
|
||||
// Common extension outside of POSIX getcwd() spec, without needing to
|
||||
// preallocate a buffer. Also supported by a number of other POSIX libcs.
|
||||
int size = 0;
|
||||
path::value_type* ptr = nullptr;
|
||||
typedef decltype(&::free) Deleter;
|
||||
Deleter deleter = &::free;
|
||||
#else
|
||||
errno = 0; // Note: POSIX mandates that modifying `errno` is thread-safe.
|
||||
auto size = ::pathconf(".", _PC_PATH_MAX);
|
||||
if (size == -1) {
|
||||
if (errno != 0) {
|
||||
return err.report(capture_errno(), "call to pathconf failed");
|
||||
|
||||
// `pathconf` returns `-1` without an error to indicate no limit.
|
||||
} else {
|
||||
# if defined(__MVS__) && !defined(PATH_MAX)
|
||||
size = _XOPEN_PATH_MAX + 1;
|
||||
# else
|
||||
size = PATH_MAX + 1;
|
||||
# endif
|
||||
}
|
||||
}
|
||||
|
||||
auto buff = unique_ptr<path::value_type[]>(new path::value_type[size + 1]);
|
||||
path::value_type* ptr = buff.get();
|
||||
|
||||
// Preallocated buffer, don't free the buffer in the second unique_ptr
|
||||
// below.
|
||||
struct Deleter {
|
||||
void operator()(void*) const {}
|
||||
};
|
||||
Deleter deleter;
|
||||
#endif
|
||||
|
||||
unique_ptr<path::value_type, Deleter> hold(detail::getcwd(ptr, size), deleter);
|
||||
if (hold.get() == nullptr)
|
||||
return err.report(capture_errno(), "call to getcwd failed");
|
||||
|
||||
return {hold.get()};
|
||||
}
|
||||
|
||||
void __current_path(const path& p, error_code* ec) {
|
||||
ErrorHandler<void> err("current_path", ec, &p);
|
||||
if (detail::chdir(p.c_str()) == -1)
|
||||
err.report(capture_errno());
|
||||
}
|
||||
|
||||
bool __equivalent(const path& p1, const path& p2, error_code* ec) {
|
||||
ErrorHandler<bool> err("equivalent", ec, &p1, &p2);
|
||||
|
||||
error_code ec1, ec2;
|
||||
StatT st1 = {}, st2 = {};
|
||||
auto s1 = detail::posix_stat(p1.native(), st1, &ec1);
|
||||
if (!exists(s1))
|
||||
return err.report(errc::not_supported);
|
||||
auto s2 = detail::posix_stat(p2.native(), st2, &ec2);
|
||||
if (!exists(s2))
|
||||
return err.report(errc::not_supported);
|
||||
|
||||
return detail::stat_equivalent(st1, st2);
|
||||
}
|
||||
|
||||
uintmax_t __file_size(const path& p, error_code* ec) {
|
||||
ErrorHandler<uintmax_t> err("file_size", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
StatT st;
|
||||
file_status fst = detail::posix_stat(p, st, &m_ec);
|
||||
if (!exists(fst) || !is_regular_file(fst)) {
|
||||
errc error_kind = is_directory(fst) ? errc::is_a_directory : errc::not_supported;
|
||||
if (!m_ec)
|
||||
m_ec = make_error_code(error_kind);
|
||||
return err.report(m_ec);
|
||||
}
|
||||
// is_regular_file(p) == true
|
||||
return static_cast<uintmax_t>(st.st_size);
|
||||
}
|
||||
|
||||
uintmax_t __hard_link_count(const path& p, error_code* ec) {
|
||||
ErrorHandler<uintmax_t> err("hard_link_count", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
StatT st;
|
||||
detail::posix_stat(p, st, &m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
return static_cast<uintmax_t>(st.st_nlink);
|
||||
}
|
||||
|
||||
bool __fs_is_empty(const path& p, error_code* ec) {
|
||||
ErrorHandler<bool> err("is_empty", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
StatT pst;
|
||||
auto st = detail::posix_stat(p, pst, &m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
else if (!is_directory(st) && !is_regular_file(st))
|
||||
return err.report(errc::not_supported);
|
||||
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<uintmax_t>(pst.st_size) == 0;
|
||||
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
|
||||
file_time_type __last_write_time(const path& p, error_code* ec) {
|
||||
using namespace chrono;
|
||||
ErrorHandler<file_time_type> err("last_write_time", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
StatT st;
|
||||
detail::posix_stat(p, st, &m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
return detail::__extract_last_write_time(p, st, ec);
|
||||
}
|
||||
|
||||
void __last_write_time(const path& p, file_time_type new_time, error_code* ec) {
|
||||
using detail::fs_time;
|
||||
ErrorHandler<void> err("last_write_time", ec, &p);
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
TimeSpec ts;
|
||||
if (!fs_time::convert_to_timespec(ts, new_time))
|
||||
return err.report(errc::value_too_large);
|
||||
detail::WinHandle h(p.c_str(), FILE_WRITE_ATTRIBUTES, 0);
|
||||
if (!h)
|
||||
return err.report(detail::make_windows_error(GetLastError()));
|
||||
FILETIME last_write = timespec_to_filetime(ts);
|
||||
if (!SetFileTime(h, nullptr, nullptr, &last_write))
|
||||
return err.report(detail::make_windows_error(GetLastError()));
|
||||
#else
|
||||
error_code m_ec;
|
||||
array<TimeSpec, 2> tbuf;
|
||||
# if !defined(_LIBCPP_USE_UTIMENSAT)
|
||||
// This implementation has a race condition between determining the
|
||||
// last access time and attempting to set it to the same value using
|
||||
// ::utimes
|
||||
StatT st;
|
||||
file_status fst = detail::posix_stat(p, st, &m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
tbuf[0] = detail::extract_atime(st);
|
||||
# else
|
||||
tbuf[0].tv_sec = 0;
|
||||
tbuf[0].tv_nsec = UTIME_OMIT;
|
||||
# endif
|
||||
if (!fs_time::convert_to_timespec(tbuf[1], new_time))
|
||||
return err.report(errc::value_too_large);
|
||||
|
||||
detail::set_file_times(p, tbuf, m_ec);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
void __permissions(const path& p, perms prms, perm_options opts, error_code* ec) {
|
||||
ErrorHandler<void> err("permissions", ec, &p);
|
||||
|
||||
auto has_opt = [&](perm_options o) { return bool(o & opts); };
|
||||
const bool resolve_symlinks = !has_opt(perm_options::nofollow);
|
||||
const bool add_perms = has_opt(perm_options::add);
|
||||
const bool remove_perms = has_opt(perm_options::remove);
|
||||
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
||||
(add_perms + remove_perms + has_opt(perm_options::replace)) == 1,
|
||||
"One and only one of the perm_options constants 'replace', 'add', or 'remove' must be present in opts");
|
||||
|
||||
bool set_sym_perms = false;
|
||||
prms &= perms::mask;
|
||||
if (!resolve_symlinks || (add_perms || remove_perms)) {
|
||||
error_code m_ec;
|
||||
file_status st = resolve_symlinks ? detail::posix_stat(p, &m_ec) : detail::posix_lstat(p, &m_ec);
|
||||
set_sym_perms = is_symlink(st);
|
||||
if (m_ec)
|
||||
return err.report(m_ec);
|
||||
// TODO(hardening): double-check this assertion -- it might be a valid (if rare) case when the permissions are
|
||||
// unknown.
|
||||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(st.permissions() != perms::unknown, "Permissions unexpectedly unknown");
|
||||
if (add_perms)
|
||||
prms |= st.permissions();
|
||||
else if (remove_perms)
|
||||
prms = st.permissions() & ~prms;
|
||||
}
|
||||
const auto real_perms = static_cast<detail::ModeT>(prms & perms::mask);
|
||||
|
||||
#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD)
|
||||
const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0;
|
||||
if (detail::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) {
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
#else
|
||||
if (set_sym_perms)
|
||||
return err.report(errc::operation_not_supported);
|
||||
if (::chmod(p.c_str(), real_perms) == -1) {
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
path __read_symlink(const path& p, error_code* ec) {
|
||||
ErrorHandler<path> err("read_symlink", ec, &p);
|
||||
|
||||
#if defined(PATH_MAX) || defined(MAX_SYMLINK_SIZE)
|
||||
struct NullDeleter {
|
||||
void operator()(void*) const {}
|
||||
};
|
||||
# ifdef MAX_SYMLINK_SIZE
|
||||
const size_t size = MAX_SYMLINK_SIZE + 1;
|
||||
# else
|
||||
const size_t size = PATH_MAX + 1;
|
||||
# endif
|
||||
path::value_type stack_buff[size];
|
||||
auto buff = std::unique_ptr<path::value_type[], NullDeleter>(stack_buff);
|
||||
#else
|
||||
StatT sb;
|
||||
if (detail::lstat(p.c_str(), &sb) == -1) {
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
const size_t size = sb.st_size + 1;
|
||||
auto buff = unique_ptr<path::value_type[]>(new path::value_type[size]);
|
||||
#endif
|
||||
detail::SSizeT ret;
|
||||
if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1)
|
||||
return err.report(capture_errno());
|
||||
// Note that `ret` returning `0` would work, resulting in a valid empty string being returned.
|
||||
if (static_cast<size_t>(ret) >= size)
|
||||
return err.report(errc::value_too_large);
|
||||
buff[ret] = 0;
|
||||
return {buff.get()};
|
||||
}
|
||||
|
||||
bool __remove(const path& p, error_code* ec) {
|
||||
ErrorHandler<bool> err("remove", ec, &p);
|
||||
if (detail::remove(p.c_str()) == -1) {
|
||||
if (errno != ENOENT)
|
||||
err.report(capture_errno());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// We currently have two implementations of `__remove_all`. The first one is general and
|
||||
// used on platforms where we don't have access to the `openat()` family of POSIX functions.
|
||||
// That implementation uses `directory_iterator`, however it is vulnerable to some race
|
||||
// conditions, see https://reviews.llvm.org/D118134 for details.
|
||||
//
|
||||
// The second implementation is used on platforms where `openat()` & friends are available,
|
||||
// and it threads file descriptors through recursive calls to avoid such race conditions.
|
||||
#if defined(_LIBCPP_WIN32API) || defined(__MVS__)
|
||||
# define REMOVE_ALL_USE_DIRECTORY_ITERATOR
|
||||
#endif
|
||||
|
||||
#if defined(REMOVE_ALL_USE_DIRECTORY_ITERATOR)
|
||||
|
||||
namespace {
|
||||
|
||||
uintmax_t remove_all_impl(path const& p, error_code& ec) {
|
||||
const auto npos = static_cast<uintmax_t>(-1);
|
||||
const file_status st = __symlink_status(p, &ec);
|
||||
if (ec)
|
||||
return npos;
|
||||
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
|
||||
|
||||
uintmax_t __remove_all(const path& p, error_code* ec) {
|
||||
ErrorHandler<uintmax_t> err("remove_all", ec, &p);
|
||||
|
||||
error_code mec;
|
||||
auto count = remove_all_impl(p, mec);
|
||||
if (mec) {
|
||||
if (mec == errc::no_such_file_or_directory)
|
||||
return 0;
|
||||
return err.report(mec);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#else // !REMOVE_ALL_USE_DIRECTORY_ITERATOR
|
||||
|
||||
namespace {
|
||||
|
||||
template <class Cleanup>
|
||||
struct scope_exit {
|
||||
explicit scope_exit(Cleanup const& cleanup) : cleanup_(cleanup) {}
|
||||
|
||||
~scope_exit() { cleanup_(); }
|
||||
|
||||
private:
|
||||
Cleanup cleanup_;
|
||||
};
|
||||
_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(scope_exit);
|
||||
|
||||
uintmax_t remove_all_impl(int parent_directory, const path& p, error_code& ec) {
|
||||
// First, try to open the path as a directory.
|
||||
const int options = O_CLOEXEC | O_RDONLY | O_DIRECTORY | O_NOFOLLOW;
|
||||
int fd = ::openat(parent_directory, p.c_str(), options);
|
||||
if (fd != -1) {
|
||||
// If that worked, iterate over the contents of the directory and
|
||||
// remove everything in it, recursively.
|
||||
DIR* stream = ::fdopendir(fd);
|
||||
if (stream == nullptr) {
|
||||
::close(fd);
|
||||
ec = detail::capture_errno();
|
||||
return 0;
|
||||
}
|
||||
// Note: `::closedir` will also close the associated file descriptor, so
|
||||
// there should be no call to `close(fd)`.
|
||||
scope_exit close_stream([=] { ::closedir(stream); });
|
||||
|
||||
uintmax_t count = 0;
|
||||
while (true) {
|
||||
auto [str, type] = detail::posix_readdir(stream, ec);
|
||||
static_assert(std::is_same_v<decltype(str), std::string_view>);
|
||||
if (str == "." || str == "..") {
|
||||
continue;
|
||||
} else if (ec || str.empty()) {
|
||||
break; // we're done iterating through the directory
|
||||
} else {
|
||||
count += remove_all_impl(fd, str, ec);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, remove the now-empty directory itself.
|
||||
if (::unlinkat(parent_directory, p.c_str(), AT_REMOVEDIR) == -1) {
|
||||
ec = detail::capture_errno();
|
||||
return count;
|
||||
}
|
||||
|
||||
return count + 1; // the contents of the directory + the directory itself
|
||||
}
|
||||
|
||||
ec = detail::capture_errno();
|
||||
|
||||
// If we failed to open `p` because it didn't exist, it's not an
|
||||
// error -- it might have moved or have been deleted already.
|
||||
if (ec == errc::no_such_file_or_directory) {
|
||||
ec.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If opening `p` failed because it wasn't a directory, remove it as
|
||||
// a normal file instead. Note that `openat()` can return either ENOTDIR
|
||||
// or ELOOP depending on the exact reason of the failure. On FreeBSD it
|
||||
// may return EMLINK instead of ELOOP, contradicting POSIX.
|
||||
if (ec == errc::not_a_directory || ec == errc::too_many_symbolic_link_levels || ec == errc::too_many_links) {
|
||||
ec.clear();
|
||||
if (::unlinkat(parent_directory, p.c_str(), /* flags = */ 0) == -1) {
|
||||
ec = detail::capture_errno();
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Otherwise, it's a real error -- we don't remove anything.
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
uintmax_t __remove_all(const path& p, error_code* ec) {
|
||||
ErrorHandler<uintmax_t> err("remove_all", ec, &p);
|
||||
error_code mec;
|
||||
uintmax_t count = remove_all_impl(AT_FDCWD, p, mec);
|
||||
if (mec)
|
||||
return err.report(mec);
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif // REMOVE_ALL_USE_DIRECTORY_ITERATOR
|
||||
|
||||
void __rename(const path& from, const path& to, error_code* ec) {
|
||||
ErrorHandler<void> err("rename", ec, &from, &to);
|
||||
if (detail::rename(from.c_str(), to.c_str()) == -1)
|
||||
err.report(capture_errno());
|
||||
}
|
||||
|
||||
void __resize_file(const path& p, uintmax_t size, error_code* ec) {
|
||||
ErrorHandler<void> err("resize_file", ec, &p);
|
||||
if (detail::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1)
|
||||
return err.report(capture_errno());
|
||||
}
|
||||
|
||||
space_info __space(const path& p, error_code* ec) {
|
||||
ErrorHandler<void> err("space", ec, &p);
|
||||
space_info si;
|
||||
detail::StatVFS m_svfs = {};
|
||||
if (detail::statvfs(p.c_str(), &m_svfs) == -1) {
|
||||
err.report(capture_errno());
|
||||
si.capacity = si.free = si.available = static_cast<uintmax_t>(-1);
|
||||
return si;
|
||||
}
|
||||
// Multiply with overflow checking.
|
||||
auto do_mult = [&](uintmax_t& out, uintmax_t other) {
|
||||
out = other * m_svfs.f_frsize;
|
||||
if (other == 0 || out / other != m_svfs.f_frsize)
|
||||
out = static_cast<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, error_code* ec) { return detail::posix_stat(p, ec); }
|
||||
|
||||
file_status __symlink_status(const path& p, error_code* ec) { return detail::posix_lstat(p, ec); }
|
||||
|
||||
path __temp_directory_path(error_code* ec) {
|
||||
ErrorHandler<path> err("temp_directory_path", ec);
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
wchar_t buf[MAX_PATH];
|
||||
DWORD retval = GetTempPathW(MAX_PATH, buf);
|
||||
if (!retval)
|
||||
return err.report(detail::make_windows_error(GetLastError()));
|
||||
if (retval > MAX_PATH)
|
||||
return err.report(errc::filename_too_long);
|
||||
// GetTempPathW returns a path with a trailing slash, which we
|
||||
// shouldn't include for consistency.
|
||||
if (buf[retval - 1] == L'\\')
|
||||
buf[retval - 1] = L'\0';
|
||||
path p(buf);
|
||||
#else
|
||||
const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
|
||||
const char* ret = nullptr;
|
||||
|
||||
for (auto& ep : env_paths)
|
||||
if ((ret = getenv(ep)))
|
||||
break;
|
||||
if (ret == nullptr) {
|
||||
# if defined(__ANDROID__)
|
||||
ret = "/data/local/tmp";
|
||||
# else
|
||||
ret = "/tmp";
|
||||
# endif
|
||||
}
|
||||
|
||||
path p(ret);
|
||||
#endif
|
||||
error_code m_ec;
|
||||
file_status st = detail::posix_stat(p, &m_ec);
|
||||
if (!status_known(st))
|
||||
return err.report(m_ec, "cannot access path " PATH_CSTR_FMT, p.c_str());
|
||||
|
||||
if (!exists(st) || !is_directory(st))
|
||||
return err.report(errc::not_a_directory, "path " PATH_CSTR_FMT " is not a directory", p.c_str());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
path __weakly_canonical(const path& p, error_code* ec) {
|
||||
ErrorHandler<path> err("weakly_canonical", ec, &p);
|
||||
|
||||
if (p.empty())
|
||||
return __canonical("", ec);
|
||||
|
||||
path result;
|
||||
path tmp;
|
||||
tmp.__reserve(p.native().size());
|
||||
auto PP = PathParser::CreateEnd(p.native());
|
||||
--PP;
|
||||
vector<string_view_t> DNEParts;
|
||||
|
||||
error_code m_ec;
|
||||
while (PP.State_ != PathParser::PS_BeforeBegin) {
|
||||
tmp.assign(createView(p.native().data(), &PP.RawEntry.back()));
|
||||
file_status st = __status(tmp, &m_ec);
|
||||
if (!status_known(st)) {
|
||||
return err.report(m_ec);
|
||||
} else if (exists(st)) {
|
||||
result = __canonical(tmp, &m_ec);
|
||||
if (m_ec) {
|
||||
return err.report(m_ec);
|
||||
}
|
||||
break;
|
||||
}
|
||||
DNEParts.push_back(*PP);
|
||||
--PP;
|
||||
}
|
||||
if (PP.State_ == PathParser::PS_BeforeBegin) {
|
||||
result = __canonical("", &m_ec);
|
||||
if (m_ec) {
|
||||
return err.report(m_ec);
|
||||
}
|
||||
}
|
||||
if (DNEParts.empty())
|
||||
return result;
|
||||
for (auto It = DNEParts.rbegin(); It != DNEParts.rend(); ++It)
|
||||
result /= *It;
|
||||
return result.lexically_normal();
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
441
third_party/libcxx/fs/path.cpp
vendored
Normal file
441
third_party/libcxx/fs/path.cpp
vendored
Normal file
|
@ -0,0 +1,441 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <__config>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "error.h"
|
||||
#include "path_parser.h"
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
using detail::ErrorHandler;
|
||||
using parser::createView;
|
||||
using parser::PathParser;
|
||||
using parser::string_view_t;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// path definitions
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
constexpr path::value_type path::preferred_separator;
|
||||
|
||||
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_ += PATHSTR(".");
|
||||
}
|
||||
__pn_.append(replacement.__pn_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// path.decompose
|
||||
|
||||
string_view_t path::__root_name() const {
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
if (PP.State_ == PathParser::PS_InRootName)
|
||||
return *PP;
|
||||
return {};
|
||||
}
|
||||
|
||||
string_view_t path::__root_directory() const {
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
if (PP.State_ == PathParser::PS_InRootName)
|
||||
++PP;
|
||||
if (PP.State_ == PathParser::PS_InRootDir)
|
||||
return *PP;
|
||||
return {};
|
||||
}
|
||||
|
||||
string_view_t path::__root_path_raw() const {
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
if (PP.State_ == PathParser::PS_InRootName) {
|
||||
auto NextCh = PP.peek();
|
||||
if (NextCh && isSeparator(*NextCh)) {
|
||||
++PP;
|
||||
return createView(__pn_.data(), &PP.RawEntry.back());
|
||||
}
|
||||
return PP.RawEntry;
|
||||
}
|
||||
if (PP.State_ == PathParser::PS_InRootDir)
|
||||
return *PP;
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool ConsumeRootName(PathParser* PP) {
|
||||
static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect");
|
||||
while (PP->State_ <= PathParser::PS_InRootName)
|
||||
++(*PP);
|
||||
return PP->State_ == PathParser::PS_AtEnd;
|
||||
}
|
||||
|
||||
static bool ConsumeRootDir(PathParser* PP) {
|
||||
static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3,
|
||||
"Values for enums are incorrect");
|
||||
while (PP->State_ <= PathParser::PS_InRootDir)
|
||||
++(*PP);
|
||||
return PP->State_ == PathParser::PS_AtEnd;
|
||||
}
|
||||
|
||||
string_view_t path::__relative_path() const {
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
if (ConsumeRootDir(&PP))
|
||||
return {};
|
||||
return createView(PP.RawEntry.data(), &__pn_.back());
|
||||
}
|
||||
|
||||
string_view_t path::__parent_path() const {
|
||||
if (empty())
|
||||
return {};
|
||||
// Determine if we have a root path but not a relative path. In that case
|
||||
// return *this.
|
||||
{
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
if (ConsumeRootDir(&PP))
|
||||
return __pn_;
|
||||
}
|
||||
// Otherwise remove a single element from the end of the path, and return
|
||||
// a string representing that path
|
||||
{
|
||||
auto PP = PathParser::CreateEnd(__pn_);
|
||||
--PP;
|
||||
if (PP.RawEntry.data() == __pn_.data())
|
||||
return {};
|
||||
--PP;
|
||||
return createView(__pn_.data(), &PP.RawEntry.back());
|
||||
}
|
||||
}
|
||||
|
||||
string_view_t path::__filename() const {
|
||||
if (empty())
|
||||
return {};
|
||||
{
|
||||
PathParser PP = PathParser::CreateBegin(__pn_);
|
||||
if (ConsumeRootDir(&PP))
|
||||
return {};
|
||||
}
|
||||
return *(--PathParser::CreateEnd(__pn_));
|
||||
}
|
||||
|
||||
string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; }
|
||||
|
||||
string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// path.gen
|
||||
|
||||
enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };
|
||||
|
||||
static PathPartKind ClassifyPathPart(string_view_t Part) {
|
||||
if (Part.empty())
|
||||
return PK_TrailingSep;
|
||||
if (Part == PATHSTR("."))
|
||||
return PK_Dot;
|
||||
if (Part == PATHSTR(".."))
|
||||
return PK_DotDot;
|
||||
if (Part == PATHSTR("/"))
|
||||
return PK_RootSep;
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
if (Part == PATHSTR("\\"))
|
||||
return PK_RootSep;
|
||||
#endif
|
||||
return PK_Filename;
|
||||
}
|
||||
|
||||
path path::lexically_normal() const {
|
||||
if (__pn_.empty())
|
||||
return *this;
|
||||
|
||||
using PartKindPair = pair<string_view_t, PathPartKind>;
|
||||
vector<PartKindPair> Parts;
|
||||
// Guess as to how many elements the path has to avoid reallocating.
|
||||
Parts.reserve(32);
|
||||
|
||||
// Track the total size of the parts as we collect them. This allows the
|
||||
// resulting path to reserve the correct amount of memory.
|
||||
size_t NewPathSize = 0;
|
||||
auto AddPart = [&](PathPartKind K, string_view_t P) {
|
||||
NewPathSize += P.size();
|
||||
Parts.emplace_back(P, K);
|
||||
};
|
||||
auto LastPartKind = [&]() {
|
||||
if (Parts.empty())
|
||||
return PK_None;
|
||||
return Parts.back().second;
|
||||
};
|
||||
|
||||
bool MaybeNeedTrailingSep = false;
|
||||
// Build a stack containing the remaining elements of the path, popping off
|
||||
// elements which occur before a '..' entry.
|
||||
for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {
|
||||
auto Part = *PP;
|
||||
PathPartKind Kind = ClassifyPathPart(Part);
|
||||
switch (Kind) {
|
||||
case PK_Filename:
|
||||
case PK_RootSep: {
|
||||
// Add all non-dot and non-dot-dot elements to the stack of elements.
|
||||
AddPart(Kind, Part);
|
||||
MaybeNeedTrailingSep = false;
|
||||
break;
|
||||
}
|
||||
case PK_DotDot: {
|
||||
// Only push a ".." element if there are no elements preceding the "..",
|
||||
// or if the preceding element is itself "..".
|
||||
auto LastKind = LastPartKind();
|
||||
if (LastKind == PK_Filename) {
|
||||
NewPathSize -= Parts.back().first.size();
|
||||
Parts.pop_back();
|
||||
} else if (LastKind != PK_RootSep)
|
||||
AddPart(PK_DotDot, PATHSTR(".."));
|
||||
MaybeNeedTrailingSep = LastKind == PK_Filename;
|
||||
break;
|
||||
}
|
||||
case PK_Dot:
|
||||
case PK_TrailingSep: {
|
||||
MaybeNeedTrailingSep = true;
|
||||
break;
|
||||
}
|
||||
case PK_None:
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
}
|
||||
// [fs.path.generic]p6.8: If the path is empty, add a dot.
|
||||
if (Parts.empty())
|
||||
return PATHSTR(".");
|
||||
|
||||
// [fs.path.generic]p6.7: If the last filename is dot-dot, remove any
|
||||
// trailing directory-separator.
|
||||
bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;
|
||||
|
||||
path Result;
|
||||
Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);
|
||||
for (auto& PK : Parts)
|
||||
Result /= PK.first;
|
||||
|
||||
if (NeedTrailingSep)
|
||||
Result /= PATHSTR("");
|
||||
|
||||
Result.make_preferred();
|
||||
return Result;
|
||||
}
|
||||
|
||||
static int DetermineLexicalElementCount(PathParser PP) {
|
||||
int Count = 0;
|
||||
for (; PP; ++PP) {
|
||||
auto Elem = *PP;
|
||||
if (Elem == PATHSTR(".."))
|
||||
--Count;
|
||||
else if (Elem != PATHSTR(".") && Elem != PATHSTR(""))
|
||||
++Count;
|
||||
}
|
||||
return Count;
|
||||
}
|
||||
|
||||
path path::lexically_relative(const path& base) const {
|
||||
{ // perform root-name/root-directory mismatch checks
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
auto PPBase = PathParser::CreateBegin(base.__pn_);
|
||||
auto CheckIterMismatchAtBase = [&]() {
|
||||
return PP.State_ != PPBase.State_ && (PP.inRootPath() || PPBase.inRootPath());
|
||||
};
|
||||
if (PP.inRootName() && PPBase.inRootName()) {
|
||||
if (*PP != *PPBase)
|
||||
return {};
|
||||
} else if (CheckIterMismatchAtBase())
|
||||
return {};
|
||||
|
||||
if (PP.inRootPath())
|
||||
++PP;
|
||||
if (PPBase.inRootPath())
|
||||
++PPBase;
|
||||
if (CheckIterMismatchAtBase())
|
||||
return {};
|
||||
}
|
||||
|
||||
// Find the first mismatching element
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
auto PPBase = PathParser::CreateBegin(base.__pn_);
|
||||
while (PP && PPBase && PP.State_ == PPBase.State_ && *PP == *PPBase) {
|
||||
++PP;
|
||||
++PPBase;
|
||||
}
|
||||
|
||||
// If there is no mismatch, return ".".
|
||||
if (!PP && !PPBase)
|
||||
return ".";
|
||||
|
||||
// Otherwise, determine the number of elements, 'n', which are not dot or
|
||||
// dot-dot minus the number of dot-dot elements.
|
||||
int ElemCount = DetermineLexicalElementCount(PPBase);
|
||||
if (ElemCount < 0)
|
||||
return {};
|
||||
|
||||
// if n == 0 and (a == end() || a->empty()), returns path("."); otherwise
|
||||
if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR("")))
|
||||
return PATHSTR(".");
|
||||
|
||||
// return a path constructed with 'n' dot-dot elements, followed by the
|
||||
// elements of '*this' after the mismatch.
|
||||
path Result;
|
||||
// FIXME: Reserve enough room in Result that it won't have to re-allocate.
|
||||
while (ElemCount--)
|
||||
Result /= PATHSTR("..");
|
||||
for (; PP; ++PP)
|
||||
Result /= *PP;
|
||||
return Result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// path.comparisons
|
||||
static int CompareRootName(PathParser* LHS, PathParser* RHS) {
|
||||
if (!LHS->inRootName() && !RHS->inRootName())
|
||||
return 0;
|
||||
|
||||
auto GetRootName = [](PathParser* Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); };
|
||||
int res = GetRootName(LHS).compare(GetRootName(RHS));
|
||||
ConsumeRootName(LHS);
|
||||
ConsumeRootName(RHS);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CompareRootDir(PathParser* LHS, PathParser* RHS) {
|
||||
if (!LHS->inRootDir() && RHS->inRootDir())
|
||||
return -1;
|
||||
else if (LHS->inRootDir() && !RHS->inRootDir())
|
||||
return 1;
|
||||
else {
|
||||
ConsumeRootDir(LHS);
|
||||
ConsumeRootDir(RHS);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int CompareRelative(PathParser* LHSPtr, PathParser* RHSPtr) {
|
||||
auto& LHS = *LHSPtr;
|
||||
auto& RHS = *RHSPtr;
|
||||
|
||||
int res;
|
||||
while (LHS && RHS) {
|
||||
if ((res = (*LHS).compare(*RHS)) != 0)
|
||||
return res;
|
||||
++LHS;
|
||||
++RHS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int CompareEndState(PathParser* LHS, PathParser* RHS) {
|
||||
if (LHS->atEnd() && !RHS->atEnd())
|
||||
return -1;
|
||||
else if (!LHS->atEnd() && RHS->atEnd())
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int path::__compare(string_view_t __s) const {
|
||||
auto LHS = PathParser::CreateBegin(__pn_);
|
||||
auto RHS = PathParser::CreateBegin(__s);
|
||||
int res;
|
||||
|
||||
if ((res = CompareRootName(&LHS, &RHS)) != 0)
|
||||
return res;
|
||||
|
||||
if ((res = CompareRootDir(&LHS, &RHS)) != 0)
|
||||
return res;
|
||||
|
||||
if ((res = CompareRelative(&LHS, &RHS)) != 0)
|
||||
return res;
|
||||
|
||||
return CompareEndState(&LHS, &RHS);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// path.nonmembers
|
||||
size_t hash_value(const path& __p) noexcept {
|
||||
auto PP = PathParser::CreateBegin(__p.native());
|
||||
size_t hash_value = 0;
|
||||
hash<string_view_t> hasher;
|
||||
while (PP) {
|
||||
hash_value = __hash_combine(hash_value, hasher(*PP));
|
||||
++PP;
|
||||
}
|
||||
return hash_value;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// path.itr
|
||||
path::iterator path::begin() const {
|
||||
auto PP = PathParser::CreateBegin(__pn_);
|
||||
iterator it;
|
||||
it.__path_ptr_ = this;
|
||||
it.__state_ = static_cast<path::iterator::_ParserState>(PP.State_);
|
||||
it.__entry_ = PP.RawEntry;
|
||||
it.__stashed_elem_.__assign_view(*PP);
|
||||
return it;
|
||||
}
|
||||
|
||||
path::iterator path::end() const {
|
||||
iterator it{};
|
||||
it.__state_ = path::iterator::_AtEnd;
|
||||
it.__path_ptr_ = this;
|
||||
return it;
|
||||
}
|
||||
|
||||
path::iterator& path::iterator::__increment() {
|
||||
PathParser PP(__path_ptr_->native(), __entry_, __state_);
|
||||
++PP;
|
||||
__state_ = static_cast<_ParserState>(PP.State_);
|
||||
__entry_ = PP.RawEntry;
|
||||
__stashed_elem_.__assign_view(*PP);
|
||||
return *this;
|
||||
}
|
||||
|
||||
path::iterator& path::iterator::__decrement() {
|
||||
PathParser PP(__path_ptr_->native(), __entry_, __state_);
|
||||
--PP;
|
||||
__state_ = static_cast<_ParserState>(PP.State_);
|
||||
__entry_ = PP.RawEntry;
|
||||
__stashed_elem_.__assign_view(*PP);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Windows path conversions
|
||||
size_t __wide_to_char(const wstring& str, char* out, size_t outlen) {
|
||||
if (str.empty())
|
||||
return 0;
|
||||
ErrorHandler<size_t> err("__wide_to_char", nullptr);
|
||||
UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
|
||||
BOOL used_default = FALSE;
|
||||
int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default);
|
||||
if (ret <= 0 || used_default)
|
||||
return err.report(errc::illegal_byte_sequence);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t __char_to_wide(const string& str, wchar_t* out, size_t outlen) {
|
||||
if (str.empty())
|
||||
return 0;
|
||||
ErrorHandler<size_t> err("__char_to_wide", nullptr);
|
||||
UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
|
||||
int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen);
|
||||
if (ret <= 0)
|
||||
return err.report(errc::illegal_byte_sequence);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
352
third_party/libcxx/fs/path_parser.h
vendored
Normal file
352
third_party/libcxx/fs/path_parser.h
vendored
Normal file
|
@ -0,0 +1,352 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef PATH_PARSER_H
|
||||
#define PATH_PARSER_H
|
||||
|
||||
#include <__config>
|
||||
#include <__utility/unreachable.h>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
|
||||
#include "format_string.h"
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
inline bool isSeparator(path::value_type C) {
|
||||
if (C == '/')
|
||||
return true;
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
if (C == '\\')
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool isDriveLetter(path::value_type C) { return (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z'); }
|
||||
|
||||
namespace parser {
|
||||
|
||||
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 = path::iterator::_BeforeBegin,
|
||||
PS_InRootName = path::iterator::_InRootName,
|
||||
PS_InRootDir = path::iterator::_InRootDir,
|
||||
PS_InFilenames = path::iterator::_InFilenames,
|
||||
PS_InTrailingSep = path::iterator::_InTrailingSep,
|
||||
PS_AtEnd = path::iterator::_AtEnd
|
||||
};
|
||||
|
||||
const string_view_t Path;
|
||||
string_view_t RawEntry;
|
||||
ParserState State_;
|
||||
|
||||
private:
|
||||
PathParser(string_view_t P, ParserState State) noexcept : Path(P), State_(State) {}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
static PathParser CreateBegin(string_view_t P) noexcept {
|
||||
PathParser PP(P, PS_BeforeBegin);
|
||||
PP.increment();
|
||||
return PP;
|
||||
}
|
||||
|
||||
static PathParser CreateEnd(string_view_t P) noexcept {
|
||||
PathParser PP(P, PS_AtEnd);
|
||||
return PP;
|
||||
}
|
||||
|
||||
PosPtr peek() const noexcept {
|
||||
auto TkEnd = getNextTokenStartPos();
|
||||
auto End = getAfterBack();
|
||||
return TkEnd == End ? nullptr : TkEnd;
|
||||
}
|
||||
|
||||
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 = consumeRootName(Start, End);
|
||||
if (TkEnd)
|
||||
return makeState(PS_InRootName, Start, TkEnd);
|
||||
}
|
||||
_LIBCPP_FALLTHROUGH();
|
||||
case PS_InRootName: {
|
||||
PosPtr TkEnd = consumeAllSeparators(Start, End);
|
||||
if (TkEnd)
|
||||
return makeState(PS_InRootDir, Start, TkEnd);
|
||||
else
|
||||
return makeState(PS_InFilenames, Start, consumeName(Start, End));
|
||||
}
|
||||
case PS_InRootDir:
|
||||
return makeState(PS_InFilenames, Start, consumeName(Start, End));
|
||||
|
||||
case PS_InFilenames: {
|
||||
PosPtr SepEnd = consumeAllSeparators(Start, End);
|
||||
if (SepEnd != End) {
|
||||
PosPtr TkEnd = consumeName(SepEnd, End);
|
||||
if (TkEnd)
|
||||
return makeState(PS_InFilenames, SepEnd, TkEnd);
|
||||
}
|
||||
return makeState(PS_InTrailingSep, Start, SepEnd);
|
||||
}
|
||||
|
||||
case PS_InTrailingSep:
|
||||
return makeState(PS_AtEnd);
|
||||
|
||||
case PS_AtEnd:
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void decrement() noexcept {
|
||||
const PosPtr REnd = getBeforeFront();
|
||||
const PosPtr RStart = getCurrentTokenStartPos() - 1;
|
||||
if (RStart == REnd) // we're decrementing the begin
|
||||
return makeState(PS_BeforeBegin);
|
||||
|
||||
switch (State_) {
|
||||
case PS_AtEnd: {
|
||||
// Try to consume a trailing separator or root directory first.
|
||||
if (PosPtr SepEnd = consumeAllSeparators(RStart, REnd)) {
|
||||
if (SepEnd == REnd)
|
||||
return makeState(PS_InRootDir, Path.data(), RStart + 1);
|
||||
PosPtr TkStart = consumeRootName(SepEnd, REnd);
|
||||
if (TkStart == REnd)
|
||||
return makeState(PS_InRootDir, RStart, RStart + 1);
|
||||
return makeState(PS_InTrailingSep, SepEnd + 1, RStart + 1);
|
||||
} else {
|
||||
PosPtr TkStart = consumeRootName(RStart, REnd);
|
||||
if (TkStart == REnd)
|
||||
return makeState(PS_InRootName, TkStart + 1, RStart + 1);
|
||||
TkStart = consumeName(RStart, REnd);
|
||||
return makeState(PS_InFilenames, TkStart + 1, RStart + 1);
|
||||
}
|
||||
}
|
||||
case PS_InTrailingSep:
|
||||
return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1);
|
||||
case PS_InFilenames: {
|
||||
PosPtr SepEnd = consumeAllSeparators(RStart, REnd);
|
||||
if (SepEnd == REnd)
|
||||
return makeState(PS_InRootDir, Path.data(), RStart + 1);
|
||||
PosPtr TkStart = consumeRootName(SepEnd ? SepEnd : RStart, REnd);
|
||||
if (TkStart == REnd) {
|
||||
if (SepEnd)
|
||||
return makeState(PS_InRootDir, SepEnd + 1, RStart + 1);
|
||||
return makeState(PS_InRootName, TkStart + 1, RStart + 1);
|
||||
}
|
||||
TkStart = consumeName(SepEnd, REnd);
|
||||
return makeState(PS_InFilenames, TkStart + 1, SepEnd + 1);
|
||||
}
|
||||
case PS_InRootDir:
|
||||
return makeState(PS_InRootName, Path.data(), RStart + 1);
|
||||
case PS_InRootName:
|
||||
case PS_BeforeBegin:
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/// \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 PATHSTR("");
|
||||
case PS_InRootDir:
|
||||
if (RawEntry[0] == '\\')
|
||||
return PATHSTR("\\");
|
||||
else
|
||||
return PATHSTR("/");
|
||||
case PS_InTrailingSep:
|
||||
return PATHSTR("");
|
||||
case PS_InRootName:
|
||||
case PS_InFilenames:
|
||||
return RawEntry;
|
||||
}
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return State_ != PS_BeforeBegin && State_ != PS_AtEnd; }
|
||||
|
||||
PathParser& operator++() noexcept {
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathParser& operator--() noexcept {
|
||||
decrement();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool atEnd() const noexcept { return State_ == PS_AtEnd; }
|
||||
|
||||
bool inRootDir() const noexcept { return State_ == PS_InRootDir; }
|
||||
|
||||
bool inRootName() const noexcept { return State_ == PS_InRootName; }
|
||||
|
||||
bool inRootPath() const noexcept { return inRootName() || inRootDir(); }
|
||||
|
||||
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 = {};
|
||||
}
|
||||
|
||||
PosPtr getAfterBack() const noexcept { return Path.data() + Path.size(); }
|
||||
|
||||
PosPtr getBeforeFront() const noexcept { return Path.data() - 1; }
|
||||
|
||||
/// \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();
|
||||
}
|
||||
__libcpp_unreachable();
|
||||
}
|
||||
|
||||
/// \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();
|
||||
}
|
||||
|
||||
// Consume all consecutive separators.
|
||||
PosPtr consumeAllSeparators(PosPtr P, PosPtr End) const noexcept {
|
||||
if (P == nullptr || P == End || !isSeparator(*P))
|
||||
return nullptr;
|
||||
const int Inc = P < End ? 1 : -1;
|
||||
P += Inc;
|
||||
while (P != End && isSeparator(*P))
|
||||
P += Inc;
|
||||
return P;
|
||||
}
|
||||
|
||||
// Consume exactly N separators, or return nullptr.
|
||||
PosPtr consumeNSeparators(PosPtr P, PosPtr End, int N) const noexcept {
|
||||
PosPtr Ret = consumeAllSeparators(P, End);
|
||||
if (Ret == nullptr)
|
||||
return nullptr;
|
||||
if (P < End) {
|
||||
if (Ret == P + N)
|
||||
return Ret;
|
||||
} else {
|
||||
if (Ret == P - N)
|
||||
return Ret;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PosPtr consumeName(PosPtr P, PosPtr End) const noexcept {
|
||||
PosPtr Start = P;
|
||||
if (P == nullptr || P == End || isSeparator(*P))
|
||||
return nullptr;
|
||||
const int Inc = P < End ? 1 : -1;
|
||||
P += Inc;
|
||||
while (P != End && !isSeparator(*P))
|
||||
P += Inc;
|
||||
if (P == End && Inc < 0) {
|
||||
// Iterating backwards and consumed all the rest of the input.
|
||||
// Check if the start of the string would have been considered
|
||||
// a root name.
|
||||
PosPtr RootEnd = consumeRootName(End + 1, Start);
|
||||
if (RootEnd)
|
||||
return RootEnd - 1;
|
||||
}
|
||||
return P;
|
||||
}
|
||||
|
||||
PosPtr consumeDriveLetter(PosPtr P, PosPtr End) const noexcept {
|
||||
if (P == End)
|
||||
return nullptr;
|
||||
if (P < End) {
|
||||
if (P + 1 == End || !isDriveLetter(P[0]) || P[1] != ':')
|
||||
return nullptr;
|
||||
return P + 2;
|
||||
} else {
|
||||
if (P - 1 == End || !isDriveLetter(P[-1]) || P[0] != ':')
|
||||
return nullptr;
|
||||
return P - 2;
|
||||
}
|
||||
}
|
||||
|
||||
PosPtr consumeNetworkRoot(PosPtr P, PosPtr End) const noexcept {
|
||||
if (P == End)
|
||||
return nullptr;
|
||||
if (P < End)
|
||||
return consumeName(consumeNSeparators(P, End, 2), End);
|
||||
else
|
||||
return consumeNSeparators(consumeName(P, End), End, 2);
|
||||
}
|
||||
|
||||
PosPtr consumeRootName(PosPtr P, PosPtr End) const noexcept {
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
if (PosPtr Ret = consumeDriveLetter(P, End))
|
||||
return Ret;
|
||||
if (PosPtr Ret = consumeNetworkRoot(P, End))
|
||||
return Ret;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
inline string_view_pair separate_filename(string_view_t const& s) {
|
||||
if (s == PATHSTR(".") || s == PATHSTR("..") || s.empty())
|
||||
return string_view_pair{s, PATHSTR("")};
|
||||
auto pos = s.find_last_of('.');
|
||||
if (pos == string_view_t::npos || pos == 0)
|
||||
return string_view_pair{s, string_view_t{}};
|
||||
return string_view_pair{s.substr(0, pos), s.substr(pos)};
|
||||
}
|
||||
|
||||
inline string_view_t createView(PosPtr S, PosPtr E) noexcept { return {S, static_cast<size_t>(E - S) + 1}; }
|
||||
|
||||
} // namespace parser
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // PATH_PARSER_H
|
497
third_party/libcxx/fs/posix_compat.h
vendored
Normal file
497
third_party/libcxx/fs/posix_compat.h
vendored
Normal file
|
@ -0,0 +1,497 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
//
|
||||
// POSIX-like portability helper functions.
|
||||
//
|
||||
// These generally behave like the proper posix functions, with these
|
||||
// exceptions:
|
||||
// On Windows, they take paths in wchar_t* form, instead of char* form.
|
||||
// The symlink() function is split into two frontends, symlink_file()
|
||||
// and symlink_dir().
|
||||
//
|
||||
// These are provided within an anonymous namespace within the detail
|
||||
// namespace - callers need to include this header and call them as
|
||||
// detail::function(), regardless of platform.
|
||||
//
|
||||
|
||||
#ifndef POSIX_COMPAT_H
|
||||
#define POSIX_COMPAT_H
|
||||
|
||||
#include <__assert>
|
||||
#include <__config>
|
||||
#include <filesystem>
|
||||
|
||||
#include "error.h"
|
||||
#include "time_utils.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
# include <winioctl.h>
|
||||
#else
|
||||
# include <fcntl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/statvfs.h>
|
||||
# include <sys/time.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
// This struct isn't defined in the normal Windows SDK, but only in the
|
||||
// Windows Driver Kit.
|
||||
struct LIBCPP_REPARSE_DATA_BUFFER {
|
||||
unsigned long ReparseTag;
|
||||
unsigned short ReparseDataLength;
|
||||
unsigned short Reserved;
|
||||
union {
|
||||
struct {
|
||||
unsigned short SubstituteNameOffset;
|
||||
unsigned short SubstituteNameLength;
|
||||
unsigned short PrintNameOffset;
|
||||
unsigned short PrintNameLength;
|
||||
unsigned long Flags;
|
||||
wchar_t PathBuffer[1];
|
||||
} SymbolicLinkReparseBuffer;
|
||||
struct {
|
||||
unsigned short SubstituteNameOffset;
|
||||
unsigned short SubstituteNameLength;
|
||||
unsigned short PrintNameOffset;
|
||||
unsigned short PrintNameLength;
|
||||
wchar_t PathBuffer[1];
|
||||
} MountPointReparseBuffer;
|
||||
struct {
|
||||
unsigned char DataBuffer[1];
|
||||
} GenericReparseBuffer;
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
|
||||
// Various C runtime header sets provide more or less of these. As we
|
||||
// provide our own implementation, undef all potential defines from the
|
||||
// C runtime headers and provide a complete set of macros of our own.
|
||||
|
||||
# undef _S_IFMT
|
||||
# undef _S_IFDIR
|
||||
# undef _S_IFCHR
|
||||
# undef _S_IFIFO
|
||||
# undef _S_IFREG
|
||||
# undef _S_IFBLK
|
||||
# undef _S_IFLNK
|
||||
# undef _S_IFSOCK
|
||||
|
||||
# define _S_IFMT 0xF000
|
||||
# define _S_IFDIR 0x4000
|
||||
# define _S_IFCHR 0x2000
|
||||
# define _S_IFIFO 0x1000
|
||||
# define _S_IFREG 0x8000
|
||||
# define _S_IFBLK 0x6000
|
||||
# define _S_IFLNK 0xA000
|
||||
# define _S_IFSOCK 0xC000
|
||||
|
||||
# undef S_ISDIR
|
||||
# undef S_ISFIFO
|
||||
# undef S_ISCHR
|
||||
# undef S_ISREG
|
||||
# undef S_ISLNK
|
||||
# undef S_ISBLK
|
||||
# undef S_ISSOCK
|
||||
|
||||
# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
|
||||
# define S_ISCHR(m) (((m) & _S_IFMT) == _S_IFCHR)
|
||||
# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
|
||||
# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
|
||||
# define S_ISBLK(m) (((m) & _S_IFMT) == _S_IFBLK)
|
||||
# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
|
||||
# define S_ISSOCK(m) (((m) & _S_IFMT) == _S_IFSOCK)
|
||||
|
||||
# define O_NONBLOCK 0
|
||||
|
||||
inline int set_errno(int e = GetLastError()) {
|
||||
errno = static_cast<int>(__win_err_to_errc(e));
|
||||
return -1;
|
||||
}
|
||||
|
||||
class WinHandle {
|
||||
public:
|
||||
WinHandle(const wchar_t* p, DWORD access, DWORD flags) {
|
||||
h = CreateFileW(
|
||||
p,
|
||||
access,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | flags,
|
||||
nullptr);
|
||||
}
|
||||
~WinHandle() {
|
||||
if (h != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(h);
|
||||
}
|
||||
operator HANDLE() const { return h; }
|
||||
operator bool() const { return h != INVALID_HANDLE_VALUE; }
|
||||
|
||||
private:
|
||||
HANDLE h;
|
||||
};
|
||||
|
||||
inline int stat_handle(HANDLE h, StatT* buf) {
|
||||
FILE_BASIC_INFO basic;
|
||||
if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic)))
|
||||
return set_errno();
|
||||
memset(buf, 0, sizeof(*buf));
|
||||
buf->st_mtim = filetime_to_timespec(basic.LastWriteTime);
|
||||
buf->st_atim = filetime_to_timespec(basic.LastAccessTime);
|
||||
buf->st_mode = 0555; // Read-only
|
||||
if (!(basic.FileAttributes & FILE_ATTRIBUTE_READONLY))
|
||||
buf->st_mode |= 0222; // Write
|
||||
if (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
buf->st_mode |= _S_IFDIR;
|
||||
} else {
|
||||
buf->st_mode |= _S_IFREG;
|
||||
}
|
||||
if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
FILE_ATTRIBUTE_TAG_INFO tag;
|
||||
if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, sizeof(tag)))
|
||||
return set_errno();
|
||||
if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
||||
buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK;
|
||||
}
|
||||
FILE_STANDARD_INFO standard;
|
||||
if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, sizeof(standard)))
|
||||
return set_errno();
|
||||
buf->st_nlink = standard.NumberOfLinks;
|
||||
buf->st_size = standard.EndOfFile.QuadPart;
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
if (!GetFileInformationByHandle(h, &info))
|
||||
return set_errno();
|
||||
buf->st_dev = info.dwVolumeSerialNumber;
|
||||
memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4);
|
||||
memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int stat_file(const wchar_t* path, StatT* buf, DWORD flags) {
|
||||
WinHandle h(path, FILE_READ_ATTRIBUTES, flags);
|
||||
if (!h)
|
||||
return set_errno();
|
||||
int ret = stat_handle(h, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline int stat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, 0); }
|
||||
|
||||
inline int lstat(const wchar_t* path, StatT* buf) { return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT); }
|
||||
|
||||
inline int fstat(int fd, StatT* buf) {
|
||||
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
||||
return stat_handle(h, buf);
|
||||
}
|
||||
|
||||
inline int mkdir(const wchar_t* path, int permissions) {
|
||||
(void)permissions;
|
||||
if (!CreateDirectoryW(path, nullptr))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int symlink_file_dir(const wchar_t* oldname, const wchar_t* newname, bool is_dir) {
|
||||
path dest(oldname);
|
||||
dest.make_preferred();
|
||||
oldname = dest.c_str();
|
||||
DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
|
||||
if (CreateSymbolicLinkW(newname, oldname, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
|
||||
return 0;
|
||||
int e = GetLastError();
|
||||
if (e != ERROR_INVALID_PARAMETER)
|
||||
return set_errno(e);
|
||||
if (CreateSymbolicLinkW(newname, oldname, flags))
|
||||
return 0;
|
||||
return set_errno();
|
||||
}
|
||||
|
||||
inline int symlink_file(const wchar_t* oldname, const wchar_t* newname) {
|
||||
return symlink_file_dir(oldname, newname, false);
|
||||
}
|
||||
|
||||
inline int symlink_dir(const wchar_t* oldname, const wchar_t* newname) {
|
||||
return symlink_file_dir(oldname, newname, true);
|
||||
}
|
||||
|
||||
inline int link(const wchar_t* oldname, const wchar_t* newname) {
|
||||
if (CreateHardLinkW(newname, oldname, nullptr))
|
||||
return 0;
|
||||
return set_errno();
|
||||
}
|
||||
|
||||
inline int remove(const wchar_t* path) {
|
||||
detail::WinHandle h(path, DELETE, FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
if (!h)
|
||||
return set_errno();
|
||||
FILE_DISPOSITION_INFO info;
|
||||
info.DeleteFile = TRUE;
|
||||
if (!SetFileInformationByHandle(h, FileDispositionInfo, &info, sizeof(info)))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int truncate_handle(HANDLE h, off_t length) {
|
||||
LARGE_INTEGER size_param;
|
||||
size_param.QuadPart = length;
|
||||
if (!SetFilePointerEx(h, size_param, 0, FILE_BEGIN))
|
||||
return set_errno();
|
||||
if (!SetEndOfFile(h))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int ftruncate(int fd, off_t length) {
|
||||
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
||||
return truncate_handle(h, length);
|
||||
}
|
||||
|
||||
inline int truncate(const wchar_t* path, off_t length) {
|
||||
detail::WinHandle h(path, GENERIC_WRITE, 0);
|
||||
if (!h)
|
||||
return set_errno();
|
||||
return truncate_handle(h, length);
|
||||
}
|
||||
|
||||
inline int rename(const wchar_t* from, const wchar_t* to) {
|
||||
if (!(MoveFileExW(from, to, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int chdir(const wchar_t* path) {
|
||||
if (!SetCurrentDirectoryW(path))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct StatVFS {
|
||||
uint64_t f_frsize;
|
||||
uint64_t f_blocks;
|
||||
uint64_t f_bfree;
|
||||
uint64_t f_bavail;
|
||||
};
|
||||
|
||||
inline int statvfs(const wchar_t* p, StatVFS* buf) {
|
||||
path dir = p;
|
||||
while (true) {
|
||||
error_code local_ec;
|
||||
const file_status st = status(dir, local_ec);
|
||||
if (!exists(st) || is_directory(st))
|
||||
break;
|
||||
path parent = dir.parent_path();
|
||||
if (parent == dir) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
dir = parent;
|
||||
}
|
||||
ULARGE_INTEGER free_bytes_available_to_caller, total_number_of_bytes, total_number_of_free_bytes;
|
||||
if (!GetDiskFreeSpaceExW(
|
||||
dir.c_str(), &free_bytes_available_to_caller, &total_number_of_bytes, &total_number_of_free_bytes))
|
||||
return set_errno();
|
||||
buf->f_frsize = 1;
|
||||
buf->f_blocks = total_number_of_bytes.QuadPart;
|
||||
buf->f_bfree = total_number_of_free_bytes.QuadPart;
|
||||
buf->f_bavail = free_bytes_available_to_caller.QuadPart;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline wchar_t* getcwd([[maybe_unused]] wchar_t* in_buf, [[maybe_unused]] size_t in_size) {
|
||||
// Only expected to be used with us allocating the buffer.
|
||||
_LIBCPP_ASSERT_INTERNAL(in_buf == nullptr, "Windows getcwd() assumes in_buf==nullptr");
|
||||
_LIBCPP_ASSERT_INTERNAL(in_size == 0, "Windows getcwd() assumes in_size==0");
|
||||
|
||||
size_t buff_size = MAX_PATH + 10;
|
||||
std::unique_ptr<wchar_t, decltype(&::free)> buff(static_cast<wchar_t*>(malloc(buff_size * sizeof(wchar_t))), &::free);
|
||||
DWORD retval = GetCurrentDirectoryW(buff_size, buff.get());
|
||||
if (retval > buff_size) {
|
||||
buff_size = retval;
|
||||
buff.reset(static_cast<wchar_t*>(malloc(buff_size * sizeof(wchar_t))));
|
||||
retval = GetCurrentDirectoryW(buff_size, buff.get());
|
||||
}
|
||||
if (!retval) {
|
||||
set_errno();
|
||||
return nullptr;
|
||||
}
|
||||
return buff.release();
|
||||
}
|
||||
|
||||
inline wchar_t* realpath(const wchar_t* path, [[maybe_unused]] wchar_t* resolved_name) {
|
||||
// Only expected to be used with us allocating the buffer.
|
||||
_LIBCPP_ASSERT_INTERNAL(resolved_name == nullptr, "Windows realpath() assumes a null resolved_name");
|
||||
|
||||
WinHandle h(path, FILE_READ_ATTRIBUTES, 0);
|
||||
if (!h) {
|
||||
set_errno();
|
||||
return nullptr;
|
||||
}
|
||||
size_t buff_size = MAX_PATH + 10;
|
||||
std::unique_ptr<wchar_t, decltype(&::free)> buff(static_cast<wchar_t*>(malloc(buff_size * sizeof(wchar_t))), &::free);
|
||||
DWORD retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
if (retval > buff_size) {
|
||||
buff_size = retval;
|
||||
buff.reset(static_cast<wchar_t*>(malloc(buff_size * sizeof(wchar_t))));
|
||||
retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
|
||||
}
|
||||
if (!retval) {
|
||||
set_errno();
|
||||
return nullptr;
|
||||
}
|
||||
wchar_t* ptr = buff.get();
|
||||
if (!wcsncmp(ptr, L"\\\\?\\", 4)) {
|
||||
if (ptr[5] == ':') { // \\?\X: -> X:
|
||||
memmove(&ptr[0], &ptr[4], (wcslen(&ptr[4]) + 1) * sizeof(wchar_t));
|
||||
} else if (!wcsncmp(&ptr[4], L"UNC\\", 4)) { // \\?\UNC\server -> \\server
|
||||
wcscpy(&ptr[0], L"\\\\");
|
||||
memmove(&ptr[2], &ptr[8], (wcslen(&ptr[8]) + 1) * sizeof(wchar_t));
|
||||
}
|
||||
}
|
||||
return buff.release();
|
||||
}
|
||||
|
||||
# define AT_FDCWD -1
|
||||
# define AT_SYMLINK_NOFOLLOW 1
|
||||
using ModeT = int;
|
||||
|
||||
inline int fchmod_handle(HANDLE h, int perms) {
|
||||
FILE_BASIC_INFO basic;
|
||||
if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic)))
|
||||
return set_errno();
|
||||
DWORD orig_attributes = basic.FileAttributes;
|
||||
basic.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
|
||||
if ((perms & 0222) == 0)
|
||||
basic.FileAttributes |= FILE_ATTRIBUTE_READONLY;
|
||||
if (basic.FileAttributes != orig_attributes && !SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof(basic)))
|
||||
return set_errno();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int fchmodat(int /*fd*/, const wchar_t* path, int perms, int flag) {
|
||||
DWORD attributes = GetFileAttributesW(path);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES)
|
||||
return set_errno();
|
||||
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT && !(flag & AT_SYMLINK_NOFOLLOW)) {
|
||||
// If the file is a symlink, and we are supposed to operate on the target
|
||||
// of the symlink, we need to open a handle to it, without the
|
||||
// FILE_FLAG_OPEN_REPARSE_POINT flag, to open the destination of the
|
||||
// symlink, and operate on it via the handle.
|
||||
detail::WinHandle h(path, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0);
|
||||
if (!h)
|
||||
return set_errno();
|
||||
return fchmod_handle(h, perms);
|
||||
} else {
|
||||
// For a non-symlink, or if operating on the symlink itself instead of
|
||||
// its target, we can use SetFileAttributesW, saving a few calls.
|
||||
DWORD orig_attributes = attributes;
|
||||
attributes &= ~FILE_ATTRIBUTE_READONLY;
|
||||
if ((perms & 0222) == 0)
|
||||
attributes |= FILE_ATTRIBUTE_READONLY;
|
||||
if (attributes != orig_attributes && !SetFileAttributesW(path, attributes))
|
||||
return set_errno();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int fchmod(int fd, int perms) {
|
||||
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
||||
return fchmod_handle(h, perms);
|
||||
}
|
||||
|
||||
# define MAX_SYMLINK_SIZE MAXIMUM_REPARSE_DATA_BUFFER_SIZE
|
||||
using SSizeT = ::int64_t;
|
||||
|
||||
inline SSizeT readlink(const wchar_t* path, wchar_t* ret_buf, size_t bufsize) {
|
||||
uint8_t buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
detail::WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT);
|
||||
if (!h)
|
||||
return set_errno();
|
||||
DWORD out;
|
||||
if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf, sizeof(buf), &out, 0))
|
||||
return set_errno();
|
||||
const auto* reparse = reinterpret_cast<LIBCPP_REPARSE_DATA_BUFFER*>(buf);
|
||||
size_t path_buf_offset = offsetof(LIBCPP_REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[0]);
|
||||
if (out < path_buf_offset) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
const auto& symlink = reparse->SymbolicLinkReparseBuffer;
|
||||
unsigned short name_offset, name_length;
|
||||
if (symlink.PrintNameLength == 0) {
|
||||
name_offset = symlink.SubstituteNameOffset;
|
||||
name_length = symlink.SubstituteNameLength;
|
||||
} else {
|
||||
name_offset = symlink.PrintNameOffset;
|
||||
name_length = symlink.PrintNameLength;
|
||||
}
|
||||
// name_offset/length are expressed in bytes, not in wchar_t
|
||||
if (path_buf_offset + name_offset + name_length > out) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (name_length / sizeof(wchar_t) > bufsize) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(ret_buf, &symlink.PathBuffer[name_offset / sizeof(wchar_t)], name_length);
|
||||
return name_length / sizeof(wchar_t);
|
||||
}
|
||||
|
||||
#else
|
||||
inline int symlink_file(const char* oldname, const char* newname) { return ::symlink(oldname, newname); }
|
||||
inline int symlink_dir(const char* oldname, const char* newname) { return ::symlink(oldname, newname); }
|
||||
using ::chdir;
|
||||
using ::fchmod;
|
||||
# if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD)
|
||||
using ::fchmodat;
|
||||
# endif
|
||||
using ::fstat;
|
||||
using ::ftruncate;
|
||||
using ::getcwd;
|
||||
using ::link;
|
||||
using ::lstat;
|
||||
using ::mkdir;
|
||||
using ::readlink;
|
||||
using ::realpath;
|
||||
using ::remove;
|
||||
using ::rename;
|
||||
using ::stat;
|
||||
using ::statvfs;
|
||||
using ::truncate;
|
||||
|
||||
# define O_BINARY 0
|
||||
|
||||
using StatVFS = struct statvfs;
|
||||
using ModeT = ::mode_t;
|
||||
using SSizeT = ::ssize_t;
|
||||
|
||||
#endif
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // POSIX_COMPAT_H
|
351
third_party/libcxx/fs/time_utils.h
vendored
Normal file
351
third_party/libcxx/fs/time_utils.h
vendored
Normal file
|
@ -0,0 +1,351 @@
|
|||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
|
||||
#ifndef FILESYSTEM_TIME_UTILS_H
|
||||
#define FILESYSTEM_TIME_UTILS_H
|
||||
|
||||
#include <__config>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <ratio>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "error.h"
|
||||
#include "format_string.h"
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <fcntl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/time.h> // for ::utimes as used in __last_write_time
|
||||
#endif
|
||||
|
||||
// We can use the presence of UTIME_OMIT to detect platforms that provide utimensat.
|
||||
#if defined(UTIME_OMIT)
|
||||
# define _LIBCPP_USE_UTIMENSAT
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
// Various C runtime versions (UCRT, or the legacy msvcrt.dll used by
|
||||
// some mingw toolchains) provide different stat function implementations,
|
||||
// with a number of limitations with respect to what we want from the
|
||||
// stat function. Instead provide our own which does exactly what we want,
|
||||
// along with our own stat structure and flag macros.
|
||||
|
||||
struct TimeSpec {
|
||||
int64_t tv_sec;
|
||||
int64_t tv_nsec;
|
||||
};
|
||||
struct StatT {
|
||||
unsigned st_mode;
|
||||
TimeSpec st_atim;
|
||||
TimeSpec st_mtim;
|
||||
uint64_t st_dev; // FILE_ID_INFO::VolumeSerialNumber
|
||||
struct FileIdStruct {
|
||||
unsigned char id[16]; // FILE_ID_INFO::FileId
|
||||
bool operator==(const FileIdStruct& other) const {
|
||||
for (int i = 0; i < 16; i++)
|
||||
if (id[i] != other.id[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
} st_ino;
|
||||
uint32_t st_nlink;
|
||||
uintmax_t st_size;
|
||||
};
|
||||
|
||||
// There were 369 years and 89 leap days from the Windows epoch
|
||||
// (1601) to the Unix epoch (1970).
|
||||
# define FILE_TIME_OFFSET_SECS (uint64_t(369 * 365 + 89) * (24 * 60 * 60))
|
||||
|
||||
inline TimeSpec filetime_to_timespec(LARGE_INTEGER li) {
|
||||
TimeSpec ret;
|
||||
ret.tv_sec = li.QuadPart / 10000000 - FILE_TIME_OFFSET_SECS;
|
||||
ret.tv_nsec = (li.QuadPart % 10000000) * 100;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline TimeSpec filetime_to_timespec(FILETIME ft) {
|
||||
LARGE_INTEGER li;
|
||||
li.LowPart = ft.dwLowDateTime;
|
||||
li.HighPart = ft.dwHighDateTime;
|
||||
return filetime_to_timespec(li);
|
||||
}
|
||||
|
||||
inline FILETIME timespec_to_filetime(TimeSpec ts) {
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = ts.tv_nsec / 100 + (ts.tv_sec + FILE_TIME_OFFSET_SECS) * 10000000;
|
||||
FILETIME ft;
|
||||
ft.dwLowDateTime = li.LowPart;
|
||||
ft.dwHighDateTime = li.HighPart;
|
||||
return ft;
|
||||
}
|
||||
|
||||
#else
|
||||
using TimeSpec = struct timespec;
|
||||
using TimeVal = struct timeval;
|
||||
using StatT = struct stat;
|
||||
|
||||
inline TimeVal make_timeval(TimeSpec const& ts) {
|
||||
using namespace chrono;
|
||||
auto Convert = [](long nsec) {
|
||||
using int_type = decltype(std::declval<TimeVal>().tv_usec);
|
||||
auto dur = duration_cast<microseconds>(nanoseconds(nsec)).count();
|
||||
return static_cast<int_type>(dur);
|
||||
};
|
||||
TimeVal TV = {};
|
||||
TV.tv_sec = ts.tv_sec;
|
||||
TV.tv_usec = Convert(ts.tv_nsec);
|
||||
return TV;
|
||||
}
|
||||
#endif
|
||||
|
||||
using chrono::duration;
|
||||
using chrono::duration_cast;
|
||||
|
||||
template <class FileTimeT, class TimeT, bool IsFloat = is_floating_point<typename FileTimeT::rep>::value>
|
||||
struct time_util_base {
|
||||
using rep = typename FileTimeT::rep;
|
||||
using fs_duration = typename FileTimeT::duration;
|
||||
using fs_seconds = duration<rep>;
|
||||
using fs_nanoseconds = duration<rep, nano>;
|
||||
using fs_microseconds = duration<rep, micro>;
|
||||
|
||||
static constexpr rep max_seconds = duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
|
||||
|
||||
static constexpr rep max_nsec =
|
||||
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() - fs_seconds(max_seconds)).count();
|
||||
|
||||
static constexpr rep min_seconds = duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
|
||||
|
||||
static constexpr rep min_nsec_timespec =
|
||||
duration_cast<fs_nanoseconds>((FileTimeT::duration::min() - fs_seconds(min_seconds)) + fs_seconds(1)).count();
|
||||
|
||||
private:
|
||||
static constexpr fs_duration get_min_nsecs() {
|
||||
return duration_cast<fs_duration>(fs_nanoseconds(min_nsec_timespec) - duration_cast<fs_nanoseconds>(fs_seconds(1)));
|
||||
}
|
||||
// Static assert that these values properly round trip.
|
||||
static_assert(fs_seconds(min_seconds) + get_min_nsecs() == FileTimeT::duration::min(), "value doesn't roundtrip");
|
||||
|
||||
static constexpr bool check_range() {
|
||||
// This kinda sucks, but it's what happens when we don't have __int128_t.
|
||||
if (sizeof(TimeT) == sizeof(rep)) {
|
||||
typedef duration<long long, ratio<3600 * 24 * 365> > Years;
|
||||
return duration_cast<Years>(fs_seconds(max_seconds)) > Years(250) &&
|
||||
duration_cast<Years>(fs_seconds(min_seconds)) < Years(-250);
|
||||
}
|
||||
return max_seconds >= numeric_limits<TimeT>::max() && min_seconds <= numeric_limits<TimeT>::min();
|
||||
}
|
||||
#if _LIBCPP_STD_VER >= 14
|
||||
static_assert(check_range(), "the representable range is unacceptable small");
|
||||
#endif
|
||||
};
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
struct time_util_base<FileTimeT, TimeT, true> {
|
||||
using rep = typename FileTimeT::rep;
|
||||
using fs_duration = typename FileTimeT::duration;
|
||||
using fs_seconds = duration<rep>;
|
||||
using fs_nanoseconds = duration<rep, nano>;
|
||||
using fs_microseconds = duration<rep, micro>;
|
||||
|
||||
static const rep max_seconds;
|
||||
static const rep max_nsec;
|
||||
static const rep min_seconds;
|
||||
static const rep min_nsec_timespec;
|
||||
};
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::max_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::max_nsec =
|
||||
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() - fs_seconds(max_seconds)).count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::min_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::min_nsec_timespec =
|
||||
duration_cast<fs_nanoseconds>((FileTimeT::duration::min() - fs_seconds(min_seconds)) + fs_seconds(1)).count();
|
||||
|
||||
template <class FileTimeT, class TimeT, class TimeSpecT>
|
||||
struct time_util : time_util_base<FileTimeT, TimeT> {
|
||||
using Base = time_util_base<FileTimeT, TimeT>;
|
||||
using Base::max_nsec;
|
||||
using Base::max_seconds;
|
||||
using Base::min_nsec_timespec;
|
||||
using Base::min_seconds;
|
||||
|
||||
using typename Base::fs_duration;
|
||||
using typename Base::fs_microseconds;
|
||||
using typename Base::fs_nanoseconds;
|
||||
using typename Base::fs_seconds;
|
||||
|
||||
public:
|
||||
template <class CType, class ChronoType>
|
||||
static constexpr 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 constexpr 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 constexpr bool is_representable(FileTimeT tm) {
|
||||
auto secs = duration_cast<fs_seconds>(tm.time_since_epoch());
|
||||
auto nsecs = duration_cast<fs_nanoseconds>(tm.time_since_epoch() - secs);
|
||||
if (nsecs.count() < 0) {
|
||||
secs = secs + fs_seconds(1);
|
||||
nsecs = nsecs + fs_seconds(1);
|
||||
}
|
||||
using TLim = numeric_limits<TimeT>;
|
||||
if (secs.count() >= 0)
|
||||
return secs.count() <= TLim::max();
|
||||
return secs.count() >= TLim::min();
|
||||
}
|
||||
|
||||
static constexpr FileTimeT convert_from_timespec(TimeSpecT tm) {
|
||||
if (tm.tv_sec >= 0 || tm.tv_nsec == 0) {
|
||||
return FileTimeT(fs_seconds(tm.tv_sec) + duration_cast<fs_duration>(fs_nanoseconds(tm.tv_nsec)));
|
||||
} else { // tm.tv_sec < 0
|
||||
auto adj_subsec = duration_cast<fs_duration>(fs_seconds(1) - fs_nanoseconds(tm.tv_nsec));
|
||||
auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec;
|
||||
return FileTimeT(Dur);
|
||||
}
|
||||
}
|
||||
|
||||
template <class SubSecT>
|
||||
static constexpr bool set_times_checked(TimeT* sec_out, SubSecT* subsec_out, FileTimeT tp) {
|
||||
auto dur = tp.time_since_epoch();
|
||||
auto sec_dur = duration_cast<fs_seconds>(dur);
|
||||
auto subsec_dur = duration_cast<fs_nanoseconds>(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 = sec_dur - fs_seconds(1);
|
||||
subsec_dur = subsec_dur + fs_seconds(1);
|
||||
} else {
|
||||
subsec_dur = fs_nanoseconds::zero();
|
||||
}
|
||||
}
|
||||
return checked_set(sec_out, sec_dur.count()) && checked_set(subsec_out, subsec_dur.count());
|
||||
}
|
||||
static constexpr bool convert_to_timespec(TimeSpecT& dest, FileTimeT tp) {
|
||||
if (!is_representable(tp))
|
||||
return false;
|
||||
return set_times_checked(&dest.tv_sec, &dest.tv_nsec, tp);
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
using fs_time = time_util<file_time_type, int64_t, TimeSpec>;
|
||||
#else
|
||||
using fs_time = time_util<file_time_type, time_t, TimeSpec>;
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
inline TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
|
||||
inline TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
|
||||
#elif defined(__MVS__)
|
||||
inline TimeSpec extract_mtime(StatT const& st) {
|
||||
TimeSpec TS = {st.st_mtime, 0};
|
||||
return TS;
|
||||
}
|
||||
inline TimeSpec extract_atime(StatT const& st) {
|
||||
TimeSpec TS = {st.st_atime, 0};
|
||||
return TS;
|
||||
}
|
||||
#elif defined(_AIX)
|
||||
inline TimeSpec extract_mtime(StatT const& st) {
|
||||
TimeSpec TS = {st.st_mtime, st.st_mtime_n};
|
||||
return TS;
|
||||
}
|
||||
inline TimeSpec extract_atime(StatT const& st) {
|
||||
TimeSpec TS = {st.st_atime, st.st_atime_n};
|
||||
return TS;
|
||||
}
|
||||
#else
|
||||
inline TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
|
||||
inline TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
|
||||
#endif
|
||||
|
||||
#ifndef _LIBCPP_HAS_NO_FILESYSTEM
|
||||
|
||||
# if !defined(_LIBCPP_WIN32API)
|
||||
inline bool posix_utimes(const path& p, std::array<TimeSpec, 2> const& TS, error_code& ec) {
|
||||
TimeVal ConvertedTS[2] = {make_timeval(TS[0]), make_timeval(TS[1])};
|
||||
if (::utimes(p.c_str(), ConvertedTS) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
# if defined(_LIBCPP_USE_UTIMENSAT)
|
||||
inline bool posix_utimensat(const path& p, std::array<TimeSpec, 2> const& TS, error_code& ec) {
|
||||
if (::utimensat(AT_FDCWD, p.c_str(), TS.data(), 0) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
# endif
|
||||
|
||||
inline bool set_file_times(const path& p, std::array<TimeSpec, 2> const& TS, error_code& ec) {
|
||||
# if !defined(_LIBCPP_USE_UTIMENSAT)
|
||||
return posix_utimes(p, TS, ec);
|
||||
# else
|
||||
return posix_utimensat(p, TS, ec);
|
||||
# endif
|
||||
}
|
||||
|
||||
# endif // !_LIBCPP_WIN32API
|
||||
|
||||
inline file_time_type __extract_last_write_time(const path& p, const StatT& st, error_code* ec) {
|
||||
using detail::fs_time;
|
||||
ErrorHandler<file_time_type> err("last_write_time", ec, &p);
|
||||
|
||||
auto ts = detail::extract_mtime(st);
|
||||
if (!fs_time::is_representable(ts))
|
||||
return err.report(errc::value_too_large);
|
||||
|
||||
return fs_time::convert_from_timespec(ts);
|
||||
}
|
||||
|
||||
#endif // !_LIBCPP_HAS_NO_FILESYSTEM
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // FILESYSTEM_TIME_UTILS_H
|
Loading…
Add table
Add a link
Reference in a new issue