From c7d3c84449f403716a8866e50491a1860a935b30 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Mon, 12 Jun 2023 10:43:55 -0700 Subject: [PATCH] [libc++] Split sources for The operations.cpp file contained the implementation of a ton of functionality unrelated to just the filesystem operations, and filesystem_common.h contained a lot of unrelated functionality as well. Splitting this up into more files will make it possible in the future to support parts of (e.g. path) on systems where there is no notion of a filesystem. Differential Revision: https://reviews.llvm.org/D152377 --- libcxx/src/CMakeLists.txt | 10 +- libcxx/src/filesystem/directory_entry.cpp | 74 ++ libcxx/src/filesystem/directory_iterator.cpp | 12 +- libcxx/src/filesystem/error.h | 246 +++++ libcxx/src/filesystem/file_descriptor.h | 283 +++++ libcxx/src/filesystem/filesystem_clock.cpp | 54 + libcxx/src/filesystem/filesystem_error.cpp | 37 + libcxx/src/filesystem/format_string.h | 89 ++ libcxx/src/filesystem/operations.cpp | 1145 +------------------- libcxx/src/filesystem/path.cpp | 460 ++++++++ libcxx/src/filesystem/path_parser.h | 379 +++++++ libcxx/src/filesystem/posix_compat.h | 50 +- .../{filesystem_common.h => time_utils.h} | 330 +----- .../directory_entry.mods/last_write_time.pass.cpp | 8 +- .../filesystems/convert_file_time.pass.cpp | 4 +- libcxx/utils/data/ignore_format.txt | 9 +- 16 files changed, 1732 insertions(+), 1458 deletions(-) create mode 100644 libcxx/src/filesystem/directory_entry.cpp create mode 100644 libcxx/src/filesystem/error.h create mode 100644 libcxx/src/filesystem/file_descriptor.h create mode 100644 libcxx/src/filesystem/filesystem_clock.cpp create mode 100644 libcxx/src/filesystem/filesystem_error.cpp create mode 100644 libcxx/src/filesystem/format_string.h create mode 100644 libcxx/src/filesystem/path.cpp create mode 100644 libcxx/src/filesystem/path_parser.h rename libcxx/src/filesystem/{filesystem_common.h => time_utils.h} (55%) diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 358a61e..dede386 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -110,10 +110,16 @@ endif() if (LIBCXX_ENABLE_FILESYSTEM) list(APPEND LIBCXX_SOURCES - filesystem/filesystem_common.h - filesystem/operations.cpp + filesystem/directory_entry.cpp filesystem/directory_iterator.cpp + filesystem/file_descriptor.h + filesystem/filesystem_clock.cpp + filesystem/filesystem_error.cpp + filesystem/operations.cpp + filesystem/path_parser.h + filesystem/path.cpp filesystem/posix_compat.h + filesystem/time_utils.h ) # Filesystem uses __int128_t, which requires a definition of __muloi4 when # compiled with UBSAN. This definition is not provided by libgcc_s, but is diff --git a/libcxx/src/filesystem/directory_entry.cpp b/libcxx/src/filesystem/directory_entry.cpp new file mode 100644 index 0000000..4910d39 --- /dev/null +++ b/libcxx/src/filesystem/directory_entry.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include +#include + +#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 (!_VSTD_FS::exists(st) || !_VSTD_FS::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 (_VSTD_FS::is_regular_file(st)) + __data_.__size_ = static_cast(full_st.st_size); + + if (_VSTD_FS::exists(st)) { + __data_.__nlink_ = static_cast(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 diff --git a/libcxx/src/filesystem/directory_iterator.cpp b/libcxx/src/filesystem/directory_iterator.cpp index 8d069cc..4a7e01b 100644 --- a/libcxx/src/filesystem/directory_iterator.cpp +++ b/libcxx/src/filesystem/directory_iterator.cpp @@ -11,8 +11,18 @@ #include #include #include +#include -#include "filesystem_common.h" +#include "error.h" +#include "file_descriptor.h" + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +#else +# include // for DIR & friends +#endif _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM diff --git a/libcxx/src/filesystem/error.h b/libcxx/src/filesystem/error.h new file mode 100644 index 0000000..7269600 --- /dev/null +++ b/libcxx/src/filesystem/error.h @@ -0,0 +1,246 @@ +//===----------------------------------------------------------------------===//// +// +// 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 +#include +#include +#include +#include +#include +#include +#include // __libcpp_unreachable + +#include "format_string.h" + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include // ERROR_* macros +#endif + +// TODO: Check whether these functions actually need internal linkage, or if they can be made normal header functions +_LIBCPP_DIAGNOSTIC_PUSH +_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +namespace detail { +namespace { + +#if defined(_LIBCPP_WIN32API) + +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(err)) + return pair.errc; + return errc::invalid_argument; +} + +#endif // _LIBCPP_WIN32API + +error_code capture_errno() { + _LIBCPP_ASSERT(errno != 0, "Expected errno to be non-zero"); + return error_code(errno, generic_category()); +} + +#if defined(_LIBCPP_WIN32API) +error_code make_windows_error(int err) { + return make_error_code(__win_err_to_errc(err)); +} +#endif + +template +T error_value(); +template <> +_LIBCPP_CONSTEXPR_SINCE_CXX14 void error_value() {} +template <> +bool error_value() { + return false; +} +#if __SIZEOF_SIZE_T__ != __SIZEOF_LONG_LONG__ +template <> +size_t error_value() { + return size_t(-1); +} +#endif +template <> +uintmax_t error_value() { + return uintmax_t(-1); +} +template <> +_LIBCPP_CONSTEXPR_SINCE_CXX14 file_time_type error_value() { + return file_time_type::min(); +} +template <> +path error_value() { + return {}; +} + +template +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(); + } + 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 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(); + } + +private: + ErrorHandler(ErrorHandler const&) = delete; + ErrorHandler& operator=(ErrorHandler const&) = delete; +}; + +} // end anonymous namespace +} // end namespace detail + +_LIBCPP_END_NAMESPACE_FILESYSTEM + +#endif // FILESYSTEM_ERROR_H diff --git a/libcxx/src/filesystem/file_descriptor.h b/libcxx/src/filesystem/file_descriptor.h new file mode 100644 index 0000000..e952042 --- /dev/null +++ b/libcxx/src/filesystem/file_descriptor.h @@ -0,0 +1,283 @@ +//===----------------------------------------------------------------------===//// +// +// 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 +#include +#include +#include +#include + +#include "error.h" +#include "posix_compat.h" + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +#else +# include // for DIR & friends +# include // values for fchmodat +# include +# include +# include +#endif // defined(_LIBCPP_WIN32API) + +// TODO: Check whether these functions actually need internal linkage, or if they can be made normal header functions +_LIBCPP_DIAGNOSTIC_PUSH +_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +namespace detail { +namespace { + +#if !defined(_LIBCPP_WIN32API) + +#if defined(DT_BLK) +template +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; +} +#endif // defined(DT_BLK) + +template +static file_type get_file_type(DirEntT*, long) { + return file_type::none; +} + +static pair 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 + +static 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; +} +static uintmax_t get_file_size(const WIN32_FIND_DATAW& data) { + return (static_cast(data.nFileSizeHigh) << 32) + data.nFileSizeLow; +} +static 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 + static FileDescriptor create(const path* p, error_code& ec, Args... args) { + ec.clear(); + int fd; + if ((fd = detail::open(p->c_str(), args...)) == -1) { + ec = capture_errno(); + return FileDescriptor{p}; + } + return FileDescriptor(p, fd); + } + + template + static FileDescriptor create_with_status(const path* p, error_code& ec, + Args... args) { + FileDescriptor fd = create(p, ec, args...); + if (!ec) + fd.refresh_status(ec); + + return fd; + } + + file_status get_status() const { return m_status; } + StatT const& get_stat() const { return m_stat; } + + bool status_known() const { return _VSTD_FS::status_known(m_status); } + + file_status refresh_status(error_code& ec); + + void close() noexcept { + if (fd != -1) + detail::close(fd); + fd = -1; + } + + FileDescriptor(FileDescriptor&& other) + : name(other.name), fd(other.fd), m_stat(other.m_stat), + m_status(other.m_status) { + other.fd = -1; + other.m_status = file_status{}; + } + + ~FileDescriptor() { close(); } + + FileDescriptor(FileDescriptor const&) = delete; + FileDescriptor& operator=(FileDescriptor const&) = delete; + +private: + explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {} +}; + +perms posix_get_perms(const StatT& st) noexcept { + return static_cast(st.st_mode) & perms::mask; +} + +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 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; +} + +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); +} + +file_status posix_stat(path const& p, error_code* ec) { + StatT path_stat; + return posix_stat(p, path_stat, ec); +} + +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); +} + +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 +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; +} + +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; +} + +bool stat_equivalent(const StatT& st1, const StatT& st2) { + return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); +} + +file_status FileDescriptor::refresh_status(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; +} + +} // namespace +} // end namespace detail + +_LIBCPP_END_NAMESPACE_FILESYSTEM + +_LIBCPP_DIAGNOSTIC_POP + +#endif // FILESYSTEM_FILE_DESCRIPTOR_H diff --git a/libcxx/src/filesystem/filesystem_clock.cpp b/libcxx/src/filesystem/filesystem_clock.cpp new file mode 100644 index 0000000..c6b3c89 --- /dev/null +++ b/libcxx/src/filesystem/filesystem_clock.cpp @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include +#include + +#include "posix_compat.h" // TimeSpec + +#if defined(_LIBCPP_WIN32API) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +#endif + +#if !defined(CLOCK_REALTIME) && !defined(_LIBCPP_WIN32API) +# include // for gettimeofday and timeval +#endif + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +const bool _FilesystemClock::is_steady; + +_FilesystemClock::time_point _FilesystemClock::now() noexcept { + typedef chrono::duration __secs; +#if defined(_LIBCPP_WIN32API) + typedef chrono::duration __nsecs; + FILETIME time; + GetSystemTimeAsFileTime(&time); + detail::TimeSpec tp = detail::filetime_to_timespec(time); + return time_point(__secs(tp.tv_sec) + + chrono::duration_cast(__nsecs(tp.tv_nsec))); +#elif defined(CLOCK_REALTIME) + typedef chrono::duration __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(__nsecs(tp.tv_nsec))); +#else + typedef chrono::duration __microsecs; + timeval tv; + gettimeofday(&tv, 0); + return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec)); +#endif // CLOCK_REALTIME +} + +_LIBCPP_END_NAMESPACE_FILESYSTEM diff --git a/libcxx/src/filesystem/filesystem_error.cpp b/libcxx/src/filesystem/filesystem_error.cpp new file mode 100644 index 0000000..5faed3b --- /dev/null +++ b/libcxx/src/filesystem/filesystem_error.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include + +#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 diff --git a/libcxx/src/filesystem/format_string.h b/libcxx/src/filesystem/format_string.h new file mode 100644 index 0000000..d95d9bd --- /dev/null +++ b/libcxx/src/filesystem/format_string.h @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===//// +// +// 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 +#include +#include +#include +#include + +#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 + +// TODO: Check whether these functions actually need internal linkage, or if they can be made normal header functions +_LIBCPP_DIAGNOSTIC_PUSH +_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +namespace detail { +namespace { + +_LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) +string vformat_string(const char* msg, va_list ap) { + array 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(ret) < buf.size()) { + result.assign(buf.data(), static_cast(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(ret) + 1; + result.__resize_default_init(size_with_null - 1); + ret = ::vsnprintf(&result[0], size_with_null, msg, ap); + _LIBCPP_ASSERT(static_cast(ret) == (size_with_null - 1), "TODO"); + } + return result; +} + +_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 anonymous namespace +} // end namespace detail + +_LIBCPP_END_NAMESPACE_FILESYSTEM + +_LIBCPP_DIAGNOSTIC_POP + +#endif // FILESYSTEM_FORMAT_STRING_H diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp index af31fe0..f6c3ff1 100644 --- a/libcxx/src/filesystem/operations.cpp +++ b/libcxx/src/filesystem/operations.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include <__assert> +#include <__config> #include <__utility/unreachable.h> #include #include @@ -17,9 +18,11 @@ #include #include -#include "filesystem_common.h" - +#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 @@ -45,595 +48,12 @@ # define _LIBCPP_FILESYSTEM_USE_FSTREAM #endif -#if !defined(CLOCK_REALTIME) && !defined(_LIBCPP_WIN32API) -# include // for gettimeofday and timeval -#endif - #if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB) # pragma comment(lib, "rt") #endif _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM -namespace { - -bool isSeparator(path::value_type C) { - if (C == '/') - return true; -#if defined(_LIBCPP_WIN32API) - if (C == '\\') - return true; -#endif - return false; -} - -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; -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(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; - } -}; - -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)}; -} - -string_view_t createView(PosPtr S, PosPtr E) noexcept { - return {S, static_cast(E - S) + 1}; -} - -} // namespace parser -} // namespace - -// POSIX HELPERS - -#if defined(_LIBCPP_WIN32API) -namespace detail { - -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(err)) - return pair.errc; - return errc::invalid_argument; -} - -} // namespace detail -#endif - -namespace detail { -namespace { - -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 - static FileDescriptor create(const path* p, error_code& ec, Args... args) { - ec.clear(); - int fd; - if ((fd = detail::open(p->c_str(), args...)) == -1) { - ec = capture_errno(); - return FileDescriptor{p}; - } - return FileDescriptor(p, fd); - } - - template - static FileDescriptor create_with_status(const path* p, error_code& ec, - Args... args) { - FileDescriptor fd = create(p, ec, args...); - if (!ec) - fd.refresh_status(ec); - - return fd; - } - - file_status get_status() const { return m_status; } - StatT const& get_stat() const { return m_stat; } - - bool status_known() const { return _VSTD_FS::status_known(m_status); } - - file_status refresh_status(error_code& ec); - - void close() noexcept { - if (fd != -1) - detail::close(fd); - fd = -1; - } - - FileDescriptor(FileDescriptor&& other) - : name(other.name), fd(other.fd), m_stat(other.m_stat), - m_status(other.m_status) { - other.fd = -1; - other.m_status = file_status{}; - } - - ~FileDescriptor() { close(); } - - FileDescriptor(FileDescriptor const&) = delete; - FileDescriptor& operator=(FileDescriptor const&) = delete; - -private: - explicit FileDescriptor(const path* p, int fd = -1) : name(*p), fd(fd) {} -}; - -perms posix_get_perms(const StatT& st) noexcept { - return static_cast(st.st_mode) & perms::mask; -} - -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 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; -} - -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); -} - -file_status posix_stat(path const& p, error_code* ec) { - StatT path_stat; - return posix_stat(p, path_stat, ec); -} - -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); -} - -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 -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; -} - -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; -} - -bool stat_equivalent(const StatT& st1, const StatT& st2) { - return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); -} - -file_status FileDescriptor::refresh_status(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; -} -} // namespace -} // end namespace detail - using detail::capture_errno; using detail::ErrorHandler; using detail::StatT; @@ -642,51 +62,6 @@ using parser::createView; using parser::PathParser; using parser::string_view_t; -const bool _FilesystemClock::is_steady; - -_FilesystemClock::time_point _FilesystemClock::now() noexcept { - typedef chrono::duration __secs; -#if defined(_LIBCPP_WIN32API) - typedef chrono::duration __nsecs; - FILETIME time; - GetSystemTimeAsFileTime(&time); - TimeSpec tp = detail::filetime_to_timespec(time); - return time_point(__secs(tp.tv_sec) + - chrono::duration_cast(__nsecs(tp.tv_nsec))); -#elif defined(CLOCK_REALTIME) - typedef chrono::duration __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(__nsecs(tp.tv_nsec))); -#else - typedef chrono::duration __microsecs; - timeval tv; - gettimeofday(&tv, 0); - return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec)); -#endif // CLOCK_REALTIME -} - -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(); - }(); -} - static path __do_absolute(const path& p, path* cwd, error_code* ec) { if (ec) ec->clear(); @@ -1193,18 +568,6 @@ bool __fs_is_empty(const path& p, error_code* ec) { __libcpp_unreachable(); } -static file_time_type __extract_last_write_time(const path& p, const StatT& st, - error_code* ec) { - using detail::fs_time; - ErrorHandler 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); -} - file_time_type __last_write_time(const path& p, error_code* ec) { using namespace chrono; ErrorHandler err("last_write_time", ec, &p); @@ -1214,7 +577,7 @@ file_time_type __last_write_time(const path& p, error_code* ec) { detail::posix_stat(p, st, &m_ec); if (m_ec) return err.report(m_ec); - return __extract_last_write_time(p, st, 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) { @@ -1609,500 +972,4 @@ path __weakly_canonical(const path& p, error_code* ec) { return result.lexically_normal(); } -/////////////////////////////////////////////////////////////////////////////// -// 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; - vector 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 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(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 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 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 - - -/////////////////////////////////////////////////////////////////////////////// -// directory entry definitions -/////////////////////////////////////////////////////////////////////////////// - -error_code directory_entry::__do_refresh() noexcept { - __data_.__reset(); - error_code failure_ec; - - StatT full_st; - file_status st = detail::posix_lstat(__p_, full_st, &failure_ec); - if (!status_known(st)) { - __data_.__reset(); - return failure_ec; - } - - if (!_VSTD_FS::exists(st) || !_VSTD_FS::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 (_VSTD_FS::is_regular_file(st)) - __data_.__size_ = static_cast(full_st.st_size); - - if (_VSTD_FS::exists(st)) { - __data_.__nlink_ = static_cast(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_ = - __extract_last_write_time(__p_, full_st, &ignored_ec); - } - - return failure_ec; -} - _LIBCPP_END_NAMESPACE_FILESYSTEM diff --git a/libcxx/src/filesystem/path.cpp b/libcxx/src/filesystem/path.cpp new file mode 100644 index 0000000..82f1ba7 --- /dev/null +++ b/libcxx/src/filesystem/path.cpp @@ -0,0 +1,460 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include + +#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; + vector 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 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(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 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 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 diff --git a/libcxx/src/filesystem/path_parser.h b/libcxx/src/filesystem/path_parser.h new file mode 100644 index 0000000..4cc75a5 --- /dev/null +++ b/libcxx/src/filesystem/path_parser.h @@ -0,0 +1,379 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include +#include + +#include "format_string.h" + +// TODO: Check whether these functions actually need internal linkage, or if they can be made normal header functions +_LIBCPP_DIAGNOSTIC_PUSH +_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") + +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM + +namespace { + +bool isSeparator(path::value_type C) { + if (C == '/') + return true; +#if defined(_LIBCPP_WIN32API) + if (C == '\\') + return true; +#endif + return false; +} + +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; +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(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; + } +}; + +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)}; +} + +string_view_t createView(PosPtr S, PosPtr E) noexcept { + return {S, static_cast(E - S) + 1}; +} + +} // namespace parser +} // namespace + +_LIBCPP_END_NAMESPACE_FILESYSTEM + +_LIBCPP_DIAGNOSTIC_POP + +#endif // PATH_PARSER_H diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h index 36116ec..bddead7 100644 --- a/libcxx/src/filesystem/posix_compat.h +++ b/libcxx/src/filesystem/posix_compat.h @@ -24,9 +24,10 @@ #define POSIX_COMPAT_H #include <__assert> +#include <__config> #include -#include "filesystem_common.h" +#include "error.h" #if defined(_LIBCPP_WIN32API) # define WIN32_LEAN_AND_MEAN @@ -35,10 +36,12 @@ # include # include #else +# include # include # include # include #endif +#include #include #if defined(_LIBCPP_WIN32API) @@ -71,12 +74,53 @@ struct LIBCPP_REPARSE_DATA_BUFFER { }; #endif +// TODO: Check whether these functions actually need internal linkage, or if they can be made normal header functions +_LIBCPP_DIAGNOSTIC_PUSH +_LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") +_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") + _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM namespace detail { namespace { #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; +}; + +#else +using TimeSpec = struct timespec; +using TimeVal = struct timeval; +using StatT = struct stat; +#endif + +#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 @@ -400,7 +444,7 @@ int fchmod_handle(HANDLE h, int perms) { return 0; } -int fchmodat(int fd, const wchar_t *path, int perms, int flag) { +int fchmodat(int /*fd*/, const wchar_t *path, int perms, int flag) { DWORD attributes = GetFileAttributesW(path); if (attributes == INVALID_FILE_ATTRIBUTES) return set_errno(); @@ -519,4 +563,6 @@ using SSizeT = ::ssize_t; _LIBCPP_END_NAMESPACE_FILESYSTEM +_LIBCPP_DIAGNOSTIC_POP + #endif // POSIX_COMPAT_H diff --git a/libcxx/src/filesystem/filesystem_common.h b/libcxx/src/filesystem/time_utils.h similarity index 55% rename from libcxx/src/filesystem/filesystem_common.h rename to libcxx/src/filesystem/time_utils.h index a1756cf..2e299b8 100644 --- a/libcxx/src/filesystem/filesystem_common.h +++ b/libcxx/src/filesystem/time_utils.h @@ -6,34 +6,30 @@ // //===----------------------------------------------------------------------===//// -#ifndef FILESYSTEM_COMMON_H -#define FILESYSTEM_COMMON_H +#ifndef FILESYSTEM_TIME_UTILS_H +#define FILESYSTEM_TIME_UTILS_H -#include <__assert> #include <__config> #include -#include #include -#include -#include -#include #include +#include #include -#include #include +#include +#include + +#include "error.h" +#include "format_string.h" +#include "posix_compat.h" #if defined(_LIBCPP_WIN32API) # define WIN32_LEAN_AND_MEAN # define NOMINMAX # include #else -# include // for DIR & friends -# include /* values for fchmodat */ -# include -# include # include // for ::utimes as used in __last_write_time -# include -#endif // defined(_LIBCPP_WIN32API) +#endif // We can use the presence of UTIME_OMIT to detect platforms that provide utimensat. #if defined(UTIME_OMIT) @@ -46,240 +42,14 @@ _LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wunused-function") _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-function") _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wunused-template") -#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 { - -#if defined(_LIBCPP_WIN32API) -// Non anonymous, to allow access from two translation units. -errc __win_err_to_errc(int err); -#endif - namespace { -static _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 1, 0) string -format_string_impl(const char* msg, va_list ap) { - array 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(ret) < buf.size()) { - result.assign(buf.data(), static_cast(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(ret) + 1; - result.__resize_default_init(size_with_null - 1); - ret = ::vsnprintf(&result[0], size_with_null, msg, ap); - _LIBCPP_ASSERT(static_cast(ret) == (size_with_null - 1), "TODO"); - } - return result; -} - -static _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 = format_string_impl(msg, ap); -#ifndef _LIBCPP_HAS_NO_EXCEPTIONS - } catch (...) { - va_end(ap); - throw; - } -#endif // _LIBCPP_HAS_NO_EXCEPTIONS - va_end(ap); - return ret; -} - -error_code capture_errno() { - _LIBCPP_ASSERT(errno != 0, "Expected errno to be non-zero"); - return error_code(errno, generic_category()); -} - -#if defined(_LIBCPP_WIN32API) -error_code make_windows_error(int err) { - return make_error_code(__win_err_to_errc(err)); -} -#endif - -template -T error_value(); -template <> -_LIBCPP_CONSTEXPR_SINCE_CXX14 void error_value() {} -template <> -bool error_value() { - return false; -} -#if __SIZEOF_SIZE_T__ != __SIZEOF_LONG_LONG__ -template <> -size_t error_value() { - return size_t(-1); -} -#endif -template <> -uintmax_t error_value() { - return uintmax_t(-1); -} -template <> -_LIBCPP_CONSTEXPR_SINCE_CXX14 file_time_type error_value() { - return file_time_type::min(); -} -template <> -path error_value() { - return {}; -} - -template -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(); - } - 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_ + ": " + format_string_impl(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 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(); - } - -private: - ErrorHandler(ErrorHandler const&) = delete; - ErrorHandler& operator=(ErrorHandler const&) = delete; -}; - using chrono::duration; using chrono::duration_cast; -#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 (in the anonymous detail namespace -// in posix_compat.h) 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; -}; - -#else -using TimeSpec = struct timespec; -using TimeVal = struct timeval; -using StatT = struct stat; -#endif - template ::value> struct time_util_base { @@ -307,7 +77,7 @@ struct time_util_base { .count(); private: - static _LIBCPP_CONSTEXPR_SINCE_CXX14 fs_duration get_min_nsecs() { + static _LIBCPP_CONSTEXPR fs_duration get_min_nsecs() { return duration_cast( fs_nanoseconds(min_nsec_timespec) - duration_cast(fs_seconds(1))); @@ -327,7 +97,9 @@ private: return max_seconds >= numeric_limits::max() && min_seconds <= numeric_limits::min(); } +#if _LIBCPP_STD_VER >= 14 static_assert(check_range(), "the representable range is unacceptable small"); +#endif }; template @@ -531,75 +303,19 @@ bool set_file_times(const path& p, std::array const& TS, #endif } -#if defined(DT_BLK) -template -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; -} -#endif // defined(DT_BLK) - -template -static file_type get_file_type(DirEntT*, long) { - return file_type::none; -} +#endif // !_LIBCPP_WIN32API -static pair 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)}; - } -} +file_time_type __extract_last_write_time(const path& p, const StatT& st, + error_code* ec) { + using detail::fs_time; + ErrorHandler err("last_write_time", ec, &p); -#else // _LIBCPP_WIN32API + auto ts = detail::extract_mtime(st); + if (!fs_time::is_representable(ts)) + return err.report(errc::value_too_large); -static 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; + return fs_time::convert_from_timespec(ts); } -static uintmax_t get_file_size(const WIN32_FIND_DATAW& data) { - return (static_cast(data.nFileSizeHigh) << 32) + data.nFileSizeLow; -} -static 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 } // namespace } // end namespace detail @@ -608,4 +324,4 @@ _LIBCPP_END_NAMESPACE_FILESYSTEM _LIBCPP_DIAGNOSTIC_POP -#endif // FILESYSTEM_COMMON_H +#endif // FILESYSTEM_TIME_UTILS_H diff --git a/libcxx/test/libcxx/input.output/filesystems/class.directory_entry/directory_entry.mods/last_write_time.pass.cpp b/libcxx/test/libcxx/input.output/filesystems/class.directory_entry/directory_entry.mods/last_write_time.pass.cpp index 4c6ac40..e581963 100644 --- a/libcxx/test/libcxx/input.output/filesystems/class.directory_entry/directory_entry.mods/last_write_time.pass.cpp +++ b/libcxx/test/libcxx/input.output/filesystems/class.directory_entry/directory_entry.mods/last_write_time.pass.cpp @@ -7,10 +7,10 @@ //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03 -// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../../../src/filesystem +// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../../../src -// This test relies on calling functions from the libcxx internal header -// filesystem_common.h; the Windows implementation uses different +// This test relies on calling functions from the libcxx internal headers +// of ; the Windows implementation uses different // internals and doesn't provide the same set_file_times function as for // other platforms. // UNSUPPORTED: windows @@ -30,7 +30,7 @@ #include "test_macros.h" #include "filesystem_test_helper.h" -#include "filesystem_common.h" +#include "filesystem/time_utils.h" using namespace fs::detail; diff --git a/libcxx/test/libcxx/input.output/filesystems/convert_file_time.pass.cpp b/libcxx/test/libcxx/input.output/filesystems/convert_file_time.pass.cpp index b86282c..2d057cb 100644 --- a/libcxx/test/libcxx/input.output/filesystems/convert_file_time.pass.cpp +++ b/libcxx/test/libcxx/input.output/filesystems/convert_file_time.pass.cpp @@ -12,7 +12,7 @@ // typedef TrivialClock file_time_type; -// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../src/filesystem -Wno-macro-redefined +// ADDITIONAL_COMPILE_FLAGS: -I %S/../../../../src -Wno-macro-redefined #include #include @@ -22,7 +22,7 @@ #include #include -#include "filesystem_common.h" +#include "filesystem/time_utils.h" #ifndef __SIZEOF_INT128__ #define TEST_HAS_NO_INT128_T diff --git a/libcxx/utils/data/ignore_format.txt b/libcxx/utils/data/ignore_format.txt index 2281cc2..dfe30a2 100644 --- a/libcxx/utils/data/ignore_format.txt +++ b/libcxx/utils/data/ignore_format.txt @@ -790,11 +790,18 @@ libcxx/src/condition_variable_destructor.cpp libcxx/src/debug.cpp libcxx/src/exception.cpp libcxx/src/experimental/memory_resource.cpp +libcxx/src/filesystem/directory_entry.cpp libcxx/src/filesystem/directory_iterator.cpp -libcxx/src/filesystem/filesystem_common.h +libcxx/src/filesystem/error.h +libcxx/src/filesystem/file_descriptor.h +libcxx/src/filesystem/filesystem_clock.cpp +libcxx/src/filesystem/filesystem_error.cpp libcxx/src/filesystem/int128_builtins.cpp libcxx/src/filesystem/operations.cpp +libcxx/src/filesystem/path.cpp +libcxx/src/filesystem/path_parser.h libcxx/src/filesystem/posix_compat.h +libcxx/src/filesystem/time_utils.h libcxx/src/functional.cpp libcxx/src/future.cpp libcxx/src/hash.cpp -- 2.7.4