diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst index c8a07fb8b7334..ecfbaa5b7a375 100644 --- a/libcxx/docs/ReleaseNotes/20.rst +++ b/libcxx/docs/ReleaseNotes/20.rst @@ -73,6 +73,39 @@ Improvements and New Features optimized, resulting in a performance improvement of up to 2x for trivial element types (e.g., `std::vector`), and up to 3.4x for non-trivial element types (e.g., `std::vector>`). +- On Windows, ````'s ``std::system_category`` is now distinct from ``std::generic_category``. The behavior + on other operating systems is unchanged. + + On Windows -- unlike on Unix systems -- the libc and system APIs use distinct error codes. The libc functions return + ``errno.h`` error codes via the ``errno`` global, while Win32 API functions return ``winerror.h`` error codes via + ``GetLastError()``. + + The C++ standard's ``std::error_code`` and ``std::error_category`` functionality was designed to support multiple + error domains, precisely in order to handle situations such as this. However, libc++ formerly treated + ``generic_category()`` and ``system_category()`` as equivalent, even on Windows. It now implements the intended split, + where ``system_category`` represents native ``winerror.h`` error codes, and ``generic_category`` represents libc error + codes (and, equivalently, ``std::errc::*`` errors). + + This change enables code like ``std::error_code(GetLastError(), std::system_category()) == + std::errc::invalid_argument`` to function as desired: constructing an ``error_code`` with the Windows error number in + the "system" category, and then mapping it to a generic code with ``error_condition``, for comparison with the + ``std::errc`` constant. + + This is an incompatible change: ``std::error_code(ENOSYS, std::system_category()) == + std::errc::function_not_supported`` would formerly have returned true, but now returns false on Windows. Code + providing a number from the ``errno.h`` domain should be migrated to construct a ``generic_category`` error_code, + instead. (E.g., use ``std::error_code(ENOSYS, std::generic_category())``). The new behavior matches MSVC. + +- On Windows, the ``std::filesystem`` library now returns the Win32 ``system_category`` error codes, where it's feasible + to do so. This allows interrogation and reporting of the original error code, which is useful if multiple Windows + errors map to a single generic error (such as with ``std::errc::no_such_file_or_directory``). + + This is also a slightly-incompatible API change: code inspecting the raw integer value from the returned error_code + expecting an integer from ``generic_category`` (e.g. ``err.value() == ENOTDIR``) will not work as desired. Instead, + such code should use the comparison operators which implicitly handle eror mappings, ``err == + std::errc::not_a_directory``, or use ``err.default_error_condition()`` to map to an ``error_condition``, and then test + its ``value()`` and ``category()``. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/__filesystem/directory_entry.h b/libcxx/include/__filesystem/directory_entry.h index 7d0c01b98def6..11e07acdbe00c 100644 --- a/libcxx/include/__filesystem/directory_entry.h +++ b/libcxx/include/__filesystem/directory_entry.h @@ -22,7 +22,9 @@ #include <__filesystem/perms.h> #include <__fwd/ostream.h> #include <__system_error/errc.h> +#include <__system_error/error_category.h> #include <__system_error/error_code.h> +#include <__system_error/error_condition.h> #include <__utility/move.h> #include <__utility/unreachable.h> #include @@ -274,15 +276,7 @@ class directory_entry { _LIBCPP_EXPORTED_FROM_ABI error_code __do_refresh() noexcept; _LIBCPP_HIDE_FROM_ABI static bool __is_dne_error(error_code const& __ec) { - if (!__ec) - return true; - switch (static_cast(__ec.value())) { - case errc::no_such_file_or_directory: - case errc::not_a_directory: - return true; - default: - return false; - } + return !__ec || __ec == errc::no_such_file_or_directory || __ec == errc::not_a_directory; } _LIBCPP_HIDE_FROM_ABI void diff --git a/libcxx/include/__system_error/system_error.h b/libcxx/include/__system_error/system_error.h index 918effb6917cb..36ccf94cc010d 100644 --- a/libcxx/include/__system_error/system_error.h +++ b/libcxx/include/__system_error/system_error.h @@ -39,6 +39,10 @@ class _LIBCPP_EXPORTED_FROM_ABI system_error : public runtime_error { _LIBCPP_HIDE_FROM_ABI const error_code& code() const _NOEXCEPT { return __ec_; } }; +// __ev is expected to be an error in the generic_category domain (e.g. from +// errno, or std::errc::*), not system_category (e.g. from windows syscalls). +[[__noreturn__]] _LIBCPP_EXPORTED_FROM_ABI void __throw_system_error(int __ev, const char* __what_arg); + [[__noreturn__]] _LIBCPP_HIDE_FROM_ABI inline void __throw_system_error(error_code __ec, const char* __what_arg) { #if _LIBCPP_HAS_EXCEPTIONS throw system_error(__ec, __what_arg); diff --git a/libcxx/src/filesystem/directory_iterator.cpp b/libcxx/src/filesystem/directory_iterator.cpp index d7ed9a358f559..7e8e40d17f7a0 100644 --- a/libcxx/src/filesystem/directory_iterator.cpp +++ b/libcxx/src/filesystem/directory_iterator.cpp @@ -47,9 +47,9 @@ class __dir_stream { } __stream_ = ::FindFirstFileW((root / "*").c_str(), &__data_); if (__stream_ == INVALID_HANDLE_VALUE) { - ec = detail::make_windows_error(GetLastError()); + ec = detail::get_last_error(); const bool ignore_permission_denied = bool(opts & directory_options::skip_permission_denied); - if (ignore_permission_denied && ec.value() == static_cast(errc::permission_denied)) + if (ignore_permission_denied && ec == errc::permission_denied) ec.clear(); return; } @@ -91,7 +91,7 @@ class __dir_stream { error_code close() noexcept { error_code ec; if (!::FindClose(__stream_)) - ec = detail::make_windows_error(GetLastError()); + ec = detail::get_last_error(); __stream_ = INVALID_HANDLE_VALUE; return ec; } @@ -118,7 +118,7 @@ class __dir_stream { if ((__stream_ = ::opendir(root.c_str())) == nullptr) { ec = detail::capture_errno(); const bool allow_eacces = bool(opts & directory_options::skip_permission_denied); - if (allow_eacces && ec.value() == EACCES) + if (allow_eacces && ec == errc::permission_denied) ec.clear(); return; } @@ -307,7 +307,7 @@ bool recursive_directory_iterator::__try_recursion(error_code* ec) { } if (m_ec) { const bool allow_eacess = bool(__imp_->__options_ & directory_options::skip_permission_denied); - if (m_ec.value() == EACCES && allow_eacess) { + if (m_ec == errc::permission_denied && allow_eacess) { if (ec) ec->clear(); } else { diff --git a/libcxx/src/filesystem/error.h b/libcxx/src/filesystem/error.h index 07ba7fc3eef25..c0213910b3780 100644 --- a/libcxx/src/filesystem/error.h +++ b/libcxx/src/filesystem/error.h @@ -32,80 +32,21 @@ _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM namespace detail { -#if defined(_LIBCPP_WIN32API) - -inline errc __win_err_to_errc(int err) { - constexpr struct { - DWORD win; - errc errc; - } win_error_mapping[] = { - {ERROR_ACCESS_DENIED, errc::permission_denied}, - {ERROR_ALREADY_EXISTS, errc::file_exists}, - {ERROR_BAD_NETPATH, errc::no_such_file_or_directory}, - {ERROR_BAD_PATHNAME, errc::no_such_file_or_directory}, - {ERROR_BAD_UNIT, errc::no_such_device}, - {ERROR_BROKEN_PIPE, errc::broken_pipe}, - {ERROR_BUFFER_OVERFLOW, errc::filename_too_long}, - {ERROR_BUSY, errc::device_or_resource_busy}, - {ERROR_BUSY_DRIVE, errc::device_or_resource_busy}, - {ERROR_CANNOT_MAKE, errc::permission_denied}, - {ERROR_CANTOPEN, errc::io_error}, - {ERROR_CANTREAD, errc::io_error}, - {ERROR_CANTWRITE, errc::io_error}, - {ERROR_CURRENT_DIRECTORY, errc::permission_denied}, - {ERROR_DEV_NOT_EXIST, errc::no_such_device}, - {ERROR_DEVICE_IN_USE, errc::device_or_resource_busy}, - {ERROR_DIR_NOT_EMPTY, errc::directory_not_empty}, - {ERROR_DIRECTORY, errc::invalid_argument}, - {ERROR_DISK_FULL, errc::no_space_on_device}, - {ERROR_FILE_EXISTS, errc::file_exists}, - {ERROR_FILE_NOT_FOUND, errc::no_such_file_or_directory}, - {ERROR_HANDLE_DISK_FULL, errc::no_space_on_device}, - {ERROR_INVALID_ACCESS, errc::permission_denied}, - {ERROR_INVALID_DRIVE, errc::no_such_device}, - {ERROR_INVALID_FUNCTION, errc::function_not_supported}, - {ERROR_INVALID_HANDLE, errc::invalid_argument}, - {ERROR_INVALID_NAME, errc::no_such_file_or_directory}, - {ERROR_INVALID_PARAMETER, errc::invalid_argument}, - {ERROR_LOCK_VIOLATION, errc::no_lock_available}, - {ERROR_LOCKED, errc::no_lock_available}, - {ERROR_NEGATIVE_SEEK, errc::invalid_argument}, - {ERROR_NOACCESS, errc::permission_denied}, - {ERROR_NOT_ENOUGH_MEMORY, errc::not_enough_memory}, - {ERROR_NOT_READY, errc::resource_unavailable_try_again}, - {ERROR_NOT_SAME_DEVICE, errc::cross_device_link}, - {ERROR_NOT_SUPPORTED, errc::not_supported}, - {ERROR_OPEN_FAILED, errc::io_error}, - {ERROR_OPEN_FILES, errc::device_or_resource_busy}, - {ERROR_OPERATION_ABORTED, errc::operation_canceled}, - {ERROR_OUTOFMEMORY, errc::not_enough_memory}, - {ERROR_PATH_NOT_FOUND, errc::no_such_file_or_directory}, - {ERROR_READ_FAULT, errc::io_error}, - {ERROR_REPARSE_TAG_INVALID, errc::invalid_argument}, - {ERROR_RETRY, errc::resource_unavailable_try_again}, - {ERROR_SEEK, errc::io_error}, - {ERROR_SHARING_VIOLATION, errc::permission_denied}, - {ERROR_TOO_MANY_OPEN_FILES, errc::too_many_files_open}, - {ERROR_WRITE_FAULT, errc::io_error}, - {ERROR_WRITE_PROTECT, errc::permission_denied}, - }; - - for (const auto& pair : win_error_mapping) - if (pair.win == static_cast(err)) - return pair.errc; - return errc::invalid_argument; -} - -#endif // _LIBCPP_WIN32API +// On windows, libc functions use errno, but system functions use GetLastError. +// So, callers need to be careful which of these next functions they call! inline error_code capture_errno() { _LIBCPP_ASSERT_INTERNAL(errno != 0, "Expected errno to be non-zero"); return error_code(errno, generic_category()); } +inline error_code get_last_error() { #if defined(_LIBCPP_WIN32API) -inline error_code make_windows_error(int err) { return make_error_code(__win_err_to_errc(err)); } + return std::error_code(GetLastError(), std::system_category()); +#else + return capture_errno(); #endif +} template T error_value(); diff --git a/libcxx/src/filesystem/file_descriptor.h b/libcxx/src/filesystem/file_descriptor.h index db66ad55bd4fb..9c279c451f28c 100644 --- a/libcxx/src/filesystem/file_descriptor.h +++ b/libcxx/src/filesystem/file_descriptor.h @@ -201,7 +201,7 @@ inline perms posix_get_perms(const StatT& st) noexcept { return static_cast err("posix_stat", ec, &p); @@ -236,7 +236,7 @@ inline file_status create_file_status(error_code& m_ec, path const& p, const Sta inline file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) { error_code m_ec; if (detail::stat(p.c_str(), &path_stat) == -1) - m_ec = detail::capture_errno(); + m_ec = detail::get_last_error(); return create_file_status(m_ec, p, path_stat, ec); } @@ -248,7 +248,7 @@ inline file_status posix_stat(path const& p, error_code* ec) { inline file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) { error_code m_ec; if (detail::lstat(p.c_str(), &path_stat) == -1) - m_ec = detail::capture_errno(); + m_ec = detail::get_last_error(); return create_file_status(m_ec, p, path_stat, ec); } @@ -260,7 +260,7 @@ inline file_status posix_lstat(path const& p, error_code* ec) { // http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html inline bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) { if (detail::ftruncate(fd.fd, to_size) == -1) { - ec = capture_errno(); + ec = get_last_error(); return true; } ec.clear(); @@ -269,7 +269,7 @@ inline bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& inline bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) { if (detail::fchmod(fd.fd, st.st_mode) == -1) { - ec = capture_errno(); + ec = get_last_error(); return true; } ec.clear(); @@ -286,7 +286,7 @@ inline file_status FileDescriptor::refresh_status(error_code& ec) { m_stat = {}; error_code m_ec; if (detail::fstat(fd, &m_stat) == -1) - m_ec = capture_errno(); + m_ec = get_last_error(); m_status = create_file_status(m_ec, name, m_stat, &ec); return m_status; } diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp index 208a55723d883..d9f130eb10c0c 100644 --- a/libcxx/src/filesystem/operations.cpp +++ b/libcxx/src/filesystem/operations.cpp @@ -100,7 +100,7 @@ path __canonical(path const& orig_p, error_code* ec) { #if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || defined(_LIBCPP_WIN32API) std::unique_ptr hold(detail::realpath(p.c_str(), nullptr), &::free); if (hold.get() == nullptr) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); return {hold.get()}; #else # if defined(__MVS__) && !defined(PATH_MAX) @@ -110,7 +110,7 @@ path __canonical(path const& orig_p, error_code* ec) { # endif path::value_type* ret; if ((ret = detail::realpath(p.c_str(), buff)) == nullptr) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); return {ret}; #endif } @@ -505,9 +505,9 @@ bool __create_directory(const path& p, error_code* ec) { if (detail::mkdir(p.c_str(), static_cast(perms::all)) == 0) return true; - if (errno != EEXIST) - return err.report(capture_errno()); - error_code mec = capture_errno(); + error_code mec = detail::get_last_error(); + if (mec != errc::file_exists) + return err.report(mec); error_code ignored_ec; const file_status st = status(p, ignored_ec); if (!is_directory(st)) @@ -529,10 +529,10 @@ bool __create_directory(path const& p, path const& attributes, error_code* ec) { if (detail::mkdir(p.c_str(), attr_stat.st_mode) == 0) return true; - if (errno != EEXIST) - return err.report(capture_errno()); + mec = detail::get_last_error(); + if (mec != errc::file_exists) + return err.report(mec); - mec = capture_errno(); error_code ignored_ec; st = status(p, ignored_ec); if (!is_directory(st)) @@ -543,19 +543,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 err("create_directory_symlink", ec, &from, &to); if (detail::symlink_dir(from.c_str(), to.c_str()) == -1) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } void __create_hard_link(const path& from, const path& to, error_code* ec) { ErrorHandler err("create_hard_link", ec, &from, &to); if (detail::link(from.c_str(), to.c_str()) == -1) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } void __create_symlink(path const& from, path const& to, error_code* ec) { ErrorHandler err("create_symlink", ec, &from, &to); if (detail::symlink_file(from.c_str(), to.c_str()) == -1) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } path __current_path(error_code* ec) { @@ -598,7 +598,7 @@ path __current_path(error_code* ec) { unique_ptr hold(detail::getcwd(ptr, size), deleter); if (hold.get() == nullptr) - return err.report(capture_errno(), "call to getcwd failed"); + return err.report(detail::get_last_error(), "call to getcwd failed"); return {hold.get()}; } @@ -606,7 +606,7 @@ path __current_path(error_code* ec) { void __current_path(const path& p, error_code* ec) { ErrorHandler err("current_path", ec, &p); if (detail::chdir(p.c_str()) == -1) - err.report(capture_errno()); + err.report(detail::get_last_error()); } bool __equivalent(const path& p1, const path& p2, error_code* ec) { @@ -694,10 +694,10 @@ void __last_write_time(const path& p, file_time_type new_time, error_code* ec) { 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())); + return err.report(detail::get_last_error()); FILETIME last_write = timespec_to_filetime(ts); if (!SetFileTime(h, nullptr, nullptr, &last_write)) - return err.report(detail::make_windows_error(GetLastError())); + return err.report(detail::get_last_error()); #else error_code m_ec; array tbuf; @@ -755,7 +755,7 @@ void __permissions(const path& p, perms prms, perm_options opts, error_code* ec) #if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0; if (detail::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } #else if (set_sym_perms) @@ -783,14 +783,14 @@ path __read_symlink(const path& p, error_code* ec) { #else StatT sb; if (detail::lstat(p.c_str(), &sb) == -1) { - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } const size_t size = sb.st_size + 1; auto buff = unique_ptr(new path::value_type[size]); #endif detail::SSizeT ret; if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); // Note that `ret` returning `0` would work, resulting in a valid empty string being returned. if (static_cast(ret) >= size) return err.report(errc::value_too_large); @@ -801,8 +801,9 @@ path __read_symlink(const path& p, error_code* ec) { bool __remove(const path& p, error_code* ec) { ErrorHandler err("remove", ec, &p); if (detail::remove(p.c_str()) == -1) { - if (errno != ENOENT) - err.report(capture_errno()); + error_code mec = detail::get_last_error(); + if (mec != errc::no_such_file_or_directory) + err.report(mec); return false; } return true; @@ -955,13 +956,13 @@ uintmax_t __remove_all(const path& p, error_code* ec) { void __rename(const path& from, const path& to, error_code* ec) { ErrorHandler err("rename", ec, &from, &to); if (detail::rename(from.c_str(), to.c_str()) == -1) - err.report(capture_errno()); + err.report(detail::get_last_error()); } void __resize_file(const path& p, uintmax_t size, error_code* ec) { ErrorHandler err("resize_file", ec, &p); if (detail::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1) - return err.report(capture_errno()); + return err.report(detail::get_last_error()); } space_info __space(const path& p, error_code* ec) { @@ -969,7 +970,7 @@ space_info __space(const path& p, error_code* ec) { space_info si; detail::StatVFS m_svfs = {}; if (detail::statvfs(p.c_str(), &m_svfs) == -1) { - err.report(capture_errno()); + err.report(detail::get_last_error()); si.capacity = si.free = si.available = static_cast(-1); return si; } @@ -996,7 +997,7 @@ path __temp_directory_path(error_code* ec) { wchar_t buf[MAX_PATH]; DWORD retval = GetTempPathW(MAX_PATH, buf); if (!retval) - return err.report(detail::make_windows_error(GetLastError())); + return err.report(detail::get_last_error()); if (retval > MAX_PATH) return err.report(errc::filename_too_long); // GetTempPathW returns a path with a trailing slash, which we diff --git a/libcxx/src/filesystem/posix_compat.h b/libcxx/src/filesystem/posix_compat.h index b41c004341af3..ddd99d8aaf206 100644 --- a/libcxx/src/filesystem/posix_compat.h +++ b/libcxx/src/filesystem/posix_compat.h @@ -11,9 +11,10 @@ // // 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(). +// - 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(). +// - Errors should be retrieved with get_last_error, not errno. // // These are provided within an anonymous namespace within the detail // namespace - callers need to include this header and call them as @@ -122,11 +123,6 @@ namespace detail { # define O_NONBLOCK 0 -inline int set_errno(int e = GetLastError()) { - errno = static_cast(__win_err_to_errc(e)); - return -1; -} - class WinHandle { public: WinHandle(const wchar_t* p, DWORD access, DWORD flags) { @@ -153,7 +149,7 @@ class WinHandle { inline int stat_handle(HANDLE h, StatT* buf) { FILE_BASIC_INFO basic; if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) - return set_errno(); + return -1; memset(buf, 0, sizeof(*buf)); buf->st_mtim = filetime_to_timespec(basic.LastWriteTime); buf->st_atim = filetime_to_timespec(basic.LastAccessTime); @@ -168,18 +164,18 @@ inline int stat_handle(HANDLE h, StatT* buf) { if (basic.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { FILE_ATTRIBUTE_TAG_INFO tag; if (!GetFileInformationByHandleEx(h, FileAttributeTagInfo, &tag, sizeof(tag))) - return set_errno(); + return -1; 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(); + return -1; buf->st_nlink = standard.NumberOfLinks; buf->st_size = standard.EndOfFile.QuadPart; BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(h, &info)) - return set_errno(); + return -1; buf->st_dev = info.dwVolumeSerialNumber; memcpy(&buf->st_ino.id[0], &info.nFileIndexHigh, 4); memcpy(&buf->st_ino.id[4], &info.nFileIndexLow, 4); @@ -189,7 +185,7 @@ inline int stat_handle(HANDLE h, StatT* buf) { inline int stat_file(const wchar_t* path, StatT* buf, DWORD flags) { WinHandle h(path, FILE_READ_ATTRIBUTES, flags); if (!h) - return set_errno(); + return -1; int ret = stat_handle(h, buf); return ret; } @@ -206,7 +202,7 @@ inline int fstat(int fd, StatT* buf) { inline int mkdir(const wchar_t* path, int permissions) { (void)permissions; if (!CreateDirectoryW(path, nullptr)) - return set_errno(); + return -1; return 0; } @@ -219,10 +215,10 @@ inline int symlink_file_dir(const wchar_t* oldname, const wchar_t* newname, bool return 0; int e = GetLastError(); if (e != ERROR_INVALID_PARAMETER) - return set_errno(e); + return -1; if (CreateSymbolicLinkW(newname, oldname, flags)) return 0; - return set_errno(); + return -1; } inline int symlink_file(const wchar_t* oldname, const wchar_t* newname) { @@ -236,17 +232,17 @@ inline int symlink_dir(const wchar_t* oldname, const wchar_t* newname) { inline int link(const wchar_t* oldname, const wchar_t* newname) { if (CreateHardLinkW(newname, oldname, nullptr)) return 0; - return set_errno(); + return -1; } inline int remove(const wchar_t* path) { detail::WinHandle h(path, DELETE, FILE_FLAG_OPEN_REPARSE_POINT); if (!h) - return set_errno(); + return -1; FILE_DISPOSITION_INFO info; info.DeleteFile = TRUE; if (!SetFileInformationByHandle(h, FileDispositionInfo, &info, sizeof(info))) - return set_errno(); + return -1; return 0; } @@ -254,9 +250,9 @@ inline int truncate_handle(HANDLE h, off_t length) { LARGE_INTEGER size_param; size_param.QuadPart = length; if (!SetFilePointerEx(h, size_param, 0, FILE_BEGIN)) - return set_errno(); + return -1; if (!SetEndOfFile(h)) - return set_errno(); + return -1; return 0; } @@ -268,19 +264,19 @@ inline int ftruncate(int fd, off_t length) { inline int truncate(const wchar_t* path, off_t length) { detail::WinHandle h(path, GENERIC_WRITE, 0); if (!h) - return set_errno(); + return -1; return truncate_handle(h, length); } inline int rename(const wchar_t* from, const wchar_t* to) { if (!(MoveFileExW(from, to, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))) - return set_errno(); + return -1; return 0; } inline int chdir(const wchar_t* path) { if (!SetCurrentDirectoryW(path)) - return set_errno(); + return -1; return 0; } @@ -300,7 +296,7 @@ inline int statvfs(const wchar_t* p, StatVFS* buf) { break; path parent = dir.parent_path(); if (parent == dir) { - errno = ENOENT; + SetLastError(ERROR_PATH_NOT_FOUND); return -1; } dir = parent; @@ -308,7 +304,7 @@ inline int statvfs(const wchar_t* p, StatVFS* buf) { 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(); + return -1; buf->f_frsize = 1; buf->f_blocks = total_number_of_bytes.QuadPart; buf->f_bfree = total_number_of_free_bytes.QuadPart; @@ -330,7 +326,6 @@ inline wchar_t* getcwd([[maybe_unused]] wchar_t* in_buf, [[maybe_unused]] size_t retval = GetCurrentDirectoryW(buff_size, buff.get()); } if (!retval) { - set_errno(); return nullptr; } return buff.release(); @@ -342,7 +337,6 @@ inline wchar_t* realpath(const wchar_t* path, [[maybe_unused]] wchar_t* resolved WinHandle h(path, FILE_READ_ATTRIBUTES, 0); if (!h) { - set_errno(); return nullptr; } size_t buff_size = MAX_PATH + 10; @@ -354,7 +348,6 @@ inline wchar_t* realpath(const wchar_t* path, [[maybe_unused]] wchar_t* resolved retval = GetFinalPathNameByHandleW(h, buff.get(), buff_size, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); } if (!retval) { - set_errno(); return nullptr; } wchar_t* ptr = buff.get(); @@ -376,20 +369,20 @@ using ModeT = int; inline int fchmod_handle(HANDLE h, int perms) { FILE_BASIC_INFO basic; if (!GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) - return set_errno(); + return -1; 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 -1; return 0; } inline int fchmodat(int /*fd*/, const wchar_t* path, int perms, int flag) { DWORD attributes = GetFileAttributesW(path); if (attributes == INVALID_FILE_ATTRIBUTES) - return set_errno(); + return -1; 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 @@ -397,7 +390,7 @@ inline int fchmodat(int /*fd*/, const wchar_t* path, int perms, int flag) { // 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 -1; return fchmod_handle(h, perms); } else { // For a non-symlink, or if operating on the symlink itself instead of @@ -407,7 +400,7 @@ inline int fchmodat(int /*fd*/, const wchar_t* path, int perms, int flag) { if ((perms & 0222) == 0) attributes |= FILE_ATTRIBUTE_READONLY; if (attributes != orig_attributes && !SetFileAttributesW(path, attributes)) - return set_errno(); + return -1; } return 0; } @@ -424,18 +417,18 @@ inline SSizeT readlink(const wchar_t* path, wchar_t* ret_buf, size_t bufsize) { uint8_t buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; detail::WinHandle h(path, FILE_READ_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT); if (!h) - return set_errno(); + return -1; DWORD out; if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf, sizeof(buf), &out, 0)) - return set_errno(); + return -1; const auto* reparse = reinterpret_cast(buf); size_t path_buf_offset = offsetof(LIBCPP_REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[0]); if (out < path_buf_offset) { - errno = EINVAL; + SetLastError(ERROR_REPARSE_TAG_INVALID); return -1; } if (reparse->ReparseTag != IO_REPARSE_TAG_SYMLINK) { - errno = EINVAL; + SetLastError(ERROR_REPARSE_TAG_INVALID); return -1; } const auto& symlink = reparse->SymbolicLinkReparseBuffer; @@ -449,11 +442,11 @@ inline SSizeT readlink(const wchar_t* path, wchar_t* ret_buf, size_t bufsize) { } // name_offset/length are expressed in bytes, not in wchar_t if (path_buf_offset + name_offset + name_length > out) { - errno = EINVAL; + SetLastError(ERROR_REPARSE_TAG_INVALID); return -1; } if (name_length / sizeof(wchar_t) > bufsize) { - errno = ENOMEM; + SetLastError(ERROR_NOT_ENOUGH_MEMORY); return -1; } memcpy(ret_buf, &symlink.PathBuffer[name_offset / sizeof(wchar_t)], name_length); diff --git a/libcxx/src/print.cpp b/libcxx/src/print.cpp index 37b1fc00cd7c3..4937aafe84177 100644 --- a/libcxx/src/print.cpp +++ b/libcxx/src/print.cpp @@ -51,7 +51,7 @@ __write_to_windows_console([[maybe_unused]] FILE* __stream, [[maybe_unused]] wst __view.size(), nullptr, nullptr) == 0) { - __throw_system_error(filesystem::detail::make_windows_error(GetLastError()), "failed to write formatted output"); + __throw_system_error(filesystem::detail::get_last_error(), "failed to write formatted output"); } } # endif // _LIBCPP_HAS_WIDE_CHARACTERS diff --git a/libcxx/src/system_error.cpp b/libcxx/src/system_error.cpp index d555bca995c45..d5ec73084f638 100644 --- a/libcxx/src/system_error.cpp +++ b/libcxx/src/system_error.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,8 +25,123 @@ # include #endif +#if defined(_LIBCPP_WIN32API) +# include +# include +#endif + _LIBCPP_BEGIN_NAMESPACE_STD +#if defined(_LIBCPP_WIN32API) + +namespace { +std::optional __win_err_to_errc(int err) { + switch (err) { + case ERROR_ACCESS_DENIED: + return errc::permission_denied; + case ERROR_ALREADY_EXISTS: + return errc::file_exists; + case ERROR_BAD_NETPATH: + return errc::no_such_file_or_directory; + case ERROR_BAD_PATHNAME: + return errc::no_such_file_or_directory; + case ERROR_BAD_UNIT: + return errc::no_such_device; + case ERROR_BROKEN_PIPE: + return errc::broken_pipe; + case ERROR_BUFFER_OVERFLOW: + return errc::filename_too_long; + case ERROR_BUSY: + return errc::device_or_resource_busy; + case ERROR_BUSY_DRIVE: + return errc::device_or_resource_busy; + case ERROR_CANNOT_MAKE: + return errc::permission_denied; + case ERROR_CANTOPEN: + return errc::io_error; + case ERROR_CANTREAD: + return errc::io_error; + case ERROR_CANTWRITE: + return errc::io_error; + case ERROR_CURRENT_DIRECTORY: + return errc::permission_denied; + case ERROR_DEV_NOT_EXIST: + return errc::no_such_device; + case ERROR_DEVICE_IN_USE: + return errc::device_or_resource_busy; + case ERROR_DIR_NOT_EMPTY: + return errc::directory_not_empty; + case ERROR_DIRECTORY: + return errc::invalid_argument; + case ERROR_DISK_FULL: + return errc::no_space_on_device; + case ERROR_FILE_EXISTS: + return errc::file_exists; + case ERROR_FILE_NOT_FOUND: + return errc::no_such_file_or_directory; + case ERROR_HANDLE_DISK_FULL: + return errc::no_space_on_device; + case ERROR_INVALID_ACCESS: + return errc::permission_denied; + case ERROR_INVALID_DRIVE: + return errc::no_such_device; + case ERROR_INVALID_FUNCTION: + return errc::function_not_supported; + case ERROR_INVALID_HANDLE: + return errc::invalid_argument; + case ERROR_INVALID_NAME: + return errc::no_such_file_or_directory; + case ERROR_INVALID_PARAMETER: + return errc::invalid_argument; + case ERROR_LOCK_VIOLATION: + return errc::no_lock_available; + case ERROR_LOCKED: + return errc::no_lock_available; + case ERROR_NEGATIVE_SEEK: + return errc::invalid_argument; + case ERROR_NOACCESS: + return errc::permission_denied; + case ERROR_NOT_ENOUGH_MEMORY: + return errc::not_enough_memory; + case ERROR_NOT_READY: + return errc::resource_unavailable_try_again; + case ERROR_NOT_SAME_DEVICE: + return errc::cross_device_link; + case ERROR_NOT_SUPPORTED: + return errc::not_supported; + case ERROR_OPEN_FAILED: + return errc::io_error; + case ERROR_OPEN_FILES: + return errc::device_or_resource_busy; + case ERROR_OPERATION_ABORTED: + return errc::operation_canceled; + case ERROR_OUTOFMEMORY: + return errc::not_enough_memory; + case ERROR_PATH_NOT_FOUND: + return errc::no_such_file_or_directory; + case ERROR_READ_FAULT: + return errc::io_error; + case ERROR_REPARSE_TAG_INVALID: + return errc::invalid_argument; + case ERROR_RETRY: + return errc::resource_unavailable_try_again; + case ERROR_SEEK: + return errc::io_error; + case ERROR_SHARING_VIOLATION: + return errc::permission_denied; + case ERROR_TOO_MANY_OPEN_FILES: + return errc::too_many_files_open; + case ERROR_WRITE_FAULT: + return errc::io_error; + case ERROR_WRITE_PROTECT: + return errc::permission_denied; + default: + return {}; + } +} +} // namespace +#endif + namespace { #if _LIBCPP_HAS_THREADS @@ -157,19 +273,52 @@ class _LIBCPP_HIDDEN __system_error_category : public __do_message { const char* __system_error_category::name() const noexcept { return "system"; } string __system_error_category::message(int ev) const { -#ifdef _LIBCPP_ELAST +#ifdef _LIBCPP_WIN32API + std::string result; + char* str = nullptr; + unsigned long num_chars = ::FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + ev, + 0, + reinterpret_cast(&str), + 0, + nullptr); + auto is_whitespace = [](char ch) { return ch == '\n' || ch == '\r' || ch == ' '; }; + while (num_chars > 0 && is_whitespace(str[num_chars - 1])) + --num_chars; + + if (num_chars) + result = std::string(str, num_chars); + else + result = "Unknown error"; + + LocalFree(str); + return result; +#else +# ifdef _LIBCPP_ELAST if (ev > _LIBCPP_ELAST) return string("unspecified system_category error"); -#endif // _LIBCPP_ELAST +# endif // _LIBCPP_ELAST return __do_message::message(ev); +#endif } error_condition __system_error_category::default_error_condition(int ev) const noexcept { -#ifdef _LIBCPP_ELAST +#ifdef _LIBCPP_WIN32API + // Remap windows error codes to generic error codes if possible. + if (ev == 0) + return error_condition(0, generic_category()); + if (auto maybe_errc = __win_err_to_errc(ev)) + return error_condition(static_cast(*maybe_errc), generic_category()); + return error_condition(ev, system_category()); +#else +# ifdef _LIBCPP_ELAST if (ev > _LIBCPP_ELAST) return error_condition(ev, system_category()); -#endif // _LIBCPP_ELAST +# endif // _LIBCPP_ELAST return error_condition(ev, generic_category()); +#endif } const error_category& system_category() noexcept { @@ -213,7 +362,7 @@ system_error::~system_error() noexcept {} void __throw_system_error(int ev, const char* what_arg) { #if _LIBCPP_HAS_EXCEPTIONS - std::__throw_system_error(error_code(ev, system_category()), what_arg); + std::__throw_system_error(error_code(ev, generic_category()), what_arg); #else // The above could also handle the no-exception case, but for size, avoid referencing system_category() unnecessarily. _LIBCPP_VERBOSE_ABORT( diff --git a/libcxx/test/libcxx/diagnostics/system_error_win_codes.pass.cpp b/libcxx/test/libcxx/diagnostics/system_error_win_codes.pass.cpp new file mode 100644 index 0000000000000..799a5b5c0b086 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/system_error_win_codes.pass.cpp @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: windows + +// Validate that system_error on windows accepts Windows' System Error Codes (as +// used by win32 APIs and reported by GetLastError), and that they are properly +// translated to generic conditions. + +#include +#include +#include + +#include "test_macros.h" + +int main(int, char**) { + LIBCPP_ASSERT(std::error_code(ERROR_ACCESS_DENIED, std::system_category()) == std::errc::permission_denied); + LIBCPP_ASSERT(std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()) == std::errc::no_such_file_or_directory); + return 0; +} diff --git a/libcxx/test/std/diagnostics/syserr/syserr.compare/eq_error_code_error_code.pass.cpp b/libcxx/test/std/diagnostics/syserr/syserr.compare/eq_error_code_error_code.pass.cpp index f1f49733280b1..a8b565bb0ab94 100644 --- a/libcxx/test/std/diagnostics/syserr/syserr.compare/eq_error_code_error_code.pass.cpp +++ b/libcxx/test/std/diagnostics/syserr/syserr.compare/eq_error_code_error_code.pass.cpp @@ -22,6 +22,10 @@ #include "test_macros.h" +#ifndef _WIN32 +# define TEST_SYSTEM_CATEGORY_IS_GENERIC_CATEGORY +#endif + int main(int, char**) { std::error_code e_code1(5, std::generic_category()); std::error_code e_code2(5, std::system_category()); @@ -45,7 +49,9 @@ int main(int, char**) { assert(e_code2 == e_code2); assert(e_code2 != e_code3); assert(e_code2 != e_code4); +#ifdef TEST_SYSTEM_CATEGORY_IS_GENERIC_CATEGORY LIBCPP_ASSERT(e_code2 == e_condition1); +#endif assert(e_code2 == e_condition2); LIBCPP_ASSERT(e_code2 != e_condition3); assert(e_code2 != e_condition4); @@ -65,11 +71,15 @@ int main(int, char**) { assert(e_code4 == e_code4); LIBCPP_ASSERT(e_code4 != e_condition1); assert(e_code4 != e_condition2); +#ifdef TEST_SYSTEM_CATEGORY_IS_GENERIC_CATEGORY LIBCPP_ASSERT(e_code4 == e_condition3); +#endif assert(e_code4 == e_condition4); assert(e_condition1 == e_code1); +#ifdef TEST_SYSTEM_CATEGORY_IS_GENERIC_CATEGORY LIBCPP_ASSERT(e_condition1 == e_code2); +#endif assert(e_condition1 != e_code3); LIBCPP_ASSERT(e_condition1 != e_code4); assert(e_condition1 == e_condition1); @@ -89,7 +99,9 @@ int main(int, char**) { assert(e_condition3 != e_code1); LIBCPP_ASSERT(e_condition3 != e_code2); assert(e_condition3 == e_code3); +#ifdef TEST_SYSTEM_CATEGORY_IS_GENERIC_CATEGORY LIBCPP_ASSERT(e_condition3 == e_code4); +#endif assert(e_condition3 != e_condition1); assert(e_condition3 != e_condition2); assert(e_condition3 == e_condition3); diff --git a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.derived/message.pass.cpp b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.derived/message.pass.cpp index 9f7eb42bc78d9..f7f43132902f6 100644 --- a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.derived/message.pass.cpp +++ b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.derived/message.pass.cpp @@ -29,8 +29,11 @@ int main(int, char**) { assert(!m1.empty()); assert(!m2.empty()); assert(!m3.empty()); +#ifndef _WIN32 + // On windows, system_category is distinct. LIBCPP_ASSERT(m1 == m2); - assert(m1 != m3); +#endif + assert(m2 != m3); return 0; } diff --git a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp index 6ba33ba44ca4c..255cbe75e2fa9 100644 --- a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp +++ b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp @@ -33,7 +33,12 @@ int main(int, char**) { { const std::error_category& e_cat1 = std::system_category(); std::error_condition e_cond = e_cat1.default_error_condition(5); +#ifdef _WIN32 + // Windows' system error 5 is ERROR_ACCESS_DENIED, which maps to generic code permission_denied. + LIBCPP_ASSERT(e_cond.value() == static_cast(std::errc::permission_denied)); +#else LIBCPP_ASSERT(e_cond.value() == 5); +#endif LIBCPP_ASSERT(e_cond.category() == std::generic_category()); assert(e_cat1.equivalent(5, e_cond)); diff --git a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp index 303a95a0128bc..071ee7f6c891f 100644 --- a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp +++ b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp @@ -172,8 +172,13 @@ static void test_with_ec_dne() { file_status st = status(p, status_ec); file_status sym_st = symlink_status(p, sym_status_ec); std::error_code ec = GetTestEC(2); - auto CheckEC = [&](std::error_code const& other_ec) { - bool res = ec == other_ec; + auto CheckEC = [&](std::error_code const& other_ec) { + // Note: we're comparing equality of the _canonicalized_ error_condition + // here (unlike in other tests where we expect exactly the same + // error_code). This is because directory_entry can construct its own + // generic_category error when a file doesn't exist, instead of passing + // through an underlying system_category error. + bool res = ec.default_error_condition() == other_ec.default_error_condition(); ec = GetTestEC(2); return res; }; diff --git a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/status.pass.cpp b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/status.pass.cpp index dd72232ee530a..dec04df7ca019 100644 --- a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/status.pass.cpp +++ b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/status.pass.cpp @@ -44,7 +44,7 @@ static void test_basic() { file_status es = e.status(eec); assert(ps.type() == es.type()); assert(ps.permissions() == es.permissions()); - assert(pec == eec); + assert(pec.default_error_condition() == eec.default_error_condition()); } for (const auto& p : TestCases) { const directory_entry e(p); diff --git a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp index 24e8069509527..77da936382aa2 100644 --- a/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp +++ b/libcxx/test/std/input.output/filesystems/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp @@ -44,7 +44,7 @@ static void test_signature() { file_status es = e.symlink_status(eec); assert(ps.type() == es.type()); assert(ps.permissions() == es.permissions()); - assert(pec == eec); + assert(pec.default_error_condition() == eec.default_error_condition()); } for (const auto& p : TestCases) { const directory_entry e(p); diff --git a/libcxx/test/support/filesystem_test_helper.h b/libcxx/test/support/filesystem_test_helper.h index a63d645d1a01a..2ad9efb32c60f 100644 --- a/libcxx/test/support/filesystem_test_helper.h +++ b/libcxx/test/support/filesystem_test_helper.h @@ -583,7 +583,11 @@ struct ExceptionChecker { assert(ErrorIsImp(Err.code(), {expected_err})); assert(Err.path1() == expected_path1); assert(Err.path2() == expected_path2); +#ifndef _WIN32 + // On Windows, the error strings are windows error code strings, and don't + // match textually with the strings generated for generic std::errc::*. LIBCPP_ONLY(check_libcxx_string(Err)); +#endif } void check_libcxx_string(fs::filesystem_error const& Err) {