소스 검색

KWSys 2021-11-01 (572f2a65)

Code extracted from:

    https://gitlab.kitware.com/utils/kwsys.git

at commit 572f2a6592d671ecc865fe7821a0f4803deb9be7 (master).

Upstream Shortlog
-----------------

Brad King (3):
      025a4951 SystemTools: Add Windows app exec alias support to ReadSymlink
      f5b3500f SystemTools: Simplify FileIsExecutable on Windows
      bcddb31b SystemTools: Fix Windows app exec alias layout

Yuriy O'Donnell (1):
      29f31d17 SystemTools: Add Windows app exec alias support to FileExists()
KWSys Upstream 4 년 전
부모
커밋
e9ab6eeeb5
1개의 변경된 파일86개의 추가작업 그리고 10개의 파일을 삭제
  1. 86 10
      SystemTools.cxx

+ 86 - 10
SystemTools.cxx

@@ -34,6 +34,10 @@
 #include <utility>
 #include <vector>
 
+#ifdef _WIN32
+#  include <cwchar>
+#endif
+
 // Work-around CMake dependency scanning limitation.  This must
 // duplicate the above list of headers.
 #if 0
@@ -103,6 +107,9 @@
 #  if defined(_MSC_VER) && _MSC_VER >= 1800
 #    define KWSYS_WINDOWS_DEPRECATED_GetVersionEx
 #  endif
+#  ifndef IO_REPARSE_TAG_APPEXECLINK
+#    define IO_REPARSE_TAG_APPEXECLINK (0x8000001BL)
+#  endif
 // from ntifs.h, which can only be used by drivers
 typedef struct _REPARSE_DATA_BUFFER
 {
@@ -132,8 +139,46 @@ typedef struct _REPARSE_DATA_BUFFER
     {
       UCHAR DataBuffer[1];
     } GenericReparseBuffer;
+    struct
+    {
+      ULONG Version;
+      WCHAR StringList[1];
+      // In version 3, there are 4 NUL-terminated strings:
+      // * Package ID
+      // * Entry Point
+      // * Executable Path
+      // * Application Type
+    } AppExecLinkReparseBuffer;
   } DUMMYUNIONNAME;
 } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+namespace {
+WCHAR* GetAppExecLink(PREPARSE_DATA_BUFFER data, size_t& len)
+{
+  // We only know the layout of version 3.
+  if (data->AppExecLinkReparseBuffer.Version != 3) {
+    return nullptr;
+  }
+
+  WCHAR* pstr = data->AppExecLinkReparseBuffer.StringList;
+
+  // Skip the package id and entry point strings.
+  for (int i = 0; i < 2; ++i) {
+    len = std::wcslen(pstr);
+    if (len == 0) {
+      return nullptr;
+    }
+    pstr += len + 1;
+  }
+
+  // The third string is the executable path.
+  len = std::wcslen(pstr);
+  if (len == 0) {
+    return nullptr;
+  }
+  return pstr;
+}
+}
 #endif
 
 #if !KWSYS_CXX_HAS_ENVIRON_IN_STDLIB_H
@@ -1343,8 +1388,8 @@ bool SystemTools::FileExists(const std::string& filename)
     return false;
   }
 #if defined(_WIN32)
-  DWORD attr =
-    GetFileAttributesW(Encoding::ToWindowsExtendedPath(filename).c_str());
+  const std::wstring path = Encoding::ToWindowsExtendedPath(filename);
+  DWORD attr = GetFileAttributesW(path.c_str());
   if (attr == INVALID_FILE_ATTRIBUTES) {
     return false;
   }
@@ -1352,12 +1397,38 @@ bool SystemTools::FileExists(const std::string& filename)
   if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
     // Using 0 instead of GENERIC_READ as it allows reading of file attributes
     // even if we do not have permission to read the file itself
-    HANDLE handle =
-      CreateFileW(Encoding::ToWindowsExtendedPath(filename).c_str(), 0, 0,
-                  nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+    HANDLE handle = CreateFileW(path.c_str(), 0, 0, nullptr, OPEN_EXISTING,
+                                FILE_FLAG_BACKUP_SEMANTICS, nullptr);
 
     if (handle == INVALID_HANDLE_VALUE) {
-      return false;
+      // A reparse point may be an execution alias (Windows Store app), which
+      // is similar to a symlink but it cannot be opened as a regular file.
+      // We must look at the reparse point data explicitly.
+      handle = CreateFileW(
+        path.c_str(), 0, 0, nullptr, OPEN_EXISTING,
+        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+
+      if (handle == INVALID_HANDLE_VALUE) {
+        return false;
+      }
+
+      byte buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+      DWORD bytesReturned = 0;
+
+      if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, buffer,
+                           MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytesReturned,
+                           nullptr)) {
+        CloseHandle(handle);
+        return false;
+      }
+
+      CloseHandle(handle);
+
+      PREPARSE_DATA_BUFFER data =
+        reinterpret_cast<PREPARSE_DATA_BUFFER>(&buffer[0]);
+
+      // Assume that file exists if it is an execution alias.
+      return data->ReparseTag == IO_REPARSE_TAG_APPEXECLINK;
     }
 
     CloseHandle(handle);
@@ -3011,11 +3082,7 @@ bool SystemTools::FileIsDirectory(const std::string& inName)
 
 bool SystemTools::FileIsExecutable(const std::string& name)
 {
-#if defined(_WIN32)
-  return SystemTools::FileExists(name, true);
-#else
   return !FileIsDirectory(name) && TestFileAccess(name, TEST_FILE_EXECUTE);
-#endif
 }
 
 bool SystemTools::FileIsSymlink(const std::string& name)
@@ -3164,6 +3231,15 @@ Status SystemTools::ReadSymlink(std::string const& newName,
       data->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
     substituteNameData = data->MountPointReparseBuffer.PathBuffer +
       data->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR);
+  } else if (data->ReparseTag == IO_REPARSE_TAG_APPEXECLINK) {
+    // The reparse buffer is a list of 0-terminated non-empty strings,
+    // terminated by an empty string (0-0).  We need the third string.
+    size_t destLen;
+    substituteNameData = GetAppExecLink(data, destLen);
+    if (substituteNameData == nullptr || destLen == 0) {
+      return Status::Windows(ERROR_SYMLINK_NOT_SUPPORTED);
+    }
+    substituteNameLength = static_cast<USHORT>(destLen);
   } else {
     return Status::Windows(ERROR_REPARSE_TAG_MISMATCH);
   }