Implement <filesystem>
This patch implements the <filesystem> header and uses that to provide <experimental/filesystem>. Unlike other standard headers, the symbols needed for <filesystem> have not yet been placed in libc++.so. Instead they live in the new libc++fs.a library. Users of filesystem are required to link this library. (Also note that libc++experimental no longer contains the definition of <experimental/filesystem>, which now requires linking libc++fs). The reason for keeping <filesystem> out of the dylib for now is that it's still somewhat experimental, and the possibility of requiring an ABI breaking change is very real. In the future the symbols will likely be moved into the dylib, or the dylib will be made to link libc++fs automagically). Note that moving the symbols out of libc++experimental may break user builds until they update to -lc++fs. This should be OK, because the experimental library provides no stability guarantees. However, I plan on looking into ways we can force libc++experimental to automagically link libc++fs. In order to use a single implementation and set of tests for <filesystem>, it has been placed in a special `__fs` namespace. This namespace is inline in C++17 onward, but not before that. As such implementation is available in C++11 onward, but no filesystem namespace is present "directly", and as such name conflicts shouldn't occur in C++11 or C++14. git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@338093 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
396
src/filesystem/directory_iterator.cpp
Normal file
396
src/filesystem/directory_iterator.cpp
Normal file
@@ -0,0 +1,396 @@
|
||||
//===------------------ directory_iterator.cpp ----------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is dual licensed under the MIT and the University of Illinois Open
|
||||
// Source Licenses. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "filesystem"
|
||||
#include "__config"
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
|
||||
#include "filesystem_common.h"
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
#if !defined(_LIBCPP_WIN32API)
|
||||
template <class DirEntT, class = decltype(DirEntT::d_type)>
|
||||
static 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;
|
||||
}
|
||||
|
||||
template <class DirEntT>
|
||||
static file_type get_file_type(DirEntT* ent, long) {
|
||||
return file_type::none;
|
||||
}
|
||||
|
||||
static 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
|
||||
|
||||
static file_type get_file_type(const WIN32_FIND_DATA& data) {
|
||||
//auto attrs = data.dwFileAttributes;
|
||||
// FIXME(EricWF)
|
||||
return file_type::unknown;
|
||||
}
|
||||
static uintmax_t get_file_size(const WIN32_FIND_DATA& data) {
|
||||
return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow;
|
||||
}
|
||||
static file_time_type get_write_time(const WIN32_FIND_DATA& data) {
|
||||
ULARGE_INTEGER tmp;
|
||||
FILETIME& time = data.ftLastWriteTime;
|
||||
tmp.u.LowPart = time.dwLowDateTime;
|
||||
tmp.u.HighPart = time.dwHighDateTime;
|
||||
return file_time_type(file_time_type::duration(time.QuadPart));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
} // namespace detail
|
||||
|
||||
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_(move(__ds.__root_)),
|
||||
__entry_(move(__ds.__entry_)) {
|
||||
__ds.__stream_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
__dir_stream(const path& root, directory_options opts, error_code& ec)
|
||||
: __stream_(INVALID_HANDLE_VALUE), __root_(root) {
|
||||
__stream_ = ::FindFirstFileEx(root.c_str(), &__data_);
|
||||
if (__stream_ == INVALID_HANDLE_VALUE) {
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
const bool ignore_permission_denied =
|
||||
bool(opts & directory_options::skip_permission_denied);
|
||||
if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
|
||||
ec.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~__dir_stream() noexcept {
|
||||
if (__stream_ == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
close();
|
||||
}
|
||||
|
||||
bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
|
||||
|
||||
bool advance(error_code& ec) {
|
||||
while (::FindNextFile(__stream_, &__data_)) {
|
||||
if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
|
||||
continue;
|
||||
// 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(get_file_type(__data)));
|
||||
return true;
|
||||
}
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
error_code close() noexcept {
|
||||
error_code ec;
|
||||
if (!::FindClose(__stream_))
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
__stream_ = INVALID_HANDLE_VALUE;
|
||||
return ec;
|
||||
}
|
||||
|
||||
HANDLE __stream_{INVALID_HANDLE_VALUE};
|
||||
WIN32_FIND_DATA __data_;
|
||||
|
||||
public:
|
||||
path __root_;
|
||||
directory_entry __entry_;
|
||||
};
|
||||
#else
|
||||
class __dir_stream {
|
||||
public:
|
||||
__dir_stream() = delete;
|
||||
__dir_stream& operator=(const __dir_stream&) = delete;
|
||||
|
||||
__dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_),
|
||||
__root_(move(other.__root_)),
|
||||
__entry_(move(other.__entry_)) {
|
||||
other.__stream_ = nullptr;
|
||||
}
|
||||
|
||||
__dir_stream(const path& root, directory_options opts, error_code& ec)
|
||||
: __stream_(nullptr), __root_(root) {
|
||||
if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
|
||||
ec = detail::capture_errno();
|
||||
const bool allow_eacess =
|
||||
bool(opts & directory_options::skip_permission_denied);
|
||||
if (allow_eacess && ec.value() == EACCES)
|
||||
ec.clear();
|
||||
return;
|
||||
}
|
||||
advance(ec);
|
||||
}
|
||||
|
||||
~__dir_stream() noexcept {
|
||||
if (__stream_)
|
||||
close();
|
||||
}
|
||||
|
||||
bool good() const noexcept { return __stream_ != nullptr; }
|
||||
|
||||
bool advance(error_code& ec) {
|
||||
while (true) {
|
||||
auto str_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(__imp_, "Attempting to increment an invalid iterator");
|
||||
ErrorHandler<void> err("directory_iterator::operator++()", ec);
|
||||
|
||||
error_code m_ec;
|
||||
if (!__imp_->advance(m_ec)) {
|
||||
path root = move(__imp_->__root_);
|
||||
__imp_.reset();
|
||||
if (m_ec)
|
||||
err.report(m_ec, "at root \"%s\"", root);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_entry const& directory_iterator::__dereference() const {
|
||||
_LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
|
||||
return __imp_->__entry_;
|
||||
}
|
||||
|
||||
// recursive_directory_iterator
|
||||
|
||||
struct recursive_directory_iterator::__shared_imp {
|
||||
stack<__dir_stream> __stack_;
|
||||
directory_options __options_;
|
||||
};
|
||||
|
||||
recursive_directory_iterator::recursive_directory_iterator(
|
||||
const path& p, directory_options opt, error_code* ec)
|
||||
: __imp_(nullptr), __rec_(true) {
|
||||
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(move(new_s));
|
||||
}
|
||||
|
||||
void recursive_directory_iterator::__pop(error_code* ec) {
|
||||
_LIBCPP_ASSERT(__imp_, "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 = move(stack.top().__root_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "at root \"%s\"", root);
|
||||
} 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(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 = move(curr_it.__entry_.__p_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "attempting recursion into \"%s\"", at_ent);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
429
src/filesystem/filesystem_common.h
Normal file
429
src/filesystem/filesystem_common.h
Normal file
@@ -0,0 +1,429 @@
|
||||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is dual licensed under the MIT and the University of Illinois Open
|
||||
// Source Licenses. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
|
||||
#ifndef FILESYSTEM_COMMON_H
|
||||
#define FILESYSTEM_COMMON_H
|
||||
|
||||
#include "__config"
|
||||
#include "filesystem"
|
||||
#include "array"
|
||||
#include "chrono"
|
||||
#include "cstdlib"
|
||||
#include "climits"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/time.h> // for ::utimes as used in __last_write_time
|
||||
#include <fcntl.h> /* values for fchmodat */
|
||||
|
||||
#include "../include/apple_availability.h"
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
// We can use the presence of UTIME_OMIT to detect platforms that provide
|
||||
// utimensat.
|
||||
#if defined(UTIME_OMIT)
|
||||
#define _LIBCPP_USE_UTIMENSAT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#endif
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
static string format_string_imp(const char* msg, ...) {
|
||||
// we might need a second shot at this, so pre-emptivly make a copy
|
||||
struct GuardVAList {
|
||||
va_list& target;
|
||||
bool active = true;
|
||||
GuardVAList(va_list& target) : target(target), active(true) {}
|
||||
void clear() {
|
||||
if (active)
|
||||
va_end(target);
|
||||
active = false;
|
||||
}
|
||||
~GuardVAList() {
|
||||
if (active)
|
||||
va_end(target);
|
||||
}
|
||||
};
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
GuardVAList args_guard(args);
|
||||
|
||||
va_list args_cp;
|
||||
va_copy(args_cp, args);
|
||||
GuardVAList args_copy_guard(args_cp);
|
||||
|
||||
array<char, 256> local_buff;
|
||||
size_t size = local_buff.size();
|
||||
auto ret = ::vsnprintf(local_buff.data(), size, msg, args_cp);
|
||||
|
||||
args_copy_guard.clear();
|
||||
|
||||
// handle empty expansion
|
||||
if (ret == 0)
|
||||
return string{};
|
||||
if (static_cast<size_t>(ret) < size)
|
||||
return string(local_buff.data());
|
||||
|
||||
// we did not provide a long enough buffer on our first attempt.
|
||||
// add 1 to size to account for null-byte in size cast to prevent overflow
|
||||
size = static_cast<size_t>(ret) + 1;
|
||||
auto buff_ptr = unique_ptr<char[]>(new char[size]);
|
||||
ret = ::vsnprintf(buff_ptr.get(), size, msg, args);
|
||||
return string(buff_ptr.get());
|
||||
}
|
||||
|
||||
const char* unwrap(string const& s) { return s.c_str(); }
|
||||
const char* unwrap(path const& p) { return p.native().c_str(); }
|
||||
template <class Arg>
|
||||
Arg const& unwrap(Arg const& a) {
|
||||
static_assert(!is_class<Arg>::value, "cannot pass class here");
|
||||
return a;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
string format_string(const char* fmt, Args const&... args) {
|
||||
return format_string_imp(fmt, unwrap(args)...);
|
||||
}
|
||||
|
||||
error_code capture_errno() {
|
||||
_LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
|
||||
return error_code(errno, generic_category());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T error_value();
|
||||
template <>
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX11 void error_value<void>() {}
|
||||
template <>
|
||||
bool error_value<bool>() {
|
||||
return false;
|
||||
}
|
||||
template <>
|
||||
uintmax_t error_value<uintmax_t>() {
|
||||
return uintmax_t(-1);
|
||||
}
|
||||
template <>
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX11 file_time_type error_value<file_time_type>() {
|
||||
return file_time_type::min();
|
||||
}
|
||||
template <>
|
||||
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& m_ec) const {
|
||||
if (ec) {
|
||||
*ec = m_ec;
|
||||
return error_value<T>();
|
||||
}
|
||||
string what = string("in ") + func_name;
|
||||
switch (bool(p1) + bool(p2)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, m_ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1, m_ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1, *p2, m_ec);
|
||||
}
|
||||
_LIBCPP_UNREACHABLE();
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T report(const error_code& m_ec, const char* msg, Args const&... args) const {
|
||||
if (ec) {
|
||||
*ec = m_ec;
|
||||
return error_value<T>();
|
||||
}
|
||||
string what =
|
||||
string("in ") + func_name + ": " + format_string(msg, args...);
|
||||
switch (bool(p1) + bool(p2)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, m_ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1, m_ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1, *p2, m_ec);
|
||||
}
|
||||
_LIBCPP_UNREACHABLE();
|
||||
}
|
||||
|
||||
T report(errc const& err) const { return report(make_error_code(err)); }
|
||||
|
||||
template <class... Args>
|
||||
T report(errc const& err, const char* msg, Args const&... args) const {
|
||||
return report(make_error_code(err), msg, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
ErrorHandler(ErrorHandler const&) = delete;
|
||||
ErrorHandler& operator=(ErrorHandler const&) = delete;
|
||||
};
|
||||
|
||||
using chrono::duration;
|
||||
using chrono::duration_cast;
|
||||
|
||||
using TimeSpec = struct ::timespec;
|
||||
using StatT = struct ::stat;
|
||||
|
||||
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:
|
||||
#if _LIBCPP_STD_VER > 11 && !defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR)
|
||||
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();
|
||||
}
|
||||
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 _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out,
|
||||
ChronoType time) {
|
||||
using Lim = numeric_limits<CType>;
|
||||
if (time > Lim::max() || time < Lim::min())
|
||||
return false;
|
||||
*out = static_cast<CType>(time);
|
||||
return true;
|
||||
}
|
||||
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) {
|
||||
if (tm.tv_sec >= 0) {
|
||||
return tm.tv_sec < max_seconds ||
|
||||
(tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec);
|
||||
} else if (tm.tv_sec == (min_seconds - 1)) {
|
||||
return tm.tv_nsec >= min_nsec_timespec;
|
||||
} else {
|
||||
return tm.tv_sec >= min_seconds;
|
||||
}
|
||||
}
|
||||
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) {
|
||||
auto secs = duration_cast<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 _LIBCPP_CONSTEXPR_AFTER_CXX11 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 _LIBCPP_CONSTEXPR_AFTER_CXX11 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 _LIBCPP_CONSTEXPR_AFTER_CXX11 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);
|
||||
}
|
||||
};
|
||||
|
||||
using fs_time = time_util<file_time_type, time_t, TimeSpec>;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
|
||||
TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
|
||||
#else
|
||||
TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
|
||||
TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
|
||||
#endif
|
||||
|
||||
// allow the utimes implementation to compile even it we're not going
|
||||
// to use it.
|
||||
|
||||
bool posix_utimes(const path& p, std::array<TimeSpec, 2> const& TS,
|
||||
error_code& ec) {
|
||||
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);
|
||||
};
|
||||
struct ::timeval ConvertedTS[2] = {{TS[0].tv_sec, Convert(TS[0].tv_nsec)},
|
||||
{TS[1].tv_sec, Convert(TS[1].tv_nsec)}};
|
||||
if (::utimes(p.c_str(), ConvertedTS) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_LIBCPP_USE_UTIMENSAT)
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // end namespace detail
|
||||
|
||||
_LIBCPP_END_NAMESPACE_FILESYSTEM
|
||||
|
||||
#endif // FILESYSTEM_COMMON_H
|
||||
55
src/filesystem/int128_builtins.cpp
Normal file
55
src/filesystem/int128_builtins.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/*===-- int128_builtins.cpp - Implement __muloti4 --------------------------===
|
||||
*
|
||||
* The LLVM Compiler Infrastructure
|
||||
*
|
||||
* This file is dual licensed under the MIT and the University of Illinois Open
|
||||
* Source Licenses. See LICENSE.TXT for details.
|
||||
*
|
||||
* ===----------------------------------------------------------------------===
|
||||
*
|
||||
* 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")))
|
||||
__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
|
||||
1686
src/filesystem/operations.cpp
Normal file
1686
src/filesystem/operations.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user