blob: da8222736021af7a992c759385db38c3abaec4c3 [file] [log] [blame]
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