[llvm] [Support] Handle delete_pending case for Windows fs::status (PR #90655)

via llvm-commits llvm-commits at lists.llvm.org
Wed May 1 15:15:02 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-support

Author: Jeremy Day (z2oh)

<details>
<summary>Changes</summary>

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.

Fixes #<!-- -->89137

---
Full diff: https://github.com/llvm/llvm-project/pull/90655.diff


3 Files Affected:

- (modified) llvm/include/llvm/Support/Errc.h (+4) 
- (modified) llvm/lib/Support/CMakeLists.txt (+2-1) 
- (modified) llvm/lib/Support/Windows/Path.inc (+33-3) 


``````````diff
diff --git a/llvm/include/llvm/Support/Errc.h b/llvm/include/llvm/Support/Errc.h
index 9df522cbe45c76..fcb69d303109a3 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 03e888958a0711..36a208f0919d36 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/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index 4f0336a85daaa5..96940adb3c4682 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.
@@ -786,8 +796,28 @@ 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);
+    if (attr == INVALID_FILE_ATTRIBUTES) {
+      // If the file is just pending deletion, try querying file attributes
+      // again in a loop to avoid returning a misleading permission denied
+      // error.
+      if (RtlGetLastNtStatus() == STATUS_DELETE_PENDING) {
+        for (unsigned Retry = 0; Retry != 200; ++Retry) {
+          ::Sleep(10);
+          attr = ::GetFileAttributesW(path_utf16.begin());
+          if (attr != INVALID_FILE_ATTRIBUTES ||
+              RtlGetLastNtStatus() != STATUS_DELETE_PENDING)
+            break;
+        }
+
+        if (attr == INVALID_FILE_ATTRIBUTES) {
+          return RtlGetLastNtStatus() == STATUS_DELETE_PENDING
+                     ? llvm::errc::delete_pending
+                     : getStatus(INVALID_HANDLE_VALUE, result);
+        }
+      } else {
+        return getStatus(INVALID_HANDLE_VALUE, result);
+      }
+    }
 
     // Handle reparse points.
     if (attr & FILE_ATTRIBUTE_REPARSE_POINT)

``````````

</details>


https://github.com/llvm/llvm-project/pull/90655


More information about the llvm-commits mailing list