| From 4655075efa11ee17c28493d13760d51e360f7ddd Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 16:59:07 +0200 |
| Subject: [PATCH 01/24] [libcxx] Implement the stat function family on top of |
| native windows APIs |
| |
| While the windows CRTs (the modern UCRT, and the legacy msvcrt.dll |
| that mingw still often defaults to) do provide stat functions, they're |
| a bit lacking - they only provide second precision on the modification |
| time, lack support for symlinks and a few other details. |
| |
| Instead reimplement them using a couple windows native functions, |
| getting exactly the info we need. (Technically, the implementation |
| within the CRT calls these functions anyway.) |
| |
| If we only need a few fields, we could also do with fewer calls, as a |
| later optimization. |
| |
| Differential Revision: https://reviews.llvm.org/D91141 |
| |
| (cherry picked from commit 2ff8662b5d16129ec6d1ee60dcec4f6ff8f717e2) |
| --- |
| src/CMakeLists.txt | 1 + |
| src/filesystem/filesystem_common.h | 32 ++++ |
| src/filesystem/operations.cpp | 10 +- |
| src/filesystem/posix_compat.h | 187 ++++++++++++++++++++++ |
| 4 files changed, 226 insertions(+), 4 deletions(-) |
| create mode 100644 src/filesystem/posix_compat.h |
| |
| diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt |
| index 9965104cb5b2..0d95e766100d 100644 |
| --- a/src/CMakeLists.txt |
| +++ b/src/CMakeLists.txt |
| @@ -91,6 +91,7 @@ if (LIBCXX_ENABLE_FILESYSTEM) |
| filesystem/filesystem_common.h |
| filesystem/operations.cpp |
| filesystem/directory_iterator.cpp |
| + filesystem/posix_compat.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/src/filesystem/filesystem_common.h b/src/filesystem/filesystem_common.h |
| index e0fdbccf96b1..8204e9a2e5f8 100644 |
| --- a/src/filesystem/filesystem_common.h |
| +++ b/src/filesystem/filesystem_common.h |
| @@ -224,9 +224,41 @@ private: |
| 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 <class FileTimeT, class TimeT, |
| bool IsFloat = is_floating_point<typename FileTimeT::rep>::value> |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 50a895dc2fae..548a0273ce71 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -17,6 +17,8 @@ |
| |
| #include "filesystem_common.h" |
| |
| +#include "posix_compat.h" |
| + |
| #if defined(_LIBCPP_WIN32API) |
| # define WIN32_LEAN_AND_MEAN |
| # define NOMINMAX |
| @@ -495,7 +497,7 @@ file_status create_file_status(error_code& m_ec, path const& p, |
| |
| file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) { |
| error_code m_ec; |
| - if (::stat(p.c_str(), &path_stat) == -1) |
| + if (detail::stat(p.c_str(), &path_stat) == -1) |
| m_ec = detail::capture_errno(); |
| return create_file_status(m_ec, p, path_stat, ec); |
| } |
| @@ -507,7 +509,7 @@ file_status posix_stat(path const& p, error_code* ec) { |
| |
| file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) { |
| error_code m_ec; |
| - if (::lstat(p.c_str(), &path_stat) == -1) |
| + if (detail::lstat(p.c_str(), &path_stat) == -1) |
| m_ec = detail::capture_errno(); |
| return create_file_status(m_ec, p, path_stat, ec); |
| } |
| @@ -545,7 +547,7 @@ file_status FileDescriptor::refresh_status(error_code& ec) { |
| m_status = file_status{}; |
| m_stat = {}; |
| error_code m_ec; |
| - if (::fstat(fd, &m_stat) == -1) |
| + 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; |
| @@ -1197,7 +1199,7 @@ path __read_symlink(const path& p, error_code* ec) { |
| auto buff = std::unique_ptr<char[], NullDeleter>(stack_buff); |
| #else |
| StatT sb; |
| - if (::lstat(p.c_str(), &sb) == -1) { |
| + if (detail::lstat(p.c_str(), &sb) == -1) { |
| return err.report(capture_errno()); |
| } |
| const size_t size = sb.st_size + 1; |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| new file mode 100644 |
| index 000000000000..3eec4634e929 |
| --- /dev/null |
| +++ b/src/filesystem/posix_compat.h |
| @@ -0,0 +1,187 @@ |
| +//===----------------------------------------------------------------------===// |
| +// |
| +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| +// See https://llvm.org/LICENSE.txt for license information. |
| +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| +// |
| +//===----------------------------------------------------------------------===// |
| + |
| +// |
| +// POSIX-like portability helper functions. |
| +// |
| +// These generally behave like the proper posix functions, with these |
| +// exceptions: |
| +// On Windows, they take paths in wchar_t* form, instead of char* form. |
| +// |
| +// These are provided within an anonymous namespace within the detail |
| +// namespace - callers need to include this header and call them as |
| +// detail::function(), regardless of platform. |
| +// |
| + |
| +#ifndef POSIX_COMPAT_H |
| +#define POSIX_COMPAT_H |
| + |
| +#include "filesystem" |
| + |
| +#include "filesystem_common.h" |
| + |
| +#if defined(_LIBCPP_WIN32API) |
| +# define WIN32_LEAN_AND_MEAN |
| +# define NOMINMAX |
| +# include <windows.h> |
| +# include <io.h> |
| +#else |
| +# include <unistd.h> |
| +# include <sys/stat.h> |
| +# include <sys/statvfs.h> |
| +#endif |
| +#include <time.h> |
| + |
| +_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM |
| + |
| +namespace detail { |
| +namespace { |
| + |
| +#if defined(_LIBCPP_WIN32API) |
| + |
| +// Various C runtime header sets provide more or less of these. As we |
| +// provide our own implementation, undef all potential defines from the |
| +// C runtime headers and provide a complete set of macros of our own. |
| + |
| +#undef _S_IFMT |
| +#undef _S_IFDIR |
| +#undef _S_IFCHR |
| +#undef _S_IFIFO |
| +#undef _S_IFREG |
| +#undef _S_IFBLK |
| +#undef _S_IFLNK |
| +#undef _S_IFSOCK |
| + |
| +#define _S_IFMT 0xF000 |
| +#define _S_IFDIR 0x4000 |
| +#define _S_IFCHR 0x2000 |
| +#define _S_IFIFO 0x1000 |
| +#define _S_IFREG 0x8000 |
| +#define _S_IFBLK 0x6000 |
| +#define _S_IFLNK 0xA000 |
| +#define _S_IFSOCK 0xC000 |
| + |
| +#undef S_ISDIR |
| +#undef S_ISFIFO |
| +#undef S_ISCHR |
| +#undef S_ISREG |
| +#undef S_ISLNK |
| +#undef S_ISBLK |
| +#undef S_ISSOCK |
| + |
| +#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) |
| +#define S_ISCHR(m) (((m) & _S_IFMT) == _S_IFCHR) |
| +#define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) |
| +#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) |
| +#define S_ISBLK(m) (((m) & _S_IFMT) == _S_IFBLK) |
| +#define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) |
| +#define S_ISSOCK(m) (((m) & _S_IFMT) == _S_IFSOCK) |
| + |
| + |
| +// There were 369 years and 89 leap days from the Windows epoch |
| +// (1601) to the Unix epoch (1970). |
| +#define FILE_TIME_OFFSET_SECS (uint64_t(369 * 365 + 89) * (24 * 60 * 60)) |
| + |
| +TimeSpec filetime_to_timespec(LARGE_INTEGER li) { |
| + TimeSpec ret; |
| + ret.tv_sec = li.QuadPart / 10000000 - FILE_TIME_OFFSET_SECS; |
| + ret.tv_nsec = (li.QuadPart % 10000000) * 100; |
| + return ret; |
| +} |
| + |
| +int set_errno(int e = GetLastError()) { |
| + errno = static_cast<int>(__win_err_to_errc(e)); |
| + return -1; |
| +} |
| + |
| +class WinHandle { |
| +public: |
| + WinHandle(const wchar_t *p, DWORD access, DWORD flags) { |
| + h = CreateFileW( |
| + p, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr); |
| + } |
| + ~WinHandle() { |
| + if (h != INVALID_HANDLE_VALUE) |
| + CloseHandle(h); |
| + } |
| + operator HANDLE() const { return h; } |
| + operator bool() const { return h != INVALID_HANDLE_VALUE; } |
| + |
| +private: |
| + HANDLE h; |
| +}; |
| + |
| +int stat_handle(HANDLE h, StatT *buf) { |
| + FILE_BASIC_INFO basic; |
| + if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) |
| + return set_errno(); |
| + memset(buf, 0, sizeof(*buf)); |
| + buf->st_mtim = filetime_to_timespec(basic.LastWriteTime); |
| + buf->st_atim = filetime_to_timespec(basic.LastAccessTime); |
| + buf->st_mode = 0555; // Read-only |
| + if (!(basic.FileAttributes & FILE_ATTRIBUTE_READONLY)) |
| + buf->st_mode |= 0222; // Write |
| + if (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
| + buf->st_mode |= _S_IFDIR; |
| + } else { |
| + buf->st_mode |= _S_IFREG; |
| + } |
| + if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { |
| + FILE_ATTRIBUTE_TAG_INFO tag; |
| + if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, |
| + sizeof(tag))) |
| + return set_errno(); |
| + if (tag.ReparseTag == IO_REPARSE_TAG_SYMLINK) |
| + buf->st_mode = (buf->st_mode & ~_S_IFMT) | _S_IFLNK; |
| + } |
| + FILE_STANDARD_INFO standard; |
| + if (!GetFileInformationByHandleEx(h, FileStandardInfo, &standard, |
| + sizeof(standard))) |
| + return set_errno(); |
| + buf->st_nlink = standard.NumberOfLinks; |
| + buf->st_size = standard.EndOfFile.QuadPart; |
| + BY_HANDLE_FILE_INFORMATION info; |
| + if (!GetFileInformationByHandle(h, &info)) |
| + return set_errno(); |
| + buf->st_dev = info.dwVolumeSerialNumber; |
| + memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4); |
| + memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4); |
| + return 0; |
| +} |
| + |
| +int stat_file(const wchar_t *path, StatT *buf, DWORD flags) { |
| + WinHandle h(path, FILE_READ_ATTRIBUTES, flags); |
| + if (!h) |
| + return set_errno(); |
| + int ret = stat_handle(h, buf); |
| + return ret; |
| +} |
| + |
| +int stat(const wchar_t *path, StatT *buf) { return stat_file(path, buf, 0); } |
| + |
| +int lstat(const wchar_t *path, StatT *buf) { |
| + return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT); |
| +} |
| + |
| +int fstat(int fd, StatT *buf) { |
| + HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
| + return stat_handle(h, buf); |
| +} |
| +#else |
| +using ::fstat; |
| +using ::lstat; |
| +using ::stat; |
| +#endif |
| + |
| +} // namespace |
| +} // end namespace detail |
| + |
| +_LIBCPP_END_NAMESPACE_FILESYSTEM |
| + |
| +#endif // POSIX_COMPAT_H |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From ab432bcd28596265e97eb062a19cbbbdc3d426f5 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 22:56:03 +0200 |
| Subject: [PATCH 02/24] [libcxx] Implement _FilesystemClock::now() and |
| __last_write_time for windows |
| |
| Differential Revision: https://reviews.llvm.org/D91142 |
| |
| (cherry picked from commit 592d62352933d34af62334d172c6fc665933e0de) |
| --- |
| src/filesystem/filesystem_common.h | 6 ++++++ |
| src/filesystem/operations.cpp | 23 +++++++++++++++++++++-- |
| src/filesystem/posix_compat.h | 17 +++++++++++++++++ |
| 3 files changed, 44 insertions(+), 2 deletions(-) |
| |
| diff --git a/src/filesystem/filesystem_common.h b/src/filesystem/filesystem_common.h |
| index 8204e9a2e5f8..a4505171c3eb 100644 |
| --- a/src/filesystem/filesystem_common.h |
| +++ b/src/filesystem/filesystem_common.h |
| @@ -437,7 +437,11 @@ public: |
| } |
| }; |
| |
| +#if defined(_LIBCPP_WIN32API) |
| +using fs_time = time_util<file_time_type, int64_t, TimeSpec>; |
| +#else |
| using fs_time = time_util<file_time_type, time_t, TimeSpec>; |
| +#endif |
| |
| #if defined(__APPLE__) |
| inline TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } |
| @@ -456,6 +460,7 @@ inline TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } |
| inline TimeSpec extract_atime(StatT const& st) { return st.st_atim; } |
| #endif |
| |
| +#if !defined(_LIBCPP_WIN32API) |
| inline TimeVal make_timeval(TimeSpec const& ts) { |
| using namespace chrono; |
| auto Convert = [](long nsec) { |
| @@ -498,6 +503,7 @@ bool set_file_times(const path& p, std::array<TimeSpec, 2> const& TS, |
| return posix_utimensat(p, TS, ec); |
| #endif |
| } |
| +#endif /* !_LIBCPP_WIN32API */ |
| |
| } // namespace |
| } // end namespace detail |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 548a0273ce71..40a863db69a1 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -42,7 +42,7 @@ |
| # define _LIBCPP_FILESYSTEM_USE_FSTREAM |
| #endif |
| |
| -#if !defined(CLOCK_REALTIME) |
| +#if !defined(CLOCK_REALTIME) && !defined(_LIBCPP_WIN32API) |
| # include <sys/time.h> // for gettimeofday and timeval |
| #endif |
| |
| @@ -567,7 +567,14 @@ const bool _FilesystemClock::is_steady; |
| |
| _FilesystemClock::time_point _FilesystemClock::now() noexcept { |
| typedef chrono::duration<rep> __secs; |
| -#if defined(CLOCK_REALTIME) |
| +#if defined(_LIBCPP_WIN32API) |
| + typedef chrono::duration<rep, nano> __nsecs; |
| + FILETIME time; |
| + GetSystemTimeAsFileTime(&time); |
| + TimeSpec tp = detail::filetime_to_timespec(time); |
| + return time_point(__secs(tp.tv_sec) + |
| + chrono::duration_cast<duration>(__nsecs(tp.tv_nsec))); |
| +#elif defined(CLOCK_REALTIME) |
| typedef chrono::duration<rep, nano> __nsecs; |
| struct timespec tp; |
| if (0 != clock_gettime(CLOCK_REALTIME, &tp)) |
| @@ -1121,6 +1128,17 @@ void __last_write_time(const path& p, file_time_type new_time, error_code* ec) { |
| using detail::fs_time; |
| ErrorHandler<void> err("last_write_time", ec, &p); |
| |
| +#if defined(_LIBCPP_WIN32API) |
| + TimeSpec ts; |
| + if (!fs_time::convert_to_timespec(ts, new_time)) |
| + return err.report(errc::value_too_large); |
| + detail::WinHandle h(p.c_str(), FILE_WRITE_ATTRIBUTES, 0); |
| + if (!h) |
| + return err.report(detail::make_windows_error(GetLastError())); |
| + FILETIME last_write = timespec_to_filetime(ts); |
| + if (!SetFileTime(h, nullptr, nullptr, &last_write)) |
| + return err.report(detail::make_windows_error(GetLastError())); |
| +#else |
| error_code m_ec; |
| array<TimeSpec, 2> tbuf; |
| #if !defined(_LIBCPP_USE_UTIMENSAT) |
| @@ -1142,6 +1160,7 @@ void __last_write_time(const path& p, file_time_type new_time, error_code* ec) { |
| detail::set_file_times(p, tbuf, m_ec); |
| if (m_ec) |
| return err.report(m_ec); |
| +#endif |
| } |
| |
| void __permissions(const path& p, perms prms, perm_options opts, |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 3eec4634e929..7caf9d28b49d 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -94,6 +94,23 @@ TimeSpec filetime_to_timespec(LARGE_INTEGER li) { |
| return ret; |
| } |
| |
| +TimeSpec filetime_to_timespec(FILETIME ft) { |
| + LARGE_INTEGER li; |
| + li.LowPart = ft.dwLowDateTime; |
| + li.HighPart = ft.dwHighDateTime; |
| + return filetime_to_timespec(li); |
| +} |
| + |
| +FILETIME timespec_to_filetime(TimeSpec ts) { |
| + LARGE_INTEGER li; |
| + li.QuadPart = |
| + ts.tv_nsec / 100 + (ts.tv_sec + FILE_TIME_OFFSET_SECS) * 10000000; |
| + FILETIME ft; |
| + ft.dwLowDateTime = li.LowPart; |
| + ft.dwHighDateTime = li.HighPart; |
| + return ft; |
| +} |
| + |
| int set_errno(int e = GetLastError()) { |
| errno = static_cast<int>(__win_err_to_errc(e)); |
| return -1; |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 13bc151ad49587f94a123cdb3e2ba770c1513d9d Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Fri, 6 Nov 2020 11:16:30 +0200 |
| Subject: [PATCH 03/24] [libcxx] Hook up a number of operation functions to |
| their windows counterparts |
| |
| Use the corresponding wchar functions, named "_wfunc" instead of "func", |
| where feasible, or reimplement functions with native windows APIs. |
| |
| Differential Revision: https://reviews.llvm.org/D91143 |
| |
| (cherry picked from commit efec3cc6524bc536459b9cb6faca190b1e3804b6) |
| --- |
| src/filesystem/operations.cpp | 41 ++++++----- |
| src/filesystem/posix_compat.h | 102 +++++++++++++++++++++++++++ |
| 2 files changed, 124 insertions(+), 19 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 40a863db69a1..ddb4d7588e54 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -405,7 +405,7 @@ struct FileDescriptor { |
| static FileDescriptor create(const path* p, error_code& ec, Args... args) { |
| ec.clear(); |
| int fd; |
| - if ((fd = ::open(p->c_str(), args...)) == -1) { |
| + if ((fd = detail::open(p->c_str(), args...)) == -1) { |
| ec = capture_errno(); |
| return FileDescriptor{p}; |
| } |
| @@ -431,7 +431,7 @@ struct FileDescriptor { |
| |
| void close() noexcept { |
| if (fd != -1) |
| - ::close(fd); |
| + detail::close(fd); |
| fd = -1; |
| } |
| |
| @@ -521,7 +521,7 @@ file_status posix_lstat(path const& p, error_code* ec) { |
| |
| // http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html |
| bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) { |
| - if (::ftruncate(fd.fd, to_size) == -1) { |
| + if (detail::ftruncate(fd.fd, to_size) == -1) { |
| ec = capture_errno(); |
| return true; |
| } |
| @@ -828,8 +828,8 @@ bool __copy_file(const path& from, const path& to, copy_options options, |
| ErrorHandler<bool> err("copy_file", ec, &to, &from); |
| |
| error_code m_ec; |
| - FileDescriptor from_fd = |
| - FileDescriptor::create_with_status(&from, m_ec, O_RDONLY | O_NONBLOCK); |
| + FileDescriptor from_fd = FileDescriptor::create_with_status( |
| + &from, m_ec, O_RDONLY | O_NONBLOCK | O_BINARY); |
| if (m_ec) |
| return err.report(m_ec); |
| |
| @@ -881,7 +881,7 @@ bool __copy_file(const path& from, const path& to, copy_options options, |
| |
| // Don't truncate right away. We may not be opening the file we originally |
| // looked at; we'll check this later. |
| - int to_open_flags = O_WRONLY; |
| + int to_open_flags = O_WRONLY | O_BINARY; |
| if (!to_exists) |
| to_open_flags |= O_CREAT; |
| FileDescriptor to_fd = FileDescriptor::create_with_status( |
| @@ -917,10 +917,13 @@ void __copy_symlink(const path& existing_symlink, const path& new_symlink, |
| if (ec && *ec) { |
| return; |
| } |
| - // NOTE: proposal says you should detect if you should call |
| - // create_symlink or create_directory_symlink. I don't think this |
| - // is needed with POSIX |
| - __create_symlink(real_path, new_symlink, ec); |
| +#if defined(_LIBCPP_WIN32API) |
| + error_code local_ec; |
| + if (is_directory(real_path, local_ec)) |
| + __create_directory_symlink(real_path, new_symlink, ec); |
| + else |
| +#endif |
| + __create_symlink(real_path, new_symlink, ec); |
| } |
| |
| bool __create_directories(const path& p, error_code* ec) { |
| @@ -953,7 +956,7 @@ bool __create_directories(const path& p, error_code* ec) { |
| bool __create_directory(const path& p, error_code* ec) { |
| ErrorHandler<bool> err("create_directory", ec, &p); |
| |
| - if (::mkdir(p.c_str(), static_cast<int>(perms::all)) == 0) |
| + if (detail::mkdir(p.c_str(), static_cast<int>(perms::all)) == 0) |
| return true; |
| |
| if (errno == EEXIST) { |
| @@ -981,7 +984,7 @@ bool __create_directory(path const& p, path const& attributes, error_code* ec) { |
| return err.report(errc::not_a_directory, |
| "the specified attribute path is invalid"); |
| |
| - if (::mkdir(p.c_str(), attr_stat.st_mode) == 0) |
| + if (detail::mkdir(p.c_str(), attr_stat.st_mode) == 0) |
| return true; |
| |
| if (errno == EEXIST) { |
| @@ -1000,19 +1003,19 @@ bool __create_directory(path const& p, path const& attributes, error_code* ec) { |
| void __create_directory_symlink(path const& from, path const& to, |
| error_code* ec) { |
| ErrorHandler<void> err("create_directory_symlink", ec, &from, &to); |
| - if (::symlink(from.c_str(), to.c_str()) != 0) |
| + if (detail::symlink_dir(from.c_str(), to.c_str()) == -1) |
| return err.report(capture_errno()); |
| } |
| |
| void __create_hard_link(const path& from, const path& to, error_code* ec) { |
| ErrorHandler<void> err("create_hard_link", ec, &from, &to); |
| - if (::link(from.c_str(), to.c_str()) == -1) |
| + if (detail::link(from.c_str(), to.c_str()) == -1) |
| return err.report(capture_errno()); |
| } |
| |
| void __create_symlink(path const& from, path const& to, error_code* ec) { |
| ErrorHandler<void> err("create_symlink", ec, &from, &to); |
| - if (::symlink(from.c_str(), to.c_str()) == -1) |
| + if (detail::symlink_file(from.c_str(), to.c_str()) == -1) |
| return err.report(capture_errno()); |
| } |
| |
| @@ -1032,7 +1035,7 @@ path __current_path(error_code* ec) { |
| |
| void __current_path(const path& p, error_code* ec) { |
| ErrorHandler<void> err("current_path", ec, &p); |
| - if (::chdir(p.c_str()) == -1) |
| + if (detail::chdir(p.c_str()) == -1) |
| err.report(capture_errno()); |
| } |
| |
| @@ -1236,7 +1239,7 @@ path __read_symlink(const path& p, error_code* ec) { |
| |
| bool __remove(const path& p, error_code* ec) { |
| ErrorHandler<bool> err("remove", ec, &p); |
| - if (::remove(p.c_str()) == -1) { |
| + if (detail::remove(p.c_str()) == -1) { |
| if (errno != ENOENT) |
| err.report(capture_errno()); |
| return false; |
| @@ -1285,13 +1288,13 @@ uintmax_t __remove_all(const path& p, error_code* ec) { |
| |
| void __rename(const path& from, const path& to, error_code* ec) { |
| ErrorHandler<void> err("rename", ec, &from, &to); |
| - if (::rename(from.c_str(), to.c_str()) == -1) |
| + if (detail::rename(from.c_str(), to.c_str()) == -1) |
| err.report(capture_errno()); |
| } |
| |
| void __resize_file(const path& p, uintmax_t size, error_code* ec) { |
| ErrorHandler<void> err("resize_file", ec, &p); |
| - if (::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1) |
| + if (detail::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1) |
| return err.report(capture_errno()); |
| } |
| |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 7caf9d28b49d..1e70e12cb4d7 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -12,6 +12,8 @@ |
| // These generally behave like the proper posix functions, with these |
| // exceptions: |
| // On Windows, they take paths in wchar_t* form, instead of char* form. |
| +// The symlink() function is split into two frontends, symlink_file() |
| +// and symlink_dir(). |
| // |
| // These are provided within an anonymous namespace within the detail |
| // namespace - callers need to include this header and call them as |
| @@ -82,6 +84,8 @@ namespace { |
| #define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) |
| #define S_ISSOCK(m) (((m) & _S_IFMT) == _S_IFSOCK) |
| |
| +#define O_NONBLOCK 0 |
| + |
| |
| // There were 369 years and 89 leap days from the Windows epoch |
| // (1601) to the Unix epoch (1970). |
| @@ -190,10 +194,108 @@ int fstat(int fd, StatT *buf) { |
| HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
| return stat_handle(h, buf); |
| } |
| + |
| +int mkdir(const wchar_t *path, int permissions) { |
| + (void)permissions; |
| + return _wmkdir(path); |
| +} |
| + |
| +int symlink_file_dir(const wchar_t *oldname, const wchar_t *newname, |
| + bool is_dir) { |
| + DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; |
| + if (CreateSymbolicLinkW(newname, oldname, |
| + flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) |
| + return 0; |
| + int e = GetLastError(); |
| + if (e != ERROR_INVALID_PARAMETER) |
| + return set_errno(e); |
| + if (CreateSymbolicLinkW(newname, oldname, flags)) |
| + return 0; |
| + return set_errno(); |
| +} |
| + |
| +int symlink_file(const wchar_t *oldname, const wchar_t *newname) { |
| + return symlink_file_dir(oldname, newname, false); |
| +} |
| + |
| +int symlink_dir(const wchar_t *oldname, const wchar_t *newname) { |
| + return symlink_file_dir(oldname, newname, true); |
| +} |
| + |
| +int link(const wchar_t *oldname, const wchar_t *newname) { |
| + if (CreateHardLinkW(newname, oldname, nullptr)) |
| + return 0; |
| + return set_errno(); |
| +} |
| + |
| +int remove(const wchar_t *path) { |
| + detail::WinHandle h(path, DELETE, FILE_FLAG_OPEN_REPARSE_POINT); |
| + if (!h) |
| + return set_errno(); |
| + FILE_DISPOSITION_INFO info; |
| + info.DeleteFile = TRUE; |
| + if (!SetFileInformationByHandle(h, FileDispositionInfo, &info, sizeof(info))) |
| + return set_errno(); |
| + return 0; |
| +} |
| + |
| +int truncate_handle(HANDLE h, off_t length) { |
| + LARGE_INTEGER size_param; |
| + size_param.QuadPart = length; |
| + if (!SetFilePointerEx(h, size_param, 0, FILE_BEGIN)) |
| + return set_errno(); |
| + if (!SetEndOfFile(h)) |
| + return set_errno(); |
| + return 0; |
| +} |
| + |
| +int ftruncate(int fd, off_t length) { |
| + HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
| + return truncate_handle(h, length); |
| +} |
| + |
| +int truncate(const wchar_t *path, off_t length) { |
| + detail::WinHandle h(path, GENERIC_WRITE, 0); |
| + if (!h) |
| + return set_errno(); |
| + return truncate_handle(h, length); |
| +} |
| + |
| +int rename(const wchar_t *from, const wchar_t *to) { |
| + if (!(MoveFileExW(from, to, |
| + MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | |
| + MOVEFILE_WRITE_THROUGH))) |
| + return set_errno(); |
| + return 0; |
| +} |
| + |
| +template <class... Args> int open(const wchar_t *filename, Args... args) { |
| + return _wopen(filename, args...); |
| +} |
| +int close(int fd) { return _close(fd); } |
| +int chdir(const wchar_t *path) { return _wchdir(path); } |
| #else |
| +int symlink_file(const char *oldname, const char *newname) { |
| + return ::symlink(oldname, newname); |
| +} |
| +int symlink_dir(const char *oldname, const char *newname) { |
| + return ::symlink(oldname, newname); |
| +} |
| +using ::chdir; |
| +using ::close; |
| using ::fstat; |
| +using ::ftruncate; |
| +using ::link; |
| using ::lstat; |
| +using ::mkdir; |
| +using ::open; |
| +using ::remove; |
| +using ::rename; |
| using ::stat; |
| +using ::truncate; |
| + |
| +#define O_BINARY 0 |
| + |
| #endif |
| |
| } // namespace |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 21ffff20ef2e05f8c99bd120d7b1212d75f14dcd Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Mon, 9 Nov 2020 15:20:18 +0200 |
| Subject: [PATCH 04/24] [libcxx] Sanitize paths before creating symlinks on |
| windows |
| |
| The MS STL does even more cleanup (corresponding to lexically_normal |
| I think), but this seems to be the very minimum needed for making the |
| symlinks work when the target path contains non-native paths. |
| |
| Differential Revision: https://reviews.llvm.org/D91145 |
| |
| (cherry picked from commit f65ba25cf37a57dc87db7af389c9dc637ca7dd8c) |
| --- |
| src/filesystem/posix_compat.h | 3 +++ |
| .../create_symlink.pass.cpp | 20 +++++++++++++++++++ |
| 2 files changed, 23 insertions(+) |
| |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 1e70e12cb4d7..0dbae1235a00 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -202,6 +202,9 @@ int mkdir(const wchar_t *path, int permissions) { |
| |
| int symlink_file_dir(const wchar_t *oldname, const wchar_t *newname, |
| bool is_dir) { |
| + path dest(oldname); |
| + dest.make_preferred(); |
| + oldname = dest.c_str(); |
| DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; |
| if (CreateSymbolicLinkW(newname, oldname, |
| flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) |
| diff --git a/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_symlink/create_symlink.pass.cpp b/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_symlink/create_symlink.pass.cpp |
| index 47ec916a169b..3b54cff309b7 100644 |
| --- a/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_symlink/create_symlink.pass.cpp |
| +++ b/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_symlink/create_symlink.pass.cpp |
| @@ -71,5 +71,25 @@ TEST_CASE(create_symlink_basic) |
| } |
| } |
| |
| +TEST_CASE(create_symlink_dest_cleanup) |
| +{ |
| + scoped_test_env env; |
| + const path dir = env.create_dir("dir"); |
| + const path file = env.create_file("file", 42); |
| + const path sym = dir / "link"; |
| + // The target path has to be normalized to backslashes before creating |
| + // the link on windows, otherwise the link isn't dereferencable. |
| + const path sym_target = "../file"; |
| + path sym_target_normalized = sym_target; |
| + sym_target_normalized.make_preferred(); |
| + std::error_code ec; |
| + fs::create_symlink(sym_target, sym, ec); |
| + TEST_REQUIRE(!ec); |
| + TEST_CHECK(equivalent(sym, file, ec)); |
| + const path ret = fs::read_symlink(sym, ec); |
| + TEST_CHECK(!ec); |
| + TEST_CHECK(ret.native() == sym_target_normalized.native()); |
| +} |
| + |
| |
| TEST_SUITE_END() |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 51a0f3a5af65c8e8e1618dc5621705471aba2151 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 23:32:13 +0200 |
| Subject: [PATCH 05/24] [libcxx] Implement the space function for windows |
| |
| Differential Revision: https://reviews.llvm.org/D91168 |
| |
| (cherry picked from commit a3cc99658d52f79faad26beeea06691b3a50bc95) |
| --- |
| src/filesystem/operations.cpp | 4 +-- |
| src/filesystem/posix_compat.h | 37 ++++++++++++++++++++++++++++ |
| 2 files changed, 39 insertions(+), 2 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index ddb4d7588e54..fcb5c2def23c 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1301,8 +1301,8 @@ void __resize_file(const path& p, uintmax_t size, error_code* ec) { |
| space_info __space(const path& p, error_code* ec) { |
| ErrorHandler<void> err("space", ec, &p); |
| space_info si; |
| - struct statvfs m_svfs = {}; |
| - if (::statvfs(p.c_str(), &m_svfs) == -1) { |
| + detail::StatVFS m_svfs = {}; |
| + if (detail::statvfs(p.c_str(), &m_svfs) == -1) { |
| err.report(capture_errno()); |
| si.capacity = si.free = si.available = static_cast<uintmax_t>(-1); |
| return si; |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 0dbae1235a00..5f868a090693 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -277,6 +277,40 @@ template <class... Args> int open(const wchar_t *filename, Args... args) { |
| } |
| int close(int fd) { return _close(fd); } |
| int chdir(const wchar_t *path) { return _wchdir(path); } |
| + |
| +struct StatVFS { |
| + uint64_t f_frsize; |
| + uint64_t f_blocks; |
| + uint64_t f_bfree; |
| + uint64_t f_bavail; |
| +}; |
| + |
| +int statvfs(const wchar_t *p, StatVFS *buf) { |
| + path dir = p; |
| + while (true) { |
| + error_code local_ec; |
| + const file_status st = status(dir, local_ec); |
| + if (!exists(st) || is_directory(st)) |
| + break; |
| + path parent = dir.parent_path(); |
| + if (parent == dir) { |
| + errno = ENOENT; |
| + return -1; |
| + } |
| + dir = parent; |
| + } |
| + ULARGE_INTEGER free_bytes_available_to_caller, total_number_of_bytes, |
| + total_number_of_free_bytes; |
| + if (!GetDiskFreeSpaceExW(dir.c_str(), &free_bytes_available_to_caller, |
| + &total_number_of_bytes, &total_number_of_free_bytes)) |
| + return set_errno(); |
| + buf->f_frsize = 1; |
| + buf->f_blocks = total_number_of_bytes.QuadPart; |
| + buf->f_bfree = total_number_of_free_bytes.QuadPart; |
| + buf->f_bavail = free_bytes_available_to_caller.QuadPart; |
| + return 0; |
| +} |
| + |
| #else |
| int symlink_file(const char *oldname, const char *newname) { |
| return ::symlink(oldname, newname); |
| @@ -295,10 +329,13 @@ using ::open; |
| using ::remove; |
| using ::rename; |
| using ::stat; |
| +using ::statvfs; |
| using ::truncate; |
| |
| #define O_BINARY 0 |
| |
| +using StatVFS = struct statvfs; |
| + |
| #endif |
| |
| } // namespace |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 0bdb2f73e8ba72de291d1ca95363835ab538bdab Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 23:46:12 +0200 |
| Subject: [PATCH 06/24] [libcxx] Implement the current_path function for |
| windows |
| |
| Differential Revision: https://reviews.llvm.org/D91169 |
| |
| (cherry picked from commit 0c71c914faa371ba502a2e1835f763104837cb9f) |
| --- |
| src/filesystem/operations.cpp | 25 +++++++++++++++++++++---- |
| src/filesystem/posix_compat.h | 2 ++ |
| 2 files changed, 23 insertions(+), 4 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index fcb5c2def23c..429a58501a49 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1022,15 +1022,32 @@ void __create_symlink(path const& from, path const& to, error_code* ec) { |
| path __current_path(error_code* ec) { |
| ErrorHandler<path> err("current_path", ec); |
| |
| +#if defined(_LIBCPP_WIN32API) |
| + // Common extension outside of POSIX getcwd() spec, without needing to |
| + // preallocate a buffer. Also supported by a number of other POSIX libcs. |
| + int size = 0; |
| + path::value_type* ptr = nullptr; |
| + typedef decltype(&::free) Deleter; |
| + Deleter deleter = &::free; |
| +#else |
| auto size = ::pathconf(".", _PC_PATH_MAX); |
| _LIBCPP_ASSERT(size >= 0, "pathconf returned a 0 as max size"); |
| |
| - auto buff = unique_ptr<char[]>(new char[size + 1]); |
| - char* ret; |
| - if ((ret = ::getcwd(buff.get(), static_cast<size_t>(size))) == nullptr) |
| + auto buff = unique_ptr<path::value_type[]>(new path::value_type[size + 1]); |
| + path::value_type* ptr = buff.get(); |
| + |
| + // Preallocated buffer, don't free the buffer in the second unique_ptr |
| + // below. |
| + struct Deleter { void operator()(void*) const {} }; |
| + Deleter deleter; |
| +#endif |
| + |
| + unique_ptr<path::value_type, Deleter> hold(detail::getcwd(ptr, size), |
| + deleter); |
| + if (hold.get() == nullptr) |
| return err.report(capture_errno(), "call to getcwd failed"); |
| |
| - return {buff.get()}; |
| + return {hold.get()}; |
| } |
| |
| void __current_path(const path& p, error_code* ec) { |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 5f868a090693..13753fdbb760 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -311,6 +311,7 @@ int statvfs(const wchar_t *p, StatVFS *buf) { |
| return 0; |
| } |
| |
| +wchar_t *getcwd(wchar_t *buff, size_t size) { return _wgetcwd(buff, size); } |
| #else |
| int symlink_file(const char *oldname, const char *newname) { |
| return ::symlink(oldname, newname); |
| @@ -322,6 +323,7 @@ using ::chdir; |
| using ::close; |
| using ::fstat; |
| using ::ftruncate; |
| +using ::getcwd; |
| using ::link; |
| using ::lstat; |
| using ::mkdir; |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From b6ef948856bfde6326ebc2d1fddc24890b4182f9 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 23:51:12 +0200 |
| Subject: [PATCH 07/24] [libcxx] Implement the canonical function for windows |
| |
| Differential Revision: https://reviews.llvm.org/D91170 |
| |
| (cherry picked from commit 83d705adb2e043e576c9cbaf9709795bc69fe5cf) |
| --- |
| src/filesystem/operations.cpp | 14 +++++----- |
| src/filesystem/posix_compat.h | 38 ++++++++++++++++++++++++++++ |
| 2 files changed, 45 insertions(+), 7 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 429a58501a49..fc18de9e5d80 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -636,20 +636,20 @@ path __canonical(path const& orig_p, error_code* ec) { |
| ErrorHandler<path> err("canonical", ec, &orig_p, &cwd); |
| |
| path p = __do_absolute(orig_p, &cwd, ec); |
| -#if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112 |
| - std::unique_ptr<char, decltype(&::free)> |
| - hold(::realpath(p.c_str(), nullptr), &::free); |
| +#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || defined(_LIBCPP_WIN32API) |
| + std::unique_ptr<path::value_type, decltype(&::free)> |
| + hold(detail::realpath(p.c_str(), nullptr), &::free); |
| if (hold.get() == nullptr) |
| return err.report(capture_errno()); |
| return {hold.get()}; |
| #else |
| #if defined(__MVS__) && !defined(PATH_MAX) |
| - char buff[ _XOPEN_PATH_MAX + 1 ]; |
| + path::value_type buff[ _XOPEN_PATH_MAX + 1 ]; |
| #else |
| - char buff[PATH_MAX + 1]; |
| + path::value_type buff[PATH_MAX + 1]; |
| #endif |
| - char* ret; |
| - if ((ret = ::realpath(p.c_str(), buff)) == nullptr) |
| + path::value_type* ret; |
| + if ((ret = detail::realpath(p.c_str(), buff)) == nullptr) |
| return err.report(capture_errno()); |
| return {ret}; |
| #endif |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 13753fdbb760..1adce3a779c2 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -312,6 +312,43 @@ int statvfs(const wchar_t *p, StatVFS *buf) { |
| } |
| |
| wchar_t *getcwd(wchar_t *buff, size_t size) { return _wgetcwd(buff, size); } |
| + |
| +wchar_t *realpath(const wchar_t *path, wchar_t *resolved_name) { |
| + // Only expected to be used with us allocating the buffer. |
| + _LIBCPP_ASSERT(resolved_name == nullptr, |
| + "Windows realpath() assumes a null resolved_name"); |
| + |
| + WinHandle h(path, FILE_READ_ATTRIBUTES, 0); |
| + if (!h) { |
| + set_errno(); |
| + return nullptr; |
| + } |
| + size_t buff_size = MAX_PATH + 10; |
| + std::unique_ptr<wchar_t, decltype(&::free)> buff( |
| + static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t))), &::free); |
| + DWORD retval = GetFinalPathNameByHandleW( |
| + h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); |
| + if (retval > buff_size) { |
| + buff_size = retval; |
| + buff.reset(static_cast<wchar_t *>(malloc(buff_size * sizeof(wchar_t)))); |
| + retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size, |
| + FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); |
| + } |
| + if (!retval) { |
| + set_errno(); |
| + return nullptr; |
| + } |
| + wchar_t *ptr = buff.get(); |
| + if (!wcsncmp(ptr, L"\\\\?\\", 4)) { |
| + if (ptr[5] == ':') { // \\?\X: -> X: |
| + memmove(&ptr[0], &ptr[4], (wcslen(&ptr[4]) + 1) * sizeof(wchar_t)); |
| + } else if (!wcsncmp(&ptr[4], L"UNC\\", 4)) { // \\?\UNC\server -> \\server |
| + wcscpy(&ptr[0], L"\\\\"); |
| + memmove(&ptr[2], &ptr[8], (wcslen(&ptr[8]) + 1) * sizeof(wchar_t)); |
| + } |
| + } |
| + return buff.release(); |
| +} |
| #else |
| int symlink_file(const char *oldname, const char *newname) { |
| return ::symlink(oldname, newname); |
| @@ -328,6 +365,7 @@ using ::link; |
| using ::lstat; |
| using ::mkdir; |
| using ::open; |
| +using ::realpath; |
| using ::remove; |
| using ::rename; |
| using ::stat; |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 96748da73f57e6b080643d03e41e8a690ee571a9 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 23:55:10 +0200 |
| Subject: [PATCH 08/24] [libcxx] Implement the permissions function for windows |
| |
| Differential Revision: https://reviews.llvm.org/D91171 |
| |
| (cherry picked from commit 40117b700f723a27b90989a429ceb66c0873b161) |
| --- |
| src/filesystem/operations.cpp | 10 ++---- |
| src/filesystem/posix_compat.h | 54 ++++++++++++++++++++++++++++ |
| 2 files changed, 57 insertions(+), 7 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index fc18de9e5d80..47cd5e23c092 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -455,10 +455,6 @@ perms posix_get_perms(const StatT& st) noexcept { |
| return static_cast<perms>(st.st_mode) & perms::mask; |
| } |
| |
| -::mode_t posix_convert_perms(perms prms) { |
| - return static_cast< ::mode_t>(prms & perms::mask); |
| -} |
| - |
| file_status create_file_status(error_code& m_ec, path const& p, |
| const StatT& path_stat, error_code* ec) { |
| if (ec) |
| @@ -530,7 +526,7 @@ bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) { |
| } |
| |
| bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) { |
| - if (::fchmod(fd.fd, st.st_mode) == -1) { |
| + if (detail::fchmod(fd.fd, st.st_mode) == -1) { |
| ec = capture_errno(); |
| return true; |
| } |
| @@ -1212,11 +1208,11 @@ void __permissions(const path& p, perms prms, perm_options opts, |
| else if (remove_perms) |
| prms = st.permissions() & ~prms; |
| } |
| - const auto real_perms = detail::posix_convert_perms(prms); |
| + const auto real_perms = static_cast<detail::ModeT>(prms & perms::mask); |
| |
| #if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) |
| const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0; |
| - if (::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { |
| + if (detail::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { |
| return err.report(capture_errno()); |
| } |
| #else |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 1adce3a779c2..af4a3691db33 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -349,6 +349,57 @@ wchar_t *realpath(const wchar_t *path, wchar_t *resolved_name) { |
| } |
| return buff.release(); |
| } |
| + |
| +#define AT_FDCWD -1 |
| +#define AT_SYMLINK_NOFOLLOW 1 |
| +using ModeT = int; |
| + |
| +int fchmod_handle(HANDLE h, int perms) { |
| + FILE_BASIC_INFO basic; |
| + if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) |
| + return set_errno(); |
| + DWORD orig_attributes = basic.FileAttributes; |
| + basic.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; |
| + if ((perms & 0222) == 0) |
| + basic.FileAttributes |= FILE_ATTRIBUTE_READONLY; |
| + if (basic.FileAttributes != orig_attributes && |
| + !SetFileInformationByHandle(h, FileBasicInfo, &basic, sizeof(basic))) |
| + return set_errno(); |
| + return 0; |
| +} |
| + |
| +int fchmodat(int fd, const wchar_t *path, int perms, int flag) { |
| + DWORD attributes = GetFileAttributesW(path); |
| + if (attributes == INVALID_FILE_ATTRIBUTES) |
| + return set_errno(); |
| + if (attributes & FILE_ATTRIBUTE_REPARSE_POINT && |
| + !(flag & AT_SYMLINK_NOFOLLOW)) { |
| + // If the file is a symlink, and we are supposed to operate on the target |
| + // of the symlink, we need to open a handle to it, without the |
| + // FILE_FLAG_OPEN_REPARSE_POINT flag, to open the destination of the |
| + // symlink, and operate on it via the handle. |
| + detail::WinHandle h(path, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0); |
| + if (!h) |
| + return set_errno(); |
| + return fchmod_handle(h, perms); |
| + } else { |
| + // For a non-symlink, or if operating on the symlink itself instead of |
| + // its target, we can use SetFileAttributesW, saving a few calls. |
| + DWORD orig_attributes = attributes; |
| + attributes &= ~FILE_ATTRIBUTE_READONLY; |
| + if ((perms & 0222) == 0) |
| + attributes |= FILE_ATTRIBUTE_READONLY; |
| + if (attributes != orig_attributes && !SetFileAttributesW(path, attributes)) |
| + return set_errno(); |
| + } |
| + return 0; |
| +} |
| + |
| +int fchmod(int fd, int perms) { |
| + HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
| + return fchmod_handle(h, perms); |
| +} |
| + |
| #else |
| int symlink_file(const char *oldname, const char *newname) { |
| return ::symlink(oldname, newname); |
| @@ -358,6 +409,8 @@ int symlink_dir(const char *oldname, const char *newname) { |
| } |
| using ::chdir; |
| using ::close; |
| +using ::fchmod; |
| +using ::fchmodat; |
| using ::fstat; |
| using ::ftruncate; |
| using ::getcwd; |
| @@ -375,6 +428,7 @@ using ::truncate; |
| #define O_BINARY 0 |
| |
| using StatVFS = struct statvfs; |
| +using ModeT = ::mode_t; |
| |
| #endif |
| |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From f28542330202fae54a6fee1bf02a311feef3f60a Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 23:51:18 +0200 |
| Subject: [PATCH 09/24] [libcxx] Implement the read_symlink function for |
| windows |
| |
| Differential Revision: https://reviews.llvm.org/D91172 |
| |
| (cherry picked from commit cdc60a3b9aa523b49329a7a5e4c1774d3b9e3db9) |
| --- |
| src/filesystem/operations.cpp | 16 +++--- |
| src/filesystem/posix_compat.h | 79 ++++++++++++++++++++++++++++ |
| 2 files changed, 89 insertions(+), 6 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 47cd5e23c092..a5463e48f4d7 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1227,21 +1227,25 @@ void __permissions(const path& p, perms prms, perm_options opts, |
| path __read_symlink(const path& p, error_code* ec) { |
| ErrorHandler<path> err("read_symlink", ec, &p); |
| |
| -#ifdef PATH_MAX |
| +#if defined(PATH_MAX) || defined(MAX_SYMLINK_SIZE) |
| struct NullDeleter { void operator()(void*) const {} }; |
| +#ifdef MAX_SYMLINK_SIZE |
| + const size_t size = MAX_SYMLINK_SIZE + 1; |
| +#else |
| const size_t size = PATH_MAX + 1; |
| - char stack_buff[size]; |
| - auto buff = std::unique_ptr<char[], NullDeleter>(stack_buff); |
| +#endif |
| + path::value_type stack_buff[size]; |
| + auto buff = std::unique_ptr<path::value_type[], NullDeleter>(stack_buff); |
| #else |
| StatT sb; |
| if (detail::lstat(p.c_str(), &sb) == -1) { |
| return err.report(capture_errno()); |
| } |
| const size_t size = sb.st_size + 1; |
| - auto buff = unique_ptr<char[]>(new char[size]); |
| + auto buff = unique_ptr<path::value_type[]>(new path::value_type[size]); |
| #endif |
| - ::ssize_t ret; |
| - if ((ret = ::readlink(p.c_str(), buff.get(), size)) == -1) |
| + detail::SSizeT ret; |
| + if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1) |
| return err.report(capture_errno()); |
| _LIBCPP_ASSERT(ret > 0, "TODO"); |
| if (static_cast<size_t>(ret) >= size) |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index af4a3691db33..8062bd65ce00 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -32,6 +32,7 @@ |
| # define NOMINMAX |
| # include <windows.h> |
| # include <io.h> |
| +# include <winioctl.h> |
| #else |
| # include <unistd.h> |
| # include <sys/stat.h> |
| @@ -39,6 +40,36 @@ |
| #endif |
| #include <time.h> |
| |
| +#if defined(_LIBCPP_WIN32API) |
| +// This struct isn't defined in the normal Windows SDK, but only in the |
| +// Windows Driver Kit. |
| +struct LIBCPP_REPARSE_DATA_BUFFER { |
| + unsigned long ReparseTag; |
| + unsigned short ReparseDataLength; |
| + unsigned short Reserved; |
| + union { |
| + struct { |
| + unsigned short SubstituteNameOffset; |
| + unsigned short SubstituteNameLength; |
| + unsigned short PrintNameOffset; |
| + unsigned short PrintNameLength; |
| + unsigned long Flags; |
| + wchar_t PathBuffer[1]; |
| + } SymbolicLinkReparseBuffer; |
| + struct { |
| + unsigned short SubstituteNameOffset; |
| + unsigned short SubstituteNameLength; |
| + unsigned short PrintNameOffset; |
| + unsigned short PrintNameLength; |
| + wchar_t PathBuffer[1]; |
| + } MountPointReparseBuffer; |
| + struct { |
| + unsigned char DataBuffer[1]; |
| + } GenericReparseBuffer; |
| + }; |
| +}; |
| +#endif |
| + |
| _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM |
| |
| namespace detail { |
| @@ -400,6 +431,52 @@ int fchmod(int fd, int perms) { |
| return fchmod_handle(h, perms); |
| } |
| |
| +#define MAX_SYMLINK_SIZE MAXIMUM_REPARSE_DATA_BUFFER_SIZE |
| +using SSizeT = ::int64_t; |
| + |
| +SSizeT readlink(const wchar_t *path, wchar_t *ret_buf, size_t bufsize) { |
| + uint8_t buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; |
| + detail::WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT); |
| + if (!h) |
| + return set_errno(); |
| + DWORD out; |
| + if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf, sizeof(buf), |
| + &out, 0)) |
| + return set_errno(); |
| + const auto *reparse = reinterpret_cast<LIBCPP_REPARSE_DATA_BUFFER *>(buf); |
| + size_t path_buf_offset = offsetof(LIBCPP_REPARSE_DATA_BUFFER, |
| + SymbolicLinkReparseBuffer.PathBuffer[0]); |
| + if (out < path_buf_offset) { |
| + errno = EINVAL; |
| + return -1; |
| + } |
| + if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) { |
| + errno = EINVAL; |
| + return -1; |
| + } |
| + const auto &symlink = reparse->SymbolicLinkReparseBuffer; |
| + unsigned short name_offset, name_length; |
| + if (symlink.PrintNameLength == 0) { |
| + name_offset = symlink.SubstituteNameOffset; |
| + name_length = symlink.SubstituteNameLength; |
| + } else { |
| + name_offset = symlink.PrintNameOffset; |
| + name_length = symlink.PrintNameLength; |
| + } |
| + // name_offset/length are expressed in bytes, not in wchar_t |
| + if (path_buf_offset + name_offset + name_length > out) { |
| + errno = EINVAL; |
| + return -1; |
| + } |
| + if (name_length / sizeof(wchar_t) > bufsize) { |
| + errno = ENOMEM; |
| + return -1; |
| + } |
| + memcpy(ret_buf, &symlink.PathBuffer[name_offset / sizeof(wchar_t)], |
| + name_length); |
| + return name_length / sizeof(wchar_t); |
| +} |
| + |
| #else |
| int symlink_file(const char *oldname, const char *newname) { |
| return ::symlink(oldname, newname); |
| @@ -418,6 +495,7 @@ using ::link; |
| using ::lstat; |
| using ::mkdir; |
| using ::open; |
| +using ::readlink; |
| using ::realpath; |
| using ::remove; |
| using ::rename; |
| @@ -429,6 +507,7 @@ using ::truncate; |
| |
| using StatVFS = struct statvfs; |
| using ModeT = ::mode_t; |
| +using SSizeT = ::ssize_t; |
| |
| #endif |
| |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 6f225c9fca50bda8911a4909b3cbf0eb28733384 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Fri, 30 Oct 2020 22:24:25 +0200 |
| Subject: [PATCH 10/24] [libcxx] Use the posix code for |
| directory_entry::__do_refresh |
| |
| This works just fine for windows, as all the functions it calls |
| are implemented and wrapped for windows. |
| |
| Differential Revision: https://reviews.llvm.org/D91173 |
| |
| (cherry picked from commit 4d292d531bea6f7a6021f212e59b3826bc7cd913) |
| --- |
| src/filesystem/operations.cpp | 43 ---------------------------- |
| 1 file changed, 43 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index a5463e48f4d7..dccec9378531 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1847,7 +1847,6 @@ size_t __char_to_wide(const string &str, wchar_t *out, size_t outlen) { |
| // directory entry definitions |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| -#ifndef _LIBCPP_WIN32API |
| error_code directory_entry::__do_refresh() noexcept { |
| __data_.__reset(); |
| error_code failure_ec; |
| @@ -1901,47 +1900,5 @@ error_code directory_entry::__do_refresh() noexcept { |
| |
| return failure_ec; |
| } |
| -#else |
| -error_code directory_entry::__do_refresh() noexcept { |
| - __data_.__reset(); |
| - error_code failure_ec; |
| - |
| - file_status st = _VSTD_FS::symlink_status(__p_, 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 = _VSTD_FS::status(__p_, 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{}; |
| - } |
| - __data_.__cache_type_ = directory_entry::_RefreshSymlink; |
| - } |
| - |
| - // FIXME: This is currently broken, and the implementation only a placeholder. |
| - // We need to cache last_write_time, file_size, and hard_link_count here before |
| - // the implementation actually works. |
| - |
| - return failure_ec; |
| -} |
| -#endif |
| |
| _LIBCPP_END_NAMESPACE_FILESYSTEM |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 3474a416fef709f218ff5897842819d379495cad Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Thu, 29 Oct 2020 12:10:26 +0200 |
| Subject: [PATCH 11/24] [libcxx] Implement temp_directory_path using |
| GetTempPath on windows |
| |
| This does roughly the same as the manual implementation, but checks |
| a slightly different set of environment variables and has a more |
| appropriate fallback if no environment variables are available |
| (/tmp isn't a very useful fallback on windows). |
| |
| Differential Revision: https://reviews.llvm.org/D91175 |
| |
| (cherry picked from commit d4f4e723d0b4f09d72880f1679c02d586bf8abfa) |
| --- |
| src/filesystem/operations.cpp | 14 ++++++++++++++ |
| 1 file changed, 14 insertions(+) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index dccec9378531..674f19154e6f 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1347,6 +1347,19 @@ file_status __symlink_status(const path& p, error_code* ec) { |
| path __temp_directory_path(error_code* ec) { |
| ErrorHandler<path> err("temp_directory_path", ec); |
| |
| +#if defined(_LIBCPP_WIN32API) |
| + wchar_t buf[MAX_PATH]; |
| + DWORD retval = GetTempPathW(MAX_PATH, buf); |
| + if (!retval) |
| + return err.report(detail::make_windows_error(GetLastError())); |
| + if (retval > MAX_PATH) |
| + return err.report(errc::filename_too_long); |
| + // GetTempPathW returns a path with a trailing slash, which we |
| + // shouldn't include for consistency. |
| + if (buf[retval-1] == L'\\') |
| + buf[retval-1] = L'\0'; |
| + path p(buf); |
| +#else |
| const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; |
| const char* ret = nullptr; |
| |
| @@ -1357,6 +1370,7 @@ path __temp_directory_path(error_code* ec) { |
| ret = "/tmp"; |
| |
| path p(ret); |
| +#endif |
| error_code m_ec; |
| file_status st = detail::posix_stat(p, &m_ec); |
| if (!status_known(st)) |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 3f12aeb4ae3fd43d98f0c698952713d9fef7bef3 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Tue, 3 Nov 2020 23:52:32 +0200 |
| Subject: [PATCH 12/24] [libcxx] Implement parsing of root_name for paths on |
| windows |
| |
| Differential Revision: https://reviews.llvm.org/D91176 |
| |
| (cherry picked from commit 929f0bcc24e246ea02ab57df8009a6fd5751d45c) |
| --- |
| src/filesystem/operations.cpp | 92 +++++++++++++++++++++++++--- |
| 1 file changed, 85 insertions(+), 7 deletions(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 674f19154e6f..88a039f85021 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -64,6 +64,10 @@ bool isSeparator(path::value_type C) { |
| 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; |
| @@ -120,6 +124,12 @@ public: |
| |
| 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 = consumeSeparator(Start, End); |
| if (TkEnd) |
| return makeState(PS_InRootDir, Start, TkEnd); |
| @@ -142,7 +152,6 @@ public: |
| case PS_InTrailingSep: |
| return makeState(PS_AtEnd); |
| |
| - case PS_InRootName: |
| case PS_AtEnd: |
| _LIBCPP_UNREACHABLE(); |
| } |
| @@ -160,9 +169,15 @@ public: |
| if (PosPtr SepEnd = consumeSeparator(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 = consumeName(RStart, REnd); |
| + 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); |
| } |
| } |
| @@ -173,11 +188,17 @@ public: |
| PosPtr SepEnd = consumeSeparator(RStart, REnd); |
| if (SepEnd == REnd) |
| return makeState(PS_InRootDir, Path.data(), RStart + 1); |
| - PosPtr TkEnd = consumeName(SepEnd, REnd); |
| - return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 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); |
| + return makeState(PS_InRootName, Path.data(), RStart + 1); |
| case PS_InRootName: |
| case PS_BeforeBegin: |
| _LIBCPP_UNREACHABLE(); |
| @@ -284,7 +305,7 @@ private: |
| } |
| |
| PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept { |
| - if (P == End || !isSeparator(*P)) |
| + if (P == nullptr || P == End || !isSeparator(*P)) |
| return nullptr; |
| const int Inc = P < End ? 1 : -1; |
| P += Inc; |
| @@ -293,15 +314,72 @@ private: |
| return P; |
| } |
| |
| + // Consume exactly N separators, or return nullptr. |
| + PosPtr consumeNSeparators(PosPtr P, PosPtr End, int N) const noexcept { |
| + PosPtr Ret = consumeSeparator(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 { |
| - if (P == End || isSeparator(*P)) |
| + 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) { |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From bd50990c64deae4ce6027767659c75a22822e2f5 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Sun, 1 Nov 2020 23:39:03 +0200 |
| Subject: [PATCH 13/24] [libcxx] Implement is_absolute properly for windows |
| |
| Differential Revision: https://reviews.llvm.org/D91177 |
| |
| (cherry picked from commit 8a783e68452f646360d9902d2c2bc0e115d7bfa9) |
| --- |
| include/filesystem | 21 +++++++++++++++++++++ |
| 1 file changed, 21 insertions(+) |
| |
| diff --git a/include/filesystem b/include/filesystem |
| index 92e37e183def..eecf416e851c 100644 |
| --- a/include/filesystem |
| +++ b/include/filesystem |
| @@ -1341,7 +1341,28 @@ public: |
| } |
| |
| _LIBCPP_INLINE_VISIBILITY bool is_absolute() const { |
| +#if defined(_LIBCPP_WIN32API) |
| + __string_view __root_name_str = __root_name(); |
| + __string_view __root_dir = __root_directory(); |
| + if (__root_name_str.size() == 2 && __root_name_str[1] == ':') { |
| + // A drive letter with no root directory is relative, e.g. x:example. |
| + return !__root_dir.empty(); |
| + } |
| + // If no root name, it's relative, e.g. \example is relative to the current drive |
| + if (__root_name_str.empty()) |
| + return false; |
| + if (__root_name_str.size() < 3) |
| + return false; |
| + // A server root name, like \\server, is always absolute |
| + if (__root_name_str[0] != '/' && __root_name_str[0] != '\\') |
| + return false; |
| + if (__root_name_str[1] != '/' && __root_name_str[1] != '\\') |
| + return false; |
| + // Seems to be a server root name |
| + return true; |
| +#else |
| return has_root_directory(); |
| +#endif |
| } |
| _LIBCPP_INLINE_VISIBILITY bool is_relative() const { return !is_absolute(); } |
| |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From bd884f520faaa41618d6e901e1631ff02fab3044 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Wed, 4 Nov 2020 15:59:56 +0200 |
| Subject: [PATCH 14/24] [libcxx] Implement append and operator/ properly for |
| windows |
| |
| The root_path function has to be changed to return the parsed bit |
| as-is; otherwise a path like "//net" gets a root path of "//net/", as |
| the root name, "//net", gets the root directory (an empty string) appended, |
| forming "//net/". (The same doesn't happen for the root dir "c:" though.) |
| |
| Differential Revision: https://reviews.llvm.org/D91178 |
| |
| (cherry picked from commit 78d693faecf98718dadfa6e39f291e5999f380c7) |
| --- |
| include/filesystem | 52 ++++++++++++++++--- |
| .../path.member/path.append.pass.cpp | 48 +++++++++++++++++ |
| 2 files changed, 92 insertions(+), 8 deletions(-) |
| |
| diff --git a/include/filesystem b/include/filesystem |
| index eecf416e851c..201cecc0e8b2 100644 |
| --- a/include/filesystem |
| +++ b/include/filesystem |
| @@ -1006,14 +1006,44 @@ public: |
| return *this; |
| } |
| |
| -private: |
| - template <class _ECharT> |
| - static bool __source_is_absolute(_ECharT __first_or_null) { |
| - return __is_separator(__first_or_null); |
| - } |
| - |
| public: |
| // appends |
| +#if defined(_LIBCPP_WIN32API) |
| + path& operator/=(const path& __p) { |
| + auto __p_root_name = __p.__root_name(); |
| + auto __p_root_name_size = __p_root_name.size(); |
| + if (__p.is_absolute() || |
| + (!__p_root_name.empty() && __p_root_name != root_name())) { |
| + __pn_ = __p.__pn_; |
| + return *this; |
| + } |
| + if (__p.has_root_directory()) { |
| + path __root_name_str = root_name(); |
| + __pn_ = __root_name_str.native(); |
| + __pn_ += __p.__pn_.substr(__p_root_name_size); |
| + return *this; |
| + } |
| + if (has_filename() || (!has_root_directory() && is_absolute())) |
| + __pn_ += preferred_separator; |
| + __pn_ += __p.__pn_.substr(__p_root_name_size); |
| + return *this; |
| + } |
| + template <class _Source> |
| + _LIBCPP_INLINE_VISIBILITY _EnableIfPathable<_Source> |
| + operator/=(const _Source& __src) { |
| + return operator/=(path(__src)); |
| + } |
| + |
| + template <class _Source> |
| + _EnableIfPathable<_Source> append(const _Source& __src) { |
| + return operator/=(path(__src)); |
| + } |
| + |
| + template <class _InputIt> |
| + path& append(_InputIt __first, _InputIt __last) { |
| + return operator/=(path(__first, __last)); |
| + } |
| +#else |
| path& operator/=(const path& __p) { |
| if (__p.is_absolute()) { |
| __pn_ = __p.__pn_; |
| @@ -1038,7 +1068,8 @@ public: |
| _EnableIfPathable<_Source> append(const _Source& __src) { |
| using _Traits = __is_pathable<_Source>; |
| using _CVT = _PathCVT<_SourceChar<_Source> >; |
| - if (__source_is_absolute(_Traits::__first_or_null(__src))) |
| + bool __source_is_absolute = __is_separator(_Traits::__first_or_null(__src)); |
| + if (__source_is_absolute) |
| __pn_.clear(); |
| else if (has_filename()) |
| __pn_ += preferred_separator; |
| @@ -1051,13 +1082,14 @@ public: |
| typedef typename iterator_traits<_InputIt>::value_type _ItVal; |
| static_assert(__can_convert_char<_ItVal>::value, "Must convertible"); |
| using _CVT = _PathCVT<_ItVal>; |
| - if (__first != __last && __source_is_absolute(*__first)) |
| + if (__first != __last && __is_separator(*__first)) |
| __pn_.clear(); |
| else if (has_filename()) |
| __pn_ += preferred_separator; |
| _CVT::__append_range(__pn_, __first, __last); |
| return *this; |
| } |
| +#endif |
| |
| // concatenation |
| _LIBCPP_INLINE_VISIBILITY |
| @@ -1295,7 +1327,11 @@ public: |
| return string_type(__root_directory()); |
| } |
| _LIBCPP_INLINE_VISIBILITY path root_path() const { |
| +#if defined(_LIBCPP_WIN32API) |
| + return string_type(__root_path_raw()); |
| +#else |
| return root_name().append(string_type(__root_directory())); |
| +#endif |
| } |
| _LIBCPP_INLINE_VISIBILITY path relative_path() const { |
| return string_type(__relative_path()); |
| diff --git a/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp b/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| index eabd6f92da3c..ad9d06eb9849 100644 |
| --- a/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| @@ -63,6 +63,54 @@ const AppendOperatorTestcase Cases[] = |
| , {S("/p1"), S("/p2/"), S("/p2/")} |
| , {S("p1"), S(""), S("p1/")} |
| , {S("p1/"), S(""), S("p1/")} |
| + |
| + , {S("//host"), S("foo"), S("//host/foo")} |
| + , {S("//host/"), S("foo"), S("//host/foo")} |
| + , {S("//host"), S(""), S("//host/")} |
| + |
| +#ifdef _WIN32 |
| + , {S("foo"), S("C:/bar"), S("C:/bar")} |
| + , {S("foo"), S("C:"), S("C:")} |
| + |
| + , {S("C:"), S(""), S("C:")} |
| + , {S("C:foo"), S("/bar"), S("C:/bar")} |
| + , {S("C:foo"), S("bar"), S("C:foo/bar")} |
| + , {S("C:/foo"), S("bar"), S("C:/foo/bar")} |
| + , {S("C:/foo"), S("/bar"), S("C:/bar")} |
| + |
| + , {S("C:foo"), S("C:/bar"), S("C:/bar")} |
| + , {S("C:foo"), S("C:bar"), S("C:foo/bar")} |
| + , {S("C:/foo"), S("C:/bar"), S("C:/bar")} |
| + , {S("C:/foo"), S("C:bar"), S("C:/foo/bar")} |
| + |
| + , {S("C:foo"), S("c:/bar"), S("c:/bar")} |
| + , {S("C:foo"), S("c:bar"), S("c:bar")} |
| + , {S("C:/foo"), S("c:/bar"), S("c:/bar")} |
| + , {S("C:/foo"), S("c:bar"), S("c:bar")} |
| + |
| + , {S("C:/foo"), S("D:bar"), S("D:bar")} |
| +#else |
| + , {S("foo"), S("C:/bar"), S("foo/C:/bar")} |
| + , {S("foo"), S("C:"), S("foo/C:")} |
| + |
| + , {S("C:"), S(""), S("C:/")} |
| + , {S("C:foo"), S("/bar"), S("/bar")} |
| + , {S("C:foo"), S("bar"), S("C:foo/bar")} |
| + , {S("C:/foo"), S("bar"), S("C:/foo/bar")} |
| + , {S("C:/foo"), S("/bar"), S("/bar")} |
| + |
| + , {S("C:foo"), S("C:/bar"), S("C:foo/C:/bar")} |
| + , {S("C:foo"), S("C:bar"), S("C:foo/C:bar")} |
| + , {S("C:/foo"), S("C:/bar"), S("C:/foo/C:/bar")} |
| + , {S("C:/foo"), S("C:bar"), S("C:/foo/C:bar")} |
| + |
| + , {S("C:foo"), S("c:/bar"), S("C:foo/c:/bar")} |
| + , {S("C:foo"), S("c:bar"), S("C:foo/c:bar")} |
| + , {S("C:/foo"), S("c:/bar"), S("C:/foo/c:/bar")} |
| + , {S("C:/foo"), S("c:bar"), S("C:/foo/c:bar")} |
| + |
| + , {S("C:/foo"), S("D:bar"), S("C:/foo/D:bar")} |
| +#endif |
| }; |
| |
| |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 6131abb23fc2990f9a869ef3d9c63bb0fd5ff464 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Thu, 5 Nov 2020 23:09:15 +0200 |
| Subject: [PATCH 15/24] [libcxx] Have lexically_normal return the path with |
| preferred separators |
| |
| Differential Revision: https://reviews.llvm.org/D91179 |
| |
| (cherry picked from commit 513463fd266f059864ce3c0236494cced5de0f56) |
| --- |
| src/filesystem/operations.cpp | 1 + |
| 1 file changed, 1 insertion(+) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 88a039f85021..a3b93b594a07 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1719,6 +1719,7 @@ path path::lexically_normal() const { |
| if (NeedTrailingSep) |
| Result /= PS(""); |
| |
| + Result.make_preferred(); |
| return Result; |
| } |
| |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 415ea201cb19775f253dfb0cf8dc561891cff02d Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Mon, 9 Nov 2020 11:45:13 +0200 |
| Subject: [PATCH 16/24] [libcxx] Make generic_*string return paths with forward |
| slashes on windows |
| |
| This matches what MS STL returns; in std::filesystem, forward slashes |
| are considered generic dir separators that are valid on all platforms. |
| |
| Differential Revision: https://reviews.llvm.org/D91181 |
| |
| (cherry picked from commit f4f5fb915104887fefa602cfcbd1d4fc8447d46b) |
| --- |
| include/filesystem | 22 ++++++++++++++++--- |
| .../path.generic.obs/named_overloads.pass.cpp | 15 +++++++++---- |
| 2 files changed, 30 insertions(+), 7 deletions(-) |
| |
| diff --git a/include/filesystem b/include/filesystem |
| index 201cecc0e8b2..19efca2d8fc2 100644 |
| --- a/include/filesystem |
| +++ b/include/filesystem |
| @@ -1193,7 +1193,12 @@ public: |
| #if defined(_LIBCPP_WIN32API) |
| _LIBCPP_INLINE_VISIBILITY _VSTD::wstring wstring() const { return __pn_; } |
| |
| - _VSTD::wstring generic_wstring() const { return __pn_; } |
| + _VSTD::wstring generic_wstring() const { |
| + _VSTD::wstring __s; |
| + __s.resize(__pn_.size()); |
| + _VSTD::replace_copy(__pn_.begin(), __pn_.end(), __s.begin(), '\\', '/'); |
| + return __s; |
| + } |
| |
| #if !defined(_LIBCPP_HAS_NO_LOCALIZATION) |
| template <class _ECharT, class _Traits = char_traits<_ECharT>, |
| @@ -1230,13 +1235,24 @@ public: |
| class _Allocator = allocator<_ECharT> > |
| basic_string<_ECharT, _Traits, _Allocator> |
| generic_string(const _Allocator& __a = _Allocator()) const { |
| - return string<_ECharT, _Traits, _Allocator>(__a); |
| + using _Str = basic_string<_ECharT, _Traits, _Allocator>; |
| + _Str __s = string<_ECharT, _Traits, _Allocator>(__a); |
| + // Note: This (and generic_u8string below) is slightly suboptimal as |
| + // it iterates twice over the string; once to convert it to the right |
| + // character type, and once to replace path delimiters. |
| + _VSTD::replace(__s.begin(), __s.end(), |
| + static_cast<_ECharT>('\\'), static_cast<_ECharT>('/')); |
| + return __s; |
| } |
| |
| _VSTD::string generic_string() const { return generic_string<char>(); } |
| _VSTD::u16string generic_u16string() const { return generic_string<char16_t>(); } |
| _VSTD::u32string generic_u32string() const { return generic_string<char32_t>(); } |
| - __u8_string generic_u8string() const { return u8string(); } |
| + __u8_string generic_u8string() const { |
| + __u8_string __s = u8string(); |
| + _VSTD::replace(__s.begin(), __s.end(), '\\', '/'); |
| + return __s; |
| + } |
| #endif /* !_LIBCPP_HAS_NO_LOCALIZATION */ |
| #else /* _LIBCPP_WIN32API */ |
| |
| diff --git a/test/std/input.output/filesystems/class.path/path.member/path.generic.obs/named_overloads.pass.cpp b/test/std/input.output/filesystems/class.path/path.member/path.generic.obs/named_overloads.pass.cpp |
| index 58c07e2feb70..d8991592efcf 100644 |
| --- a/test/std/input.output/filesystems/class.path/path.member/path.generic.obs/named_overloads.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.path/path.member/path.generic.obs/named_overloads.pass.cpp |
| @@ -32,17 +32,24 @@ |
| #include "min_allocator.h" |
| #include "filesystem_test_helper.h" |
| |
| -MultiStringType longString = MKSTR("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/123456789/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); |
| +MultiStringType input = MKSTR("c:\\foo\\bar"); |
| +#ifdef _WIN32 |
| +// On windows, the generic_* accessors return a path with forward slashes |
| +MultiStringType ref = MKSTR("c:/foo/bar"); |
| +#else |
| +// On posix, the input string is returned as-is |
| +MultiStringType ref = MKSTR("c:\\foo\\bar"); |
| +#endif |
| |
| int main(int, char**) |
| { |
| using namespace fs; |
| - auto const& MS = longString; |
| - const char* value = longString; |
| + auto const& MS = ref; |
| + const char* value = input; |
| const path p(value); |
| { |
| std::string s = p.generic_string(); |
| - assert(s == value); |
| + assert(s == (const char*)MS); |
| } |
| { |
| #if TEST_STD_VER > 17 && defined(__cpp_char8_t) |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From c8c08547b2d1e87391213846c7e17f5426a3dd48 Mon Sep 17 00:00:00 2001 |
| From: Zbigniew Sarbinowski <zibi@ca.ibm.com> |
| Date: Thu, 18 Feb 2021 14:49:46 +0000 |
| Subject: [PATCH 17/24] [SystemZ][ZOS] Guard using declaration for ::fchmodat |
| |
| The use of fchmodat() is beeing guarded but its using declaration is not. Let's use the same guard in both places to avoid compiler errors on platforms where `fchmodat` does not exist. |
| |
| Reviewed By: #libc, ldionne |
| |
| Differential Revision: https://reviews.llvm.org/D96303 |
| |
| (cherry picked from commit 25aa0d12445eed6e278489546d18fcb7a33cfaa6) |
| --- |
| src/filesystem/posix_compat.h | 2 ++ |
| 1 file changed, 2 insertions(+) |
| |
| diff --git a/src/filesystem/posix_compat.h b/src/filesystem/posix_compat.h |
| index 8062bd65ce00..f5c8ec39df2b 100644 |
| --- a/src/filesystem/posix_compat.h |
| +++ b/src/filesystem/posix_compat.h |
| @@ -487,7 +487,9 @@ int symlink_dir(const char *oldname, const char *newname) { |
| using ::chdir; |
| using ::close; |
| using ::fchmod; |
| +#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) |
| using ::fchmodat; |
| +#endif |
| using ::fstat; |
| using ::ftruncate; |
| using ::getcwd; |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From ff2e16fe051405232fbd9a09c406767b229d27ec Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Thu, 5 Nov 2020 00:13:22 +0200 |
| Subject: [PATCH 18/24] [libcxx] Enable filesystem by default for mingw targets |
| |
| This feature can be built successfully for windows now. However, |
| the helper functions for __int128_t aren't available in MSVC |
| configurations, so don't enable it by default there yet. (See |
| https://reviews.llvm.org/D91139 for discussion on how to proceed |
| with things in MSVC environments.) |
| |
| Differential Revision: https://reviews.llvm.org/D97075 |
| |
| (cherry picked from commit 99fc4a65847a7020ae328e42a67e80cc29c1e762) |
| --- |
| CMakeLists.txt | 6 +++++- |
| 1 file changed, 5 insertions(+), 1 deletion(-) |
| |
| diff --git a/CMakeLists.txt b/CMakeLists.txt |
| index 9bf1a02f0908..b8716a34a325 100644 |
| --- a/CMakeLists.txt |
| +++ b/CMakeLists.txt |
| @@ -90,7 +90,11 @@ option(LIBCXX_ENABLE_SHARED "Build libc++ as a shared library." ON) |
| option(LIBCXX_ENABLE_STATIC "Build libc++ as a static library." ON) |
| option(LIBCXX_ENABLE_EXPERIMENTAL_LIBRARY "Build libc++experimental.a" ON) |
| set(ENABLE_FILESYSTEM_DEFAULT ON) |
| -if (WIN32) |
| +if (WIN32 AND NOT MINGW) |
| + # Filesystem is buildable for windows, but it requires __int128 helper |
| + # functions, that currently are provided by libgcc or compiler_rt builtins. |
| + # These are available in MinGW environments, but not currently in MSVC |
| + # environments. |
| set(ENABLE_FILESYSTEM_DEFAULT OFF) |
| endif() |
| option(LIBCXX_ENABLE_FILESYSTEM "Build filesystem as part of the main libc++ library" |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 69856f3e59b210249020504329e016c691f138e9 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Fri, 18 Dec 2020 13:34:35 +0200 |
| Subject: [PATCH 19/24] [libcxx] Explicitly return the expected error code in |
| create_directories if the parent isn't a directory |
| |
| On windows, going ahead and actually trying to create the directory |
| doesn't return an error code that maps to |
| std::errc::not_a_directory in this case. |
| |
| This fixes two cases of |
| TEST_CHECK(ErrorIs(ec, std::errc::not_a_directory)) |
| in filesystems/fs.op.funcs/fs.op.create_directories/create_directories.pass.cpp |
| for windows (in testcases added in 59c72a70121567f7aee347e96b4ac8f3cfe9f4b2). |
| |
| Differential Revision: https://reviews.llvm.org/D97090 |
| |
| (cherry picked from commit c5e8f024dca9ddf6d14253fe2fcc5c4956de2d3c) |
| --- |
| src/filesystem/operations.cpp | 3 ++- |
| 1 file changed, 2 insertions(+), 1 deletion(-) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index a3b93b594a07..bfc6c44d4ce4 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1022,7 +1022,8 @@ bool __create_directories(const path& p, error_code* ec) { |
| if (ec && *ec) { |
| return false; |
| } |
| - } |
| + } else if (not is_directory(parent_st)) |
| + return err.report(errc::not_a_directory); |
| } |
| return __create_directory(p, ec); |
| } |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 6c968da9f1e9281055d31f66c292f81de204924d Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Sat, 27 Feb 2021 19:12:25 +0200 |
| Subject: [PATCH 20/24] [libcxx] Avoid infinite recursion in |
| create_directories, if the root directory doesn't exist |
| |
| Differential Revision: https://reviews.llvm.org/D97618 |
| |
| (cherry picked from commit 99c7b532946508efcf6cd978d86ee24b2a66d096) |
| --- |
| src/filesystem/operations.cpp | 2 ++ |
| .../create_directories.pass.cpp | 14 ++++++++++++++ |
| 2 files changed, 16 insertions(+) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index bfc6c44d4ce4..7c77660538b5 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -1018,6 +1018,8 @@ bool __create_directories(const path& p, error_code* ec) { |
| if (not status_known(parent_st)) |
| return err.report(m_ec); |
| if (not exists(parent_st)) { |
| + if (parent == p) |
| + return err.report(errc::invalid_argument); |
| __create_directories(parent, ec); |
| if (ec && *ec) { |
| return false; |
| diff --git a/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_directories/create_directories.pass.cpp b/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_directories/create_directories.pass.cpp |
| index 9ce450a0e48d..54820cad0f55 100644 |
| --- a/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_directories/create_directories.pass.cpp |
| +++ b/test/std/input.output/filesystems/fs.op.funcs/fs.op.create_directories/create_directories.pass.cpp |
| @@ -138,4 +138,18 @@ TEST_CASE(dest_final_part_is_file) |
| TEST_CHECK(!exists(dir)); |
| } |
| |
| +#ifdef _WIN32 |
| +TEST_CASE(nonexistent_root) |
| +{ |
| + std::error_code ec = GetTestEC(); |
| + // If Q:\ doesn't exist, create_directories would try to recurse upwards |
| + // to parent_path() until it finds a directory that does exist. As the |
| + // whole path is the root name, parent_path() returns itself, and it |
| + // would recurse indefinitely, unless the recursion is broken. |
| + if (!exists("Q:\\")) |
| + TEST_CHECK(fs::create_directories("Q:\\", ec) == false); |
| + TEST_CHECK(fs::create_directories("\\\\nonexistentserver", ec) == false); |
| +} |
| +#endif |
| + |
| TEST_SUITE_END() |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From cd87c3f6b36fd6cd991b8e3a3ac85dc208d55ea1 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Sat, 27 Feb 2021 16:09:49 +0200 |
| Subject: [PATCH 21/24] [libcxx] Map ERROR_BAD_PATHNAME to |
| errc::no_such_file_or_directory on windows |
| |
| Opening a path like \\server (without a trailing share name and |
| path) produces this error, while opening e.g. \\server\share |
| (for a nonexistent server/share) produces ERROR_BAD_NETPATH (which |
| already is mapped). |
| |
| This happens in some testcases (in fs.op.proximate); as proximate() |
| calls weakly_canonical() on the inputs, weakly_canonical() checks |
| whether the path exists or not. When the error code wasn't recognized |
| (it mapped to errc::invalid_argument), the stat operation wasn't |
| conclusive and weakly_canonical() errored out. With the proper error |
| code mapping, this isn't considered an error, just a nonexistent |
| path, and weakly_canonical() can proceed. |
| |
| This roughly matches what MS STL does - it doesn't have |
| ERROR_BAD_PATHNAME in its error code mapping table, but it |
| checks for this error code specifically in the return of their |
| correspondence of the stat function. |
| |
| Differential Revision: https://reviews.llvm.org/D97619 |
| |
| (cherry picked from commit 29012ce986fcb24175f19317b4e2d119cd8cdbb2) |
| --- |
| src/filesystem/operations.cpp | 1 + |
| 1 file changed, 1 insertion(+) |
| |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 7c77660538b5..360643d4c241 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -411,6 +411,7 @@ errc __win_err_to_errc(int err) { |
| {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}, |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 497842a9ebb0ca1a6f871a07c0f4b4f1c700a798 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Thu, 11 Mar 2021 13:06:08 +0200 |
| Subject: [PATCH 22/24] [libcxx] Avoid intermediate string objects for |
| substrings in windows operator/= |
| |
| Check that appends with a path object doesn't do allocations, even |
| on windows. |
| |
| Suggested by Marek in D98398. The patch might apply without D98398 |
| (depending on how much of the diff context has to match), but doesn't |
| make much sense until after that patch has landed. |
| |
| Differential Revision: https://reviews.llvm.org/D98412 |
| |
| (cherry picked from commit 49173ca4db21e4d1576c2440b79ebff48c6c4156) |
| --- |
| include/filesystem | 4 ++-- |
| .../class.path/path.member/path.append.pass.cpp | 9 +++++++++ |
| 2 files changed, 11 insertions(+), 2 deletions(-) |
| |
| diff --git a/include/filesystem b/include/filesystem |
| index 19efca2d8fc2..2fc110715559 100644 |
| --- a/include/filesystem |
| +++ b/include/filesystem |
| @@ -1020,12 +1020,12 @@ public: |
| if (__p.has_root_directory()) { |
| path __root_name_str = root_name(); |
| __pn_ = __root_name_str.native(); |
| - __pn_ += __p.__pn_.substr(__p_root_name_size); |
| + __pn_ += __string_view(__p.__pn_).substr(__p_root_name_size); |
| return *this; |
| } |
| if (has_filename() || (!has_root_directory() && is_absolute())) |
| __pn_ += preferred_separator; |
| - __pn_ += __p.__pn_.substr(__p_root_name_size); |
| + __pn_ += __string_view(__p.__pn_).substr(__p_root_name_size); |
| return *this; |
| } |
| template <class _Source> |
| diff --git a/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp b/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| index ad9d06eb9849..834d5e999abb 100644 |
| --- a/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.path/path.member/path.append.pass.cpp |
| @@ -189,6 +189,15 @@ void doAppendSourceAllocTest(AppendOperatorTestcase const& TC) |
| } |
| assert(PathEq(LHS, E)); |
| } |
| + { |
| + path LHS(L); PathReserve(LHS, ReserveSize); |
| + path RHS(R); |
| + { |
| + DisableAllocationGuard g; |
| + LHS /= RHS; |
| + } |
| + assert(PathEq(LHS, E)); |
| + } |
| // input iterator - For non-native char types, appends needs to copy the |
| // iterator range into a contiguous block of memory before it can perform the |
| // code_cvt conversions. |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 9a1bb266e38b31c78de03abc2d41cdd3245595d0 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Martin=20Storsj=C3=B6?= <martin@martin.st> |
| Date: Mon, 8 Mar 2021 12:03:30 +0200 |
| Subject: [PATCH 23/24] [libcxx] Test accessing a directory on windows that |
| gives "access denied" errors |
| |
| Fix handling of skip_permission_denied on windows; after converting |
| the return value of GetLastError() to a standard error_code, ec.value() |
| is in the standard errc range, not a native windows error code. This |
| was missed in 156180727d6c347eda3ba749730707acb8a48093. |
| |
| The directory "C:\System Volume Information" does seem to exist and |
| have these properties on most relevant contempory setups. |
| |
| Differential Revision: https://reviews.llvm.org/D98166 |
| |
| (cherry picked from commit e69c65d5c45557e140b13237448581d28bbd5846) |
| --- |
| src/filesystem/directory_iterator.cpp | 3 ++- |
| .../directory_entry.cons/path.pass.cpp | 18 +++++++++++++ |
| .../directory_iterator.members/ctor.pass.cpp | 8 ++++++ |
| .../rec.dir.itr.members/ctor.pass.cpp | 8 ++++++ |
| .../fs.op.funcs/fs.op.exists/exists.pass.cpp | 14 ++++++++--- |
| test/support/filesystem_test_helper.h | 25 +++++++++++++++++++ |
| 6 files changed, 72 insertions(+), 4 deletions(-) |
| |
| diff --git a/src/filesystem/directory_iterator.cpp b/src/filesystem/directory_iterator.cpp |
| index 2721dea5c98f..bb3653076bfc 100644 |
| --- a/src/filesystem/directory_iterator.cpp |
| +++ b/src/filesystem/directory_iterator.cpp |
| @@ -124,7 +124,8 @@ public: |
| ec = detail::make_windows_error(GetLastError()); |
| const bool ignore_permission_denied = |
| bool(opts & directory_options::skip_permission_denied); |
| - if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED) |
| + if (ignore_permission_denied && |
| + ec.value() == static_cast<int>(errc::permission_denied)) |
| ec.clear(); |
| return; |
| } |
| diff --git a/test/std/input.output/filesystems/class.directory_entry/directory_entry.cons/path.pass.cpp b/test/std/input.output/filesystems/class.directory_entry/directory_entry.cons/path.pass.cpp |
| index e3759e22b0ba..a118fae4d54b 100644 |
| --- a/test/std/input.output/filesystems/class.directory_entry/directory_entry.cons/path.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.directory_entry/directory_entry.cons/path.pass.cpp |
| @@ -148,6 +148,23 @@ TEST_CASE(path_ctor_dne) { |
| |
| TEST_CASE(path_ctor_cannot_resolve) { |
| using namespace fs; |
| +#ifdef _WIN32 |
| + // Windows doesn't support setting perms::none to trigger failures |
| + // reading directories; test using a special inaccessible directory |
| + // instead. |
| + const path dir = GetWindowsInaccessibleDir(); |
| + TEST_REQUIRE(!dir.empty()); |
| + const path file = dir / "file"; |
| + { |
| + std::error_code ec = GetTestEC(); |
| + directory_entry ent(file, ec); |
| + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); |
| + TEST_CHECK(ent.path() == file); |
| + } |
| + { |
| + TEST_CHECK_NO_THROW(directory_entry(file)); |
| + } |
| +#else |
| scoped_test_env env; |
| const path dir = env.create_dir("dir"); |
| const path file = env.create_file("dir/file", 42); |
| @@ -179,6 +196,7 @@ TEST_CASE(path_ctor_cannot_resolve) { |
| TEST_CHECK_NO_THROW(directory_entry(sym_in_dir)); |
| TEST_CHECK_NO_THROW(directory_entry(sym_out_of_dir)); |
| } |
| +#endif |
| } |
| |
| TEST_SUITE_END() |
| diff --git a/test/std/input.output/filesystems/class.directory_iterator/directory_iterator.members/ctor.pass.cpp b/test/std/input.output/filesystems/class.directory_iterator/directory_iterator.members/ctor.pass.cpp |
| index 48cf72a77b05..fae696613c75 100644 |
| --- a/test/std/input.output/filesystems/class.directory_iterator/directory_iterator.members/ctor.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.directory_iterator/directory_iterator.members/ctor.pass.cpp |
| @@ -87,6 +87,13 @@ TEST_CASE(test_construction_from_bad_path) |
| TEST_CASE(access_denied_test_case) |
| { |
| using namespace fs; |
| +#ifdef _WIN32 |
| + // Windows doesn't support setting perms::none to trigger failures |
| + // reading directories; test using a special inaccessible directory |
| + // instead. |
| + const path testDir = GetWindowsInaccessibleDir(); |
| + TEST_REQUIRE(!testDir.empty()); |
| +#else |
| scoped_test_env env; |
| path const testDir = env.make_env_path("dir1"); |
| path const testFile = testDir / "testFile"; |
| @@ -100,6 +107,7 @@ TEST_CASE(access_denied_test_case) |
| } |
| // Change the permissions so we can no longer iterate |
| permissions(testDir, perms::none); |
| +#endif |
| |
| // Check that the construction fails when skip_permissions_denied is |
| // not given. |
| diff --git a/test/std/input.output/filesystems/class.rec.dir.itr/rec.dir.itr.members/ctor.pass.cpp b/test/std/input.output/filesystems/class.rec.dir.itr/rec.dir.itr.members/ctor.pass.cpp |
| index ddffaca39335..53ca5737ba31 100644 |
| --- a/test/std/input.output/filesystems/class.rec.dir.itr/rec.dir.itr.members/ctor.pass.cpp |
| +++ b/test/std/input.output/filesystems/class.rec.dir.itr/rec.dir.itr.members/ctor.pass.cpp |
| @@ -88,6 +88,13 @@ TEST_CASE(test_construction_from_bad_path) |
| TEST_CASE(access_denied_test_case) |
| { |
| using namespace fs; |
| +#ifdef _WIN32 |
| + // Windows doesn't support setting perms::none to trigger failures |
| + // reading directories; test using a special inaccessible directory |
| + // instead. |
| + const path testDir = GetWindowsInaccessibleDir(); |
| + TEST_REQUIRE(!testDir.empty()); |
| +#else |
| scoped_test_env env; |
| path const testDir = env.make_env_path("dir1"); |
| path const testFile = testDir / "testFile"; |
| @@ -102,6 +109,7 @@ TEST_CASE(access_denied_test_case) |
| |
| // Change the permissions so we can no longer iterate |
| permissions(testDir, perms::none); |
| +#endif |
| |
| // Check that the construction fails when skip_permissions_denied is |
| // not given. |
| diff --git a/test/std/input.output/filesystems/fs.op.funcs/fs.op.exists/exists.pass.cpp b/test/std/input.output/filesystems/fs.op.funcs/fs.op.exists/exists.pass.cpp |
| index 581d9fe041a9..1b7131bbadff 100644 |
| --- a/test/std/input.output/filesystems/fs.op.funcs/fs.op.exists/exists.pass.cpp |
| +++ b/test/std/input.output/filesystems/fs.op.funcs/fs.op.exists/exists.pass.cpp |
| @@ -73,16 +73,24 @@ TEST_CASE(test_exist_not_found) |
| |
| TEST_CASE(test_exists_fails) |
| { |
| +#ifdef _WIN32 |
| + // Windows doesn't support setting perms::none to trigger failures |
| + // reading directories; test using a special inaccessible directory |
| + // instead. |
| + const path p = GetWindowsInaccessibleDir(); |
| + TEST_REQUIRE(!p.empty()); |
| +#else |
| scoped_test_env env; |
| const path dir = env.create_dir("dir"); |
| - const path file = env.create_file("dir/file", 42); |
| + const path p = env.create_file("dir/file", 42); |
| permissions(dir, perms::none); |
| +#endif |
| |
| std::error_code ec; |
| - TEST_CHECK(exists(file, ec) == false); |
| + TEST_CHECK(exists(p, ec) == false); |
| TEST_CHECK(ec); |
| |
| - TEST_CHECK_THROW(filesystem_error, exists(file)); |
| + TEST_CHECK_THROW(filesystem_error, exists(p)); |
| } |
| |
| TEST_CASE(test_name_too_long) { |
| diff --git a/test/support/filesystem_test_helper.h b/test/support/filesystem_test_helper.h |
| index eeee3e935b8a..92c017f610fe 100644 |
| --- a/test/support/filesystem_test_helper.h |
| +++ b/test/support/filesystem_test_helper.h |
| @@ -690,4 +690,29 @@ struct ExceptionChecker { |
| |
| }; |
| |
| +inline fs::path GetWindowsInaccessibleDir() { |
| + // Only makes sense on windows, but the code can be compiled for |
| + // any platform. |
| + const fs::path dir("C:\\System Volume Information"); |
| + std::error_code ec; |
| + const fs::path root("C:\\"); |
| + fs::directory_iterator it(root, ec); |
| + if (ec) |
| + return fs::path(); |
| + const fs::directory_iterator endIt{}; |
| + while (it != endIt) { |
| + const fs::directory_entry &ent = *it; |
| + if (ent == dir) { |
| + // Basic sanity checks on the directory_entry |
| + if (!ent.exists()) |
| + return fs::path(); |
| + if (!ent.is_directory()) |
| + return fs::path(); |
| + return ent; |
| + } |
| + ++it; |
| + } |
| + return fs::path(); |
| +} |
| + |
| #endif /* FILESYSTEM_TEST_HELPER_HPP */ |
| -- |
| 2.31.1.windows.1 |
| |
| |
| From 7a94808f488301270f4a55ff7a17a8ebff3d388c Mon Sep 17 00:00:00 2001 |
| From: Arthur O'Dwyer <arthur.j.odwyer@gmail.com> |
| Date: Fri, 5 Mar 2021 20:13:35 -0500 |
| Subject: [PATCH 24/24] [libc++] Improve src/filesystem's formatting of paths. |
| |
| This is my attempt to merge D98077 (bugfix the format strings for |
| Windows paths, which use wchar_t not char) |
| and D96986 (replace C++ variadic templates with C-style varargs so that |
| `__attribute__((format(printf)))` can be applied, for better safety) |
| and D98065 (remove an unused function overload). |
| |
| The one intentional functional change here is in `__create_what`. |
| It now prints path1 and path2 in square-brackets _and_ double-quotes, |
| rather than just square-brackets. Prior to this patch, it would |
| print either path double-quoted if-and-only-if it was the empty |
| string. Now the double-quotes are always present. I doubt anybody's |
| code is relying on the current format, right? |
| |
| Differential Revision: https://reviews.llvm.org/D98097 |
| --- |
| include/__config | 7 + |
| src/filesystem/directory_iterator.cpp | 7 +- |
| src/filesystem/filesystem_common.h | 146 ++++++++++--------- |
| src/filesystem/operations.cpp | 25 ++-- |
| test/support/filesystem_test_helper.h | 4 +- |
| 5 files changed, 101 insertions(+), 88 deletions(-) |
| |
| diff --git a/include/__config b/include/__config |
| index a3838c89e8e1..89838ca76c8c 100644 |
| --- a/include/__config |
| +++ b/include/__config |
| @@ -1445,6 +1445,13 @@ extern "C" _LIBCPP_FUNC_VIS void __sanitizer_annotate_contiguous_container( |
| # define _LIBCPP_INIT_PRIORITY_MAX |
| #endif |
| |
| +#if defined(__GNUC__) || defined(__clang__) |
| +#define _LIBCPP_FORMAT_PRINTF(a, b) \ |
| + __attribute__((__format__(__printf__, a, b))) |
| +#else |
| +#define _LIBCPP_FORMAT_PRINTF(a, b) |
| +#endif |
| + |
| #endif // __cplusplus |
| |
| #endif // _LIBCPP_CONFIG |
| diff --git a/src/filesystem/directory_iterator.cpp b/src/filesystem/directory_iterator.cpp |
| index bb3653076bfc..7b83ba9ff123 100644 |
| --- a/src/filesystem/directory_iterator.cpp |
| +++ b/src/filesystem/directory_iterator.cpp |
| @@ -273,7 +273,7 @@ directory_iterator& directory_iterator::__increment(error_code* ec) { |
| path root = move(__imp_->__root_); |
| __imp_.reset(); |
| if (m_ec) |
| - err.report(m_ec, "at root \"%s\"", root); |
| + err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str()); |
| } |
| return *this; |
| } |
| @@ -360,7 +360,7 @@ void recursive_directory_iterator::__advance(error_code* ec) { |
| if (m_ec) { |
| path root = move(stack.top().__root_); |
| __imp_.reset(); |
| - err.report(m_ec, "at root \"%s\"", root); |
| + err.report(m_ec, "at root " PATH_CSTR_FMT, root.c_str()); |
| } else { |
| __imp_.reset(); |
| } |
| @@ -405,7 +405,8 @@ bool recursive_directory_iterator::__try_recursion(error_code* ec) { |
| } else { |
| path at_ent = move(curr_it.__entry_.__p_); |
| __imp_.reset(); |
| - err.report(m_ec, "attempting recursion into \"%s\"", at_ent); |
| + err.report(m_ec, "attempting recursion into " PATH_CSTR_FMT, |
| + at_ent.c_str()); |
| } |
| } |
| return false; |
| diff --git a/src/filesystem/filesystem_common.h b/src/filesystem/filesystem_common.h |
| index a4505171c3eb..df50a7de1059 100644 |
| --- a/src/filesystem/filesystem_common.h |
| +++ b/src/filesystem/filesystem_common.h |
| @@ -42,8 +42,10 @@ |
| |
| #if defined(_LIBCPP_WIN32API) |
| #define PS(x) (L##x) |
| +#define PATH_CSTR_FMT "\"%ls\"" |
| #else |
| #define PS(x) (x) |
| +#define PATH_CSTR_FMT "\"%s\"" |
| #endif |
| |
| _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM |
| @@ -57,68 +59,47 @@ errc __win_err_to_errc(int err); |
| |
| namespace { |
| |
| -static string format_string_imp(const char* msg, ...) { |
| - // we might need a second shot at this, so pre-emptivly make a copy |
| - struct GuardVAList { |
| - va_list& target; |
| - bool active = true; |
| - GuardVAList(va_list& tgt) : target(tgt), active(true) {} |
| - void clear() { |
| - if (active) |
| - va_end(target); |
| - active = false; |
| - } |
| - ~GuardVAList() { |
| - if (active) |
| - va_end(target); |
| - } |
| - }; |
| - va_list args; |
| - va_start(args, msg); |
| - GuardVAList args_guard(args); |
| - |
| - va_list args_cp; |
| - va_copy(args_cp, args); |
| - GuardVAList args_copy_guard(args_cp); |
| - |
| - std::string result; |
| - |
| - array<char, 256> local_buff; |
| - size_t size_with_null = local_buff.size(); |
| - auto ret = ::vsnprintf(local_buff.data(), size_with_null, msg, args_cp); |
| - |
| - args_copy_guard.clear(); |
| - |
| - // handle empty expansion |
| - if (ret == 0) |
| - return result; |
| - if (static_cast<size_t>(ret) < size_with_null) { |
| - result.assign(local_buff.data(), static_cast<size_t>(ret)); |
| - return result; |
| +static _LIBCPP_FORMAT_PRINTF(1, 0) string |
| +format_string_impl(const char* msg, va_list ap) { |
| + array<char, 256> buf; |
| + |
| + va_list apcopy; |
| + va_copy(apcopy, ap); |
| + int ret = ::vsnprintf(buf.data(), buf.size(), msg, apcopy); |
| + va_end(apcopy); |
| + |
| + string result; |
| + if (static_cast<size_t>(ret) < buf.size()) { |
| + result.assign(buf.data(), static_cast<size_t>(ret)); |
| + } else { |
| + // we did not provide a long enough buffer on our first attempt. The |
| + // return value is the number of bytes (excluding the null byte) that are |
| + // needed for formatting. |
| + size_t size_with_null = static_cast<size_t>(ret) + 1; |
| + result.__resize_default_init(size_with_null - 1); |
| + ret = ::vsnprintf(&result[0], size_with_null, msg, ap); |
| + _LIBCPP_ASSERT(static_cast<size_t>(ret) == (size_with_null - 1), "TODO"); |
| } |
| - |
| - // 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_with_null = static_cast<size_t>(ret) + 1; |
| - result.__resize_default_init(size_with_null - 1); |
| - ret = ::vsnprintf(&result[0], size_with_null, msg, args); |
| - _LIBCPP_ASSERT(static_cast<size_t>(ret) == (size_with_null - 1), "TODO"); |
| - |
| return result; |
| } |
| |
| -const path::value_type* unwrap(path::string_type const& s) { return s.c_str(); } |
| -const path::value_type* unwrap(path const& p) { return p.native().c_str(); } |
| -template <class Arg> |
| -Arg const& unwrap(Arg const& a) { |
| - static_assert(!is_class<Arg>::value, "cannot pass class here"); |
| - return a; |
| -} |
| - |
| -template <class... Args> |
| -string format_string(const char* fmt, Args const&... args) { |
| - return format_string_imp(fmt, unwrap(args)...); |
| +static _LIBCPP_FORMAT_PRINTF(1, 2) string |
| +format_string(const char* msg, ...) { |
| + string ret; |
| + va_list ap; |
| + va_start(ap, msg); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + try { |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + ret = format_string_impl(msg, ap); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + } catch (...) { |
| + va_end(ap); |
| + throw; |
| + } |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + va_end(ap); |
| + return ret; |
| } |
| |
| error_code capture_errno() { |
| @@ -190,14 +171,14 @@ struct ErrorHandler { |
| _LIBCPP_UNREACHABLE(); |
| } |
| |
| - template <class... Args> |
| - T report(const error_code& ec, const char* msg, Args const&... args) const { |
| + _LIBCPP_FORMAT_PRINTF(3, 0) |
| + void report_impl(const error_code& ec, const char* msg, va_list ap) const { |
| if (ec_) { |
| *ec_ = ec; |
| - return error_value<T>(); |
| + return; |
| } |
| string what = |
| - string("in ") + func_name_ + ": " + format_string(msg, args...); |
| + string("in ") + func_name_ + ": " + format_string_impl(msg, ap); |
| switch (bool(p1_) + bool(p2_)) { |
| case 0: |
| __throw_filesystem_error(what, ec); |
| @@ -209,11 +190,44 @@ struct ErrorHandler { |
| _LIBCPP_UNREACHABLE(); |
| } |
| |
| - T report(errc const& err) const { return report(make_error_code(err)); } |
| + _LIBCPP_FORMAT_PRINTF(3, 4) |
| + T report(const error_code& ec, const char* msg, ...) const { |
| + va_list ap; |
| + va_start(ap, msg); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + try { |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + report_impl(ec, msg, ap); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + } catch (...) { |
| + va_end(ap); |
| + throw; |
| + } |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + va_end(ap); |
| + return error_value<T>(); |
| + } |
| + |
| + T report(errc const& err) const { |
| + return report(make_error_code(err)); |
| + } |
| |
| - template <class... Args> |
| - T report(errc const& err, const char* msg, Args const&... args) const { |
| - return report(make_error_code(err), msg, args...); |
| + _LIBCPP_FORMAT_PRINTF(3, 4) |
| + T report(errc const& err, const char* msg, ...) const { |
| + va_list ap; |
| + va_start(ap, msg); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + try { |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + report_impl(make_error_code(err), msg, ap); |
| +#ifndef _LIBCPP_NO_EXCEPTIONS |
| + } catch (...) { |
| + va_end(ap); |
| + throw; |
| + } |
| +#endif // _LIBCPP_NO_EXCEPTIONS |
| + va_end(ap); |
| + return error_value<T>(); |
| } |
| |
| private: |
| diff --git a/src/filesystem/operations.cpp b/src/filesystem/operations.cpp |
| index 360643d4c241..0cc4bad34c72 100644 |
| --- a/src/filesystem/operations.cpp |
| +++ b/src/filesystem/operations.cpp |
| @@ -666,27 +666,20 @@ _FilesystemClock::time_point _FilesystemClock::now() noexcept { |
| |
| filesystem_error::~filesystem_error() {} |
| |
| -#if defined(_LIBCPP_WIN32API) |
| -#define PS_FMT "%ls" |
| -#else |
| -#define PS_FMT "%s" |
| -#endif |
| - |
| void filesystem_error::__create_what(int __num_paths) { |
| const char* derived_what = system_error::what(); |
| __storage_->__what_ = [&]() -> string { |
| - const path::value_type* p1 = path1().native().empty() ? PS("\"\"") : path1().c_str(); |
| - const path::value_type* p2 = path2().native().empty() ? PS("\"\"") : path2().c_str(); |
| switch (__num_paths) { |
| - default: |
| + case 0: |
| return detail::format_string("filesystem error: %s", derived_what); |
| case 1: |
| - return detail::format_string("filesystem error: %s [" PS_FMT "]", derived_what, |
| - p1); |
| + return detail::format_string("filesystem error: %s [" PATH_CSTR_FMT "]", |
| + derived_what, path1().c_str()); |
| case 2: |
| - return detail::format_string("filesystem error: %s [" PS_FMT "] [" PS_FMT "]", |
| - derived_what, p1, p2); |
| + return detail::format_string("filesystem error: %s [" PATH_CSTR_FMT "] [" PATH_CSTR_FMT "]", |
| + derived_what, path1().c_str(), path2().c_str()); |
| } |
| + _LIBCPP_UNREACHABLE(); |
| }(); |
| } |
| |
| @@ -1456,11 +1449,11 @@ path __temp_directory_path(error_code* ec) { |
| error_code m_ec; |
| file_status st = detail::posix_stat(p, &m_ec); |
| if (!status_known(st)) |
| - return err.report(m_ec, "cannot access path \"" PS_FMT "\"", p); |
| + return err.report(m_ec, "cannot access path " PATH_CSTR_FMT, p.c_str()); |
| |
| if (!exists(st) || !is_directory(st)) |
| - return err.report(errc::not_a_directory, "path \"" PS_FMT "\" is not a directory", |
| - p); |
| + return err.report(errc::not_a_directory, |
| + "path " PATH_CSTR_FMT " is not a directory", p.c_str()); |
| |
| return p; |
| } |
| diff --git a/test/support/filesystem_test_helper.h b/test/support/filesystem_test_helper.h |
| index 92c017f610fe..9d4062a987eb 100644 |
| --- a/test/support/filesystem_test_helper.h |
| +++ b/test/support/filesystem_test_helper.h |
| @@ -653,9 +653,7 @@ struct ExceptionChecker { |
| additional_msg = opt_message + ": "; |
| } |
| auto transform_path = [](const fs::path& p) { |
| - if (p.native().empty()) |
| - return std::string("\"\""); |
| - return p.string(); |
| + return "\"" + p.string() + "\""; |
| }; |
| std::string format = [&]() -> std::string { |
| switch (num_paths) { |
| -- |
| 2.31.1.windows.1 |
| |