[llvm] [Support] Handle delete_pending case for Windows fs::status (PR #90655)
Jeremy Day via llvm-commits
llvm-commits at lists.llvm.org
Thu May 9 12:29:05 PDT 2024
https://github.com/z2oh updated https://github.com/llvm/llvm-project/pull/90655
>From a4685d24b114ddccc0225171ed6c17946e08664b Mon Sep 17 00:00:00 2001
From: Jeremy Day <jeremy at thebrowser.company>
Date: Tue, 30 Apr 2024 12:53:40 -0700
Subject: [PATCH] [Support] Handle delete_pending case for Windows fs::status
If a delete is pending on the file queried for status, a misleading `permission_denied` error code will be returned (this is the correct mapping of the error set by GetFileAttributesW). By querying the underlying NTSTATUS code via ntdll's RtlGetLastNtStatus, this case can be disambiguated. This query is repeated a number of times to wait out the pending delete, and will return a new `pending_delete` error code if the query never succeeds. In most cases, however, the loop will complete after a few iterations and the excpected `no_such_file` error will be returned instead.
---
llvm/include/llvm/Support/Errc.h | 4 +++
llvm/lib/Support/CMakeLists.txt | 3 ++-
llvm/lib/Support/ErrorHandling.cpp | 42 +++++++++++++++++++++++-------
llvm/lib/Support/Windows/Path.inc | 29 ++++++++++++++++++---
4 files changed, 64 insertions(+), 14 deletions(-)
diff --git a/llvm/include/llvm/Support/Errc.h b/llvm/include/llvm/Support/Errc.h
index 9df522cbe45c7..fcb69d303109a 100644
--- a/llvm/include/llvm/Support/Errc.h
+++ b/llvm/include/llvm/Support/Errc.h
@@ -38,6 +38,10 @@ enum class errc {
bad_address = int(std::errc::bad_address),
bad_file_descriptor = int(std::errc::bad_file_descriptor),
broken_pipe = int(std::errc::broken_pipe),
+ // There is no delete_pending in std::errc; this error code is negative to
+ // avoid conflicts. This error roughly corresponds with Windows'
+ // STATUS_DELETE_PENDING 0xC0000056.
+ delete_pending = -56,
device_or_resource_busy = int(std::errc::device_or_resource_busy),
directory_not_empty = int(std::errc::directory_not_empty),
executable_format_error = int(std::errc::executable_format_error),
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 03e888958a071..36a208f0919d3 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -40,7 +40,8 @@ endif()
if( MSVC OR MINGW )
# libuuid required for FOLDERID_Profile usage in lib/Support/Windows/Path.inc.
# advapi32 required for CryptAcquireContextW in lib/Support/Windows/Path.inc.
- set(system_libs ${system_libs} psapi shell32 ole32 uuid advapi32 ws2_32)
+ # ntdll required for RtlGetLastNtStatus in lib/Support/Windows/Path.inc.
+ set(system_libs ${system_libs} psapi shell32 ole32 uuid advapi32 ws2_32 ntdll)
elseif( CMAKE_HOST_UNIX )
if( HAVE_LIBRT )
set(system_libs ${system_libs} rt)
diff --git a/llvm/lib/Support/ErrorHandling.cpp b/llvm/lib/Support/ErrorHandling.cpp
index d2d3dcc2f478c..8f865527ecd77 100644
--- a/llvm/lib/Support/ErrorHandling.cpp
+++ b/llvm/lib/Support/ErrorHandling.cpp
@@ -30,11 +30,11 @@
#include <new>
#if defined(HAVE_UNISTD_H)
-# include <unistd.h>
+#include <unistd.h>
#endif
#if defined(_MSC_VER)
-# include <io.h>
-# include <fcntl.h>
+#include <fcntl.h>
+#include <io.h>
#endif
using namespace llvm;
@@ -89,7 +89,7 @@ void llvm::report_fatal_error(StringRef Reason, bool GenCrashDiag) {
void llvm::report_fatal_error(const Twine &Reason, bool GenCrashDiag) {
llvm::fatal_error_handler_t handler = nullptr;
- void* handlerData = nullptr;
+ void *handlerData = nullptr;
{
// Only acquire the mutex while reading the handler, so as not to invoke a
// user-supplied callback under a lock.
@@ -180,8 +180,7 @@ void llvm::report_bad_alloc_error(const char *Reason, bool GenCrashDiag) {
#ifdef LLVM_ENABLE_EXCEPTIONS
// Do not set custom new handler if exceptions are enabled. In this case OOM
// errors are handled by throwing 'std::bad_alloc'.
-void llvm::install_out_of_memory_new_handler() {
-}
+void llvm::install_out_of_memory_new_handler() {}
#else
// Causes crash on allocation failure. It is called prior to the handler set by
// 'install_bad_alloc_error_handler'.
@@ -230,20 +229,45 @@ void LLVMInstallFatalErrorHandler(LLVMFatalErrorHandler Handler) {
LLVM_EXTENSION reinterpret_cast<void *>(Handler));
}
-void LLVMResetFatalErrorHandler() {
- remove_fatal_error_handler();
-}
+void LLVMResetFatalErrorHandler() { remove_fatal_error_handler(); }
#ifdef _WIN32
+#define WIN32_NO_STATUS
+#include "llvm/Support/Windows/WindowsSupport.h"
+#undef WIN32_NO_STATUS
+#include <ntstatus.h>
#include <winerror.h>
+// This is equivalent to NtCurrentTeb()->LastStatusValue, but the public
+// _TEB definition does not expose the LastStatusValue field directly.
+// Avoid offsetting into this structure by calling RtlGetLastNtStatus
+// from ntdll.dll.
+//
+// The return of this function will roughly match that of by
+// GetLastError, but this lower level API disambiguates some cases
+// that GetLastError does not.
+//
+// For more information, see:
+// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
+// https://github.com/llvm/llvm-project/issues/89137
+extern "C" NTSYSAPI NTSTATUS NTAPI RtlGetLastNtStatus();
+
// I'd rather not double the line count of the following.
#define MAP_ERR_TO_COND(x, y) \
case x: \
return make_error_code(errc::y)
std::error_code llvm::mapWindowsError(unsigned EV) {
+ // The mapping of NTSTATUS to Win32 error loses some information; special
+ // case the generic ERROR_ACCESS_DENIED code to check the underlying
+ // NTSTATUS and potentially return a more accurate error code.
+ if (EV == ERROR_ACCESS_DENIED) {
+ llvm::errc code = RtlGetLastNtStatus() == STATUS_DELETE_PENDING
+ ? errc::delete_pending
+ : errc::permission_denied;
+ return make_error_code(code);
+ }
switch (EV) {
MAP_ERR_TO_COND(ERROR_ACCESS_DENIED, permission_denied);
MAP_ERR_TO_COND(ERROR_ALREADY_EXISTS, file_exists);
diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index 4f0336a85daaa..0a994ff96a314 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -22,12 +22,22 @@
#include <sys/stat.h>
#include <sys/types.h>
-// These two headers must be included last, and make sure shlobj is required
+// These headers must be included last, and make sure shlobj is required
// after Windows.h to make sure it picks up our definition of _WIN32_WINNT
+#define WIN32_NO_STATUS
#include "llvm/Support/Windows/WindowsSupport.h"
+#undef WIN32_NO_STATUS
+
+#include <ntstatus.h>
#include <shellapi.h>
#include <shlobj.h>
+// This is equivalent to NtCurrentTeb()->LastStatusValue, but the public
+// _TEB definition does not expose the LastStatusValue field directly.
+// Avoid offsetting into this structure by calling RtlGetLastNtStatus
+// from ntdll.dll.
+extern "C" NTSYSAPI NTSTATUS NTAPI RtlGetLastNtStatus();
+
#undef max
// MinGW doesn't define this.
@@ -785,9 +795,20 @@ std::error_code status(const Twine &path, file_status &result, bool Follow) {
DWORD Flags = FILE_FLAG_BACKUP_SEMANTICS;
if (!Follow) {
- DWORD attr = ::GetFileAttributesW(path_utf16.begin());
- if (attr == INVALID_FILE_ATTRIBUTES)
- return getStatus(INVALID_HANDLE_VALUE, result);
+ DWORD attr;
+
+ // If getting file attributes fails due to a pending deletion, try
+ // again in a loop to avoid returning a misleading permission denied
+ // error.
+ for (int Retry = 200; Retry >= 0; --Retry) {
+ attr = ::GetFileAttributesW(path_utf16.begin());
+ if (attr != INVALID_FILE_ATTRIBUTES)
+ break;
+ std::error_code code = getStatus(INVALID_HANDLE_VALUE, result);
+ if (code != llvm::errc::delete_pending || !Retry)
+ return code;
+ ::Sleep(10);
+ }
// Handle reparse points.
if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
More information about the llvm-commits
mailing list