Rewrite parts of FindFirstFileEx to:
authorThomas Bluemel <thomas@reactsoft.com>
Fri, 13 Jul 2007 19:43:09 +0000 (19:43 +0000)
committerThomas Bluemel <thomas@reactsoft.com>
Fri, 13 Jul 2007 19:43:09 +0000 (19:43 +0000)
- Fix searches relative to the current directory using the features RtlDosPathNameToNtPathName_U provides instead of hacking around them
- Enable searches for fake DOS devices. Some installers like NSIS use this feature to determine if a drive exists, this works now.

svn path=/trunk/; revision=27634

reactos/dll/win32/kernel32/file/find.c

index 792cb51..9657332 100644 (file)
@@ -21,6 +21,8 @@
 
 #define FIND_DATA_SIZE (16*1024)
 
+#define FIND_DEVICE_HANDLE ((HANDLE)0x1)
+
 typedef struct _KERNEL32_FIND_FILE_DATA
 {
    HANDLE DirectoryHandle;
@@ -49,6 +51,61 @@ typedef struct _KERNEL32_FIND_DATA_HEADER
 
 /* FUNCTIONS ****************************************************************/
 
+HANDLE
+InternalCopyDeviceFindDataW(LPWIN32_FIND_DATAW lpFindFileData,
+                            LPCWSTR lpFileName,
+                            ULONG DeviceNameInfo)
+{
+    UNICODE_STRING DeviceName;
+
+    DeviceName.Length = DeviceName.MaximumLength = (USHORT)(DeviceNameInfo & 0xFFFF);
+    DeviceName.Buffer = (LPWSTR)((ULONG_PTR)lpFileName + (DeviceNameInfo >> 16));
+
+    /* Return the data */
+    RtlZeroMemory(lpFindFileData,
+                  sizeof(*lpFindFileData));
+    lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
+    RtlCopyMemory(lpFindFileData->cFileName,
+                  DeviceName.Buffer,
+                  DeviceName.Length);
+
+    return FIND_DEVICE_HANDLE;
+}
+
+HANDLE
+InternalCopyDeviceFindDataA(LPWIN32_FIND_DATAA lpFindFileData,
+                            PUNICODE_STRING FileName,
+                            ULONG DeviceNameInfo)
+{
+    UNICODE_STRING DeviceName;
+    ANSI_STRING BufferA;
+    CHAR Buffer[MAX_PATH];
+
+    DeviceName.Length = DeviceName.MaximumLength = (USHORT)(DeviceNameInfo & 0xFFFF);
+    DeviceName.Buffer = (LPWSTR)((ULONG_PTR)FileName->Buffer + (DeviceNameInfo >> 16));
+
+    BufferA.MaximumLength = sizeof(Buffer) - sizeof(Buffer[0]);
+    BufferA.Buffer = Buffer;
+    if (bIsFileApiAnsi)
+        RtlUnicodeStringToAnsiString (&BufferA, &DeviceName, FALSE);
+    else
+        RtlUnicodeStringToOemString (&BufferA, &DeviceName, FALSE);
+
+    /* NOTE: Free the string before we try to write the results to the caller,
+             this way we prevent a memory leak in case of a fault... */
+    RtlFreeUnicodeString(FileName);
+
+    /* Return the data */
+    RtlZeroMemory(lpFindFileData,
+                  sizeof(*lpFindFileData));
+    lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
+    RtlCopyMemory(lpFindFileData->cFileName,
+                  BufferA.Buffer,
+                  BufferA.Length);
+
+    return FIND_DEVICE_HANDLE;
+}
+
 VOID
 InternalCopyFindDataW(LPWIN32_FIND_DATAW            lpFindFileData,
                       PFILE_BOTH_DIR_INFORMATION    lpFileInfo)
@@ -143,6 +200,12 @@ InternalFindNextFile (
 
        DPRINT("InternalFindNextFile(%lx)\n", hFindFile);
 
+    if (hFindFile == FIND_DEVICE_HANDLE)
+    {
+        SetLastError (ERROR_NO_MORE_FILES);
+        return FALSE;
+    }
+
         IHeader = (PKERNEL32_FIND_DATA_HEADER)hFindFile;
         if (hFindFile == NULL || hFindFile == INVALID_HANDLE_VALUE ||
             IHeader->Type != FileFind)
@@ -196,215 +259,186 @@ InternalFindNextFile (
 HANDLE
 STDCALL
 InternalFindFirstFile (
-       LPCWSTR lpFileName,
-        BOOLEAN DirectoryOnly
+    LPCWSTR    lpFileName,
+    BOOLEAN DirectoryOnly,
+    PULONG DeviceNameInfo
        )
 {
        OBJECT_ATTRIBUTES ObjectAttributes;
        PKERNEL32_FIND_DATA_HEADER IHeader;
        PKERNEL32_FIND_FILE_DATA IData;
        IO_STATUS_BLOCK IoStatusBlock;
-       UNICODE_STRING NtPathU;
-       UNICODE_STRING PatternStr = RTL_CONSTANT_STRING(L"*");
+       UNICODE_STRING NtPathU, FileName, PathFileName;
        NTSTATUS Status;
-       PWSTR e1, e2;
-       WCHAR CurrentDir[256];
-       PWCHAR SlashlessFileName;
-       PWSTR SearchPath;
-       PWCHAR SearchPattern;
-       ULONG Length;
+       PWSTR NtPathBuffer;
+       BOOLEAN RemovedSlash = FALSE;
        BOOL bResult;
+       CURDIR DirInfo;
+    HANDLE hDirectory = NULL;
 
        DPRINT("FindFirstFileW(lpFileName %S)\n",
               lpFileName);
 
-       Length = wcslen(lpFileName);
-       if (L'\\' == lpFileName[Length - 1])
-       {
-           SlashlessFileName = RtlAllocateHeap(hProcessHeap,
-                                               0,
-                                               Length * sizeof(WCHAR));
-           if (NULL == SlashlessFileName)
-           {
-               SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-               return NULL;
-           }
-           memcpy(SlashlessFileName, lpFileName, (Length - 1) * sizeof(WCHAR));
-           SlashlessFileName[Length - 1] = L'\0';
-           lpFileName = SlashlessFileName;
-       }
-       else
+       *DeviceNameInfo = 0;
+       RtlZeroMemory(&PathFileName,
+                     sizeof(PathFileName));
+       RtlInitUnicodeString(&FileName,
+                            lpFileName);
+
+       bResult = RtlDosPathNameToNtPathName_U (lpFileName,
+                                               &NtPathU,
+                                               (PCWSTR *)((ULONG_PTR)&PathFileName.Buffer),
+                                               &DirInfo);
+       if (FALSE == bResult)
        {
-           SlashlessFileName = NULL;
+           SetLastError(ERROR_PATH_NOT_FOUND);
+           return NULL;
        }
 
-       e1 = wcsrchr(lpFileName, L'/');
-       e2 = wcsrchr(lpFileName, L'\\');
-       SearchPattern = max(e1, e2);
-       SearchPath = CurrentDir;
+       /* Save the buffer pointer for later, we need to free it! */
+       NtPathBuffer = NtPathU.Buffer;
 
-       if (NULL == SearchPattern)
-       {
-          CHECKPOINT;
-          SearchPattern = (PWCHAR)lpFileName;
-           Length = GetCurrentDirectoryW(sizeof(CurrentDir) / sizeof(WCHAR), SearchPath);
-          if (0 == Length)
-          {
-             if (NULL != SlashlessFileName)
-             {
-                RtlFreeHeap(hProcessHeap,
-                            0,
-                            SlashlessFileName);
-             }
-             return NULL;
-          }
-          if (Length > sizeof(CurrentDir) / sizeof(WCHAR))
-          {
-             SearchPath = RtlAllocateHeap(hProcessHeap,
-                                          HEAP_ZERO_MEMORY,
-                                          Length * sizeof(WCHAR));
-             if (NULL == SearchPath)
-             {
-                if (NULL != SlashlessFileName)
-                {
-                   RtlFreeHeap(hProcessHeap,
-                               0,
-                               SlashlessFileName);
-                }
-                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-                return NULL;
-             }
-             GetCurrentDirectoryW(Length, SearchPath);
-          }
-       }
-       else
+       /* If there is a file name/pattern then determine it's length */
+       if (PathFileName.Buffer != NULL)
        {
-          CHECKPOINT;
-          SearchPattern++;
-          Length = SearchPattern - lpFileName;
-          if (Length + 1 > sizeof(CurrentDir) / sizeof(WCHAR))
-          {
-              SearchPath = RtlAllocateHeap(hProcessHeap,
-                                          HEAP_ZERO_MEMORY,
-                                          (Length + 1) * sizeof(WCHAR));
-             if (NULL == SearchPath)
-             {
-                if (NULL != SlashlessFileName)
-                {
-                   RtlFreeHeap(hProcessHeap,
-                               0,
-                               SlashlessFileName);
-                }
-                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-                return NULL;
-             }
-          }
-           memcpy(SearchPath, lpFileName, Length * sizeof(WCHAR));
-          SearchPath[Length] = 0;
+           PathFileName.Length = NtPathU.Length -
+               (USHORT)((ULONG_PTR)PathFileName.Buffer - (ULONG_PTR)NtPathU.Buffer);
        }
+       PathFileName.MaximumLength = PathFileName.Length;
 
-       bResult = RtlDosPathNameToNtPathName_U (SearchPath,
-                                               &NtPathU,
-                                               NULL,
-                                               NULL);
-        if (SearchPath != CurrentDir)
-       {
-          RtlFreeHeap(hProcessHeap,
-                      0,
-                      SearchPath);
-       }
-       if (FALSE == bResult)
+       if (DirInfo.DosPath.Length != 0 && DirInfo.DosPath.Buffer != PathFileName.Buffer)
        {
-          if (NULL != SlashlessFileName)
-          {
-             RtlFreeHeap(hProcessHeap,
-                         0,
-                         SlashlessFileName);
-          }
-           SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-          return NULL;
+           if (PathFileName.Buffer != NULL)
+           {
+               /* This is a relative path to DirInfo.Handle, adjust NtPathU! */
+               NtPathU.Length = NtPathU.MaximumLength =
+                   (USHORT)((ULONG_PTR)PathFileName.Buffer - (ULONG_PTR)DirInfo.DosPath.Buffer);
+               NtPathU.Buffer = DirInfo.DosPath.Buffer;
+           }
        }
-
-       DPRINT("NtPathU \'%S\'\n", NtPathU.Buffer);
-
-       IHeader = RtlAllocateHeap (hProcessHeap,
-                                  HEAP_ZERO_MEMORY,
-                                   sizeof(KERNEL32_FIND_DATA_HEADER) +
-                                      sizeof(KERNEL32_FIND_FILE_DATA) + FIND_DATA_SIZE);
-       if (NULL == IHeader)
+       else
        {
-          RtlFreeHeap (hProcessHeap,
-                       0,
-                       NtPathU.Buffer);
-          if (NULL != SlashlessFileName)
-          {
-             RtlFreeHeap(hProcessHeap,
-                         0,
-                         SlashlessFileName);
-          }
-          SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-          return NULL;
+           /* This is an absolute path, NtPathU receives the full path */
+           DirInfo.Handle = NULL;
+           if (PathFileName.Buffer != NULL)
+           {
+               NtPathU.Length = NtPathU.MaximumLength =
+                   (USHORT)((ULONG_PTR)PathFileName.Buffer - (ULONG_PTR)NtPathU.Buffer);
+           }
        }
 
-       IHeader->Type = FileFind;
-       IData = (PKERNEL32_FIND_FILE_DATA)(IHeader + 1);
-
-       /* change pattern: "*.*" --> "*" */
-       if (wcscmp (SearchPattern, L"*.*"))
+       /* Remove a trailing backslash from the path, unless it's a DOS drive directly */
+       if (NtPathU.Length > 3 * sizeof(WCHAR) &&
+           NtPathU.Buffer[(NtPathU.Length / sizeof(WCHAR)) - 2] != L':' &&
+           NtPathU.Buffer[(NtPathU.Length / sizeof(WCHAR)) - 1] != L'\\')
        {
-           RtlInitUnicodeString(&PatternStr, SearchPattern);
+           NtPathU.Length -= sizeof(WCHAR);
+           RemovedSlash = TRUE;
        }
 
-       DPRINT("NtPathU \'%S\' Pattern \'%S\'\n",
-              NtPathU.Buffer, PatternStr.Buffer);
+       DPRINT("lpFileName: \"%ws\"\n", lpFileName);
+       DPRINT("NtPathU: \"%wZ\"\n", &NtPathU);
+       DPRINT("PathFileName: \"%wZ\"\n", &PathFileName);
+       DPRINT("RelativeTo: 0x%p\n", DirInfo.Handle);
 
        InitializeObjectAttributes (&ObjectAttributes,
                                    &NtPathU,
-                                   0,
-                                   NULL,
+                                   OBJ_CASE_INSENSITIVE,
+                                   DirInfo.Handle,
                                    NULL);
 
-       Status = NtOpenFile (&IData->DirectoryHandle,
+       Status = NtOpenFile (&hDirectory,
                             FILE_LIST_DIRECTORY,
                             &ObjectAttributes,
                             &IoStatusBlock,
                             FILE_SHARE_READ|FILE_SHARE_WRITE,
                             FILE_DIRECTORY_FILE);
 
-       RtlFreeHeap (hProcessHeap,
-                    0,
-                    NtPathU.Buffer);
+       if (!NT_SUCCESS(Status) && RemovedSlash)
+       {
+           /* Try again, this time with the trailing slash... */
+           NtPathU.Length -= sizeof(WCHAR);
+
+           Status = NtOpenFile (&hDirectory,
+                                FILE_LIST_DIRECTORY,
+                                &ObjectAttributes,
+                                &IoStatusBlock,
+                                FILE_SHARE_READ|FILE_SHARE_WRITE,
+                                FILE_DIRECTORY_FILE);
+
+           NtPathU.Length -= sizeof(WCHAR);
+       }
 
        if (!NT_SUCCESS(Status))
        {
-          RtlFreeHeap (hProcessHeap, 0, IHeader);
-          if (NULL != SlashlessFileName)
-          {
-             RtlFreeHeap(hProcessHeap,
-                         0,
-                         SlashlessFileName);
-          }
+           RtlFreeHeap (hProcessHeap,
+                        0,
+                        NtPathBuffer);
+
+          /* See if the application tries to look for a DOS device */
+          *DeviceNameInfo = RtlIsDosDeviceName_U((PWSTR)((ULONG_PTR)lpFileName));
+          if (*DeviceNameInfo != 0)
+              return FIND_DEVICE_HANDLE;
+
           SetLastErrorByStatus (Status);
           return(NULL);
        }
+
+       if (PathFileName.Length == 0)
+       {
+           /* No file part?! */
+           NtClose(hDirectory);
+           RtlFreeHeap (hProcessHeap,
+                        0,
+                        NtPathBuffer);
+           SetLastError(ERROR_FILE_NOT_FOUND);
+           return NULL;
+       }
+
+       IHeader = RtlAllocateHeap (hProcessHeap,
+                                  HEAP_ZERO_MEMORY,
+                                      sizeof(KERNEL32_FIND_DATA_HEADER) +
+                                      sizeof(KERNEL32_FIND_FILE_DATA) + FIND_DATA_SIZE);
+       if (NULL == IHeader)
+       {
+           RtlFreeHeap (hProcessHeap,
+                        0,
+                        NtPathBuffer);
+           NtClose(hDirectory);
+
+           SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+           return NULL;
+       }
+
+       IHeader->Type = FileFind;
+       IData = (PKERNEL32_FIND_FILE_DATA)(IHeader + 1);
+       IData->DirectoryHandle = hDirectory;
+
+       /* change pattern: "*.*" --> "*" */
+       if (PathFileName.Length == 6 &&
+           RtlCompareMemory(PathFileName.Buffer,
+                            L"*.*",
+                            6) == 6)
+       {
+           PathFileName.Length = 2;
+       }
+
        IData->pFileInfo = (PVOID)((ULONG_PTR)IData + sizeof(KERNEL32_FIND_FILE_DATA));
        IData->pFileInfo->FileIndex = 0;
-        IData->DirectoryOnly = DirectoryOnly;
+       IData->DirectoryOnly = DirectoryOnly;
+
+       bResult = InternalFindNextFile((HANDLE)IHeader, &PathFileName);
+
+       RtlFreeHeap (hProcessHeap,
+                    0,
+                    NtPathBuffer);
 
-        bResult = InternalFindNextFile((HANDLE)IHeader, &PatternStr);
-       if (NULL != SlashlessFileName)
+       if (!bResult)
        {
-          RtlFreeHeap(hProcessHeap,
-                      0,
-                      SlashlessFileName);
+           FindClose((HANDLE)IHeader);
+           return NULL;
        }
 
-        if (!bResult)
-        {
-            FindClose((HANDLE)IHeader);
-            return NULL;
-        }
-
        return (HANDLE)IHeader;
 }
 
@@ -423,6 +457,7 @@ FindFirstFileA (
        PKERNEL32_FIND_FILE_DATA IData;
        UNICODE_STRING FileNameU;
        ANSI_STRING FileName;
+       ULONG DeviceNameInfo;
 
        RtlInitAnsiString (&FileName,
                           (LPSTR)lpFileName);
@@ -437,16 +472,23 @@ FindFirstFileA (
                                             &FileName,
                                             TRUE);
 
-       IHeader = InternalFindFirstFile (FileNameU.Buffer, FALSE);
-
-       RtlFreeUnicodeString (&FileNameU);
+       IHeader = InternalFindFirstFile (FileNameU.Buffer, FALSE, &DeviceNameInfo);
 
        if (IHeader == NULL)
        {
+               RtlFreeUnicodeString (&FileNameU);
                DPRINT("Failing request\n");
                return INVALID_HANDLE_VALUE;
        }
 
+       if ((HANDLE)IHeader == FIND_DEVICE_HANDLE)
+       {
+               /* NOTE: FileNameU will be freed in InternalCopyDeviceFindDataA */
+               return InternalCopyDeviceFindDataA(lpFindFileData, &FileNameU, DeviceNameInfo);
+       }
+
+       RtlFreeUnicodeString (&FileNameU);
+
        IData = (PKERNEL32_FIND_FILE_DATA)(IHeader + 1);
 
        DPRINT("IData->pFileInfo->FileNameLength %d\n",
@@ -502,6 +544,9 @@ FindClose (
 
        DPRINT("FindClose(hFindFile %x)\n",hFindFile);
 
+       if (hFindFile == FIND_DEVICE_HANDLE)
+               return TRUE;
+
        if (!hFindFile || hFindFile == INVALID_HANDLE_VALUE)
        {
                SetLastError (ERROR_INVALID_HANDLE);
@@ -600,6 +645,7 @@ FindFirstFileExW (LPCWSTR               lpFileName,
 {
     PKERNEL32_FIND_DATA_HEADER IHeader;
     PKERNEL32_FIND_FILE_DATA IData;
+    ULONG DeviceNameInfo;
 
     if (fInfoLevelId != FindExInfoStandard)
     {
@@ -614,12 +660,15 @@ FindFirstFileExW (LPCWSTR               lpFileName,
             return INVALID_HANDLE_VALUE;
         }
 
-        IHeader = InternalFindFirstFile (lpFileName, fSearchOp == FindExSearchLimitToDirectories ? TRUE : FALSE);
-       if (IHeader == NULL)
-       {
-               DPRINT("Failing request\n");
-               return INVALID_HANDLE_VALUE;
-       }
+        IHeader = InternalFindFirstFile (lpFileName, fSearchOp == FindExSearchLimitToDirectories ? TRUE : FALSE, &DeviceNameInfo);
+        if (IHeader == NULL)
+        {
+            DPRINT("Failing request\n");
+            return INVALID_HANDLE_VALUE;
+        }
+
+        if ((HANDLE)IHeader == FIND_DEVICE_HANDLE)
+            return InternalCopyDeviceFindDataW((LPWIN32_FIND_DATAW)lpFindFileData, lpFileName, DeviceNameInfo);
 
         IData = (PKERNEL32_FIND_FILE_DATA)(IHeader + 1);
 
@@ -650,6 +699,7 @@ FindFirstFileExA (
     PKERNEL32_FIND_FILE_DATA IData;
     UNICODE_STRING FileNameU;
     ANSI_STRING FileNameA;
+    ULONG DeviceNameInfo;
 
     if (fInfoLevelId != FindExInfoStandard)
     {
@@ -672,16 +722,23 @@ FindFirstFileExA (
        else
             RtlOemStringToUnicodeString (&FileNameU, &FileNameA, TRUE);
 
-       IHeader = InternalFindFirstFile (FileNameU.Buffer, FALSE);
-
-       RtlFreeUnicodeString (&FileNameU);
+       IHeader = InternalFindFirstFile (FileNameU.Buffer, FALSE, &DeviceNameInfo);
 
        if (IHeader == NULL)
        {
+               RtlFreeUnicodeString (&FileNameU);
                DPRINT("Failing request\n");
                return INVALID_HANDLE_VALUE;
        }
 
+       if ((HANDLE)IHeader == FIND_DEVICE_HANDLE)
+       {
+               /* NOTE: FileNameU will be freed in InternalCopyDeviceFindDataA */
+               return InternalCopyDeviceFindDataA(lpFindFileData, &FileNameU, DeviceNameInfo);
+       }
+
+       RtlFreeUnicodeString (&FileNameU);
+
         IData = (PKERNEL32_FIND_FILE_DATA)(IHeader + 1);
 
        /* copy data into WIN32_FIND_DATA structure */