Revert tree-restructure attempt: r66583, r66582, r66581, r66578, sauf ntdll changes...
[reactos.git] / reactos / dll / win32 / kernel32 / client / file / find.c
diff --git a/reactos/dll/win32/kernel32/client/file/find.c b/reactos/dll/win32/kernel32/client/file/find.c
new file mode 100644 (file)
index 0000000..ccd1385
--- /dev/null
@@ -0,0 +1,1150 @@
+/*
+ * COPYRIGHT:       See COPYING in the top level directory
+ * PROJECT:         ReactOS system libraries
+ * FILE:            dll/win32/kernel32/client/file/find.c
+ * PURPOSE:         Find functions
+ * PROGRAMMERS:     Ariadne (ariadne@xs4all.nl)
+ *                  Pierre Schweitzer (pierre.schweitzer@reactos.org)
+ *                  Hermes BELUSCA - MAITO (hermes.belusca@sfr.fr)
+ */
+
+/* INCLUDES *******************************************************************/
+
+#include <k32.h>
+#define NDEBUG
+#include <debug.h>
+DEBUG_CHANNEL(kernel32file);
+
+
+/* TYPES **********************************************************************/
+
+#define FIND_DATA_SIZE      0x4000
+#define FIND_DEVICE_HANDLE  ((HANDLE)0x1)
+
+typedef enum _FIND_DATA_TYPE
+{
+    FindFile   = 1,
+    FindStream = 2
+} FIND_DATA_TYPE;
+
+/*
+ * FILE_FULL_DIR_INFORMATION and FILE_BOTH_DIR_INFORMATION structures layout.
+ *
+ *
+ *  struct FILE_FULL_DIR_INFORMATION   |   struct FILE_BOTH_DIR_INFORMATION
+ * ------------------------------------+---------------------------------------
+ *     ULONG NextEntryOffset;          |       ULONG NextEntryOffset;
+ *     ULONG FileIndex;                |       ULONG FileIndex;
+ *     LARGE_INTEGER CreationTime;     |       LARGE_INTEGER CreationTime;
+ *     LARGE_INTEGER LastAccessTime;   |       LARGE_INTEGER LastAccessTime;
+ *     LARGE_INTEGER LastWriteTime;    |       LARGE_INTEGER LastWriteTime;
+ *     LARGE_INTEGER ChangeTime;       |       LARGE_INTEGER ChangeTime;
+ *     LARGE_INTEGER EndOfFile;        |       LARGE_INTEGER EndOfFile;
+ *     LARGE_INTEGER AllocationSize;   |       LARGE_INTEGER AllocationSize;
+ *     ULONG FileAttributes;           |       ULONG FileAttributes;
+ *     ULONG FileNameLength;           |       ULONG FileNameLength;
+ *     ULONG EaSize;                   |       ULONG EaSize;
+ * ------------------------------------+---------------------------------------
+ *     WCHAR FileName[1];              |       CCHAR ShortNameLength;
+ *                                     |       WCHAR ShortName[12];
+ *                                     |       WCHAR FileName[1];
+ *
+ * Therefore we can use pointers to FILE_FULL_DIR_INFORMATION when one doesn't
+ * want to refer to the ShortName* fields and FileName (useful for implementing
+ * the FindExInfoBasic functionality for FindFirstFileEx), however a cast to
+ * FILE_BOTH_DIR_INFORMATION is required when one wants to use FileName and
+ * ShortName* fields (needed for the FindExInfoStandard functionality).
+ *
+ */
+typedef union _DIR_INFORMATION
+{
+    PVOID DirInfo;
+    PFILE_FULL_DIR_INFORMATION FullDirInfo;
+    PFILE_BOTH_DIR_INFORMATION BothDirInfo;
+} DIR_INFORMATION;
+
+typedef struct _FIND_FILE_DATA
+{
+    HANDLE Handle;
+    FINDEX_INFO_LEVELS InfoLevel;
+    FINDEX_SEARCH_OPS SearchOp;
+
+    /*
+     * For handling STATUS_BUFFER_OVERFLOW errors emitted by
+     * NtQueryDirectoryFile in the FildNextFile function.
+     */
+    BOOLEAN HasMoreData;
+
+    /*
+     * "Pointer" to the next file info structure in the buffer.
+     * The type is defined by the 'InfoLevel' parameter.
+     */
+    DIR_INFORMATION NextDirInfo;
+
+    BYTE Buffer[FIND_DATA_SIZE];
+} FIND_FILE_DATA, *PFIND_FILE_DATA;
+
+typedef struct _FIND_STREAM_DATA
+{
+    STREAM_INFO_LEVELS InfoLevel;
+    PFILE_STREAM_INFORMATION FileStreamInfo;
+    PFILE_STREAM_INFORMATION CurrentInfo;
+} FIND_STREAM_DATA, *PFIND_STREAM_DATA;
+
+typedef struct _FIND_DATA_HANDLE
+{
+    FIND_DATA_TYPE Type;
+    RTL_CRITICAL_SECTION Lock;
+
+    /*
+     * Pointer to the following finding data, located at
+     * (this + 1). The type is defined by the 'Type' parameter.
+     */
+    union
+    {
+        PFIND_FILE_DATA FindFileData;
+        PFIND_STREAM_DATA FindStreamData;
+    } u;
+
+} FIND_DATA_HANDLE, *PFIND_DATA_HANDLE;
+
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+static VOID
+CopyDeviceFindData(OUT LPWIN32_FIND_DATAW lpFindFileData,
+                   IN LPCWSTR lpFileName,
+                   IN ULONG DeviceNameInfo)
+{
+    LPCWSTR DeviceName;
+    SIZE_T Length;
+
+    _SEH2_TRY
+    {
+        /* DeviceNameInfo == { USHORT Offset; USHORT Length } */
+        Length     =  (SIZE_T)(DeviceNameInfo & 0xFFFF);
+        DeviceName = (LPCWSTR)((ULONG_PTR)lpFileName + ((DeviceNameInfo >> 16) & 0xFFFF));
+
+        /* Return the data */
+        RtlZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
+        lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
+        RtlCopyMemory(lpFindFileData->cFileName,
+                      DeviceName,
+                      Length);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+    }
+    _SEH2_END;
+
+    return;
+}
+
+static VOID
+CopyFindData(OUT LPWIN32_FIND_DATAW lpFindFileData,
+             IN FINDEX_INFO_LEVELS fInfoLevelId,
+             IN DIR_INFORMATION DirInfo)
+{
+#define ULARGE_INTEGER_2_FILETIME(ft, ul) \
+do { \
+    (ft).dwHighDateTime = (ul).u.HighPart; \
+    (ft).dwLowDateTime  = (ul).u.LowPart ; \
+} while(0)
+
+    _SEH2_TRY
+    {
+        RtlZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
+
+        lpFindFileData->dwFileAttributes = DirInfo.FullDirInfo->FileAttributes;
+
+        ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftCreationTime, DirInfo.FullDirInfo->CreationTime);
+        ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftLastAccessTime, DirInfo.FullDirInfo->LastAccessTime);
+        ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftLastWriteTime, DirInfo.FullDirInfo->LastWriteTime);
+
+        lpFindFileData->nFileSizeHigh = DirInfo.FullDirInfo->EndOfFile.u.HighPart;
+        lpFindFileData->nFileSizeLow = DirInfo.FullDirInfo->EndOfFile.u.LowPart;
+
+        /* dwReserved0 contains the NTFS reparse point tag, if any. */
+        if (DirInfo.FullDirInfo->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+            lpFindFileData->dwReserved0 = DirInfo.FullDirInfo->EaSize;
+        else
+            lpFindFileData->dwReserved0 = 0;
+
+        /* Unused dwReserved1 field */
+        lpFindFileData->dwReserved1 = 0;
+
+        if (fInfoLevelId == FindExInfoStandard)
+        {
+            RtlCopyMemory(lpFindFileData->cFileName,
+                          DirInfo.BothDirInfo->FileName,
+                          DirInfo.BothDirInfo->FileNameLength);
+            lpFindFileData->cFileName[DirInfo.BothDirInfo->FileNameLength / sizeof(WCHAR)] = UNICODE_NULL;
+
+            RtlCopyMemory(lpFindFileData->cAlternateFileName,
+                          DirInfo.BothDirInfo->ShortName,
+                          DirInfo.BothDirInfo->ShortNameLength);
+            lpFindFileData->cAlternateFileName[DirInfo.BothDirInfo->ShortNameLength / sizeof(WCHAR)] = UNICODE_NULL;
+        }
+        else if (fInfoLevelId == FindExInfoBasic)
+        {
+            RtlCopyMemory(lpFindFileData->cFileName,
+                          DirInfo.FullDirInfo->FileName,
+                          DirInfo.FullDirInfo->FileNameLength);
+            lpFindFileData->cFileName[DirInfo.FullDirInfo->FileNameLength / sizeof(WCHAR)] = UNICODE_NULL;
+
+            lpFindFileData->cAlternateFileName[0] = UNICODE_NULL;
+        }
+        else
+        {
+            /* Invalid InfoLevelId */
+            ASSERT(FALSE);
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+    }
+    _SEH2_END;
+
+    return;
+}
+
+static VOID
+CopyStreamData(IN OUT PFIND_STREAM_DATA FindStreamData,
+               OUT PWIN32_FIND_STREAM_DATA lpFindStreamData)
+{
+    _SEH2_TRY
+    {
+        ASSERT(FindStreamData->CurrentInfo);
+
+        switch (FindStreamData->InfoLevel)
+        {
+            case FindStreamInfoStandard:
+            {
+                ULONG StreamNameLen = min(FindStreamData->CurrentInfo->StreamNameLength,
+                                          sizeof(lpFindStreamData->cStreamName) - sizeof(WCHAR));
+
+                RtlZeroMemory(lpFindStreamData, sizeof(*lpFindStreamData));
+
+                lpFindStreamData->StreamSize.QuadPart = FindStreamData->CurrentInfo->StreamSize.QuadPart;
+                RtlCopyMemory(lpFindStreamData->cStreamName,
+                              FindStreamData->CurrentInfo->StreamName,
+                              StreamNameLen);
+                lpFindStreamData->cStreamName[StreamNameLen / sizeof(WCHAR)] = UNICODE_NULL;
+
+                break;
+            }
+
+            default:
+            {
+                /* Invalid InfoLevel */
+                ASSERT(FALSE);
+                break;
+            }
+        }
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+    }
+    _SEH2_END;
+
+    return;
+}
+
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+/*
+ * @implemented
+ */
+HANDLE
+WINAPI
+FindFirstFileA(IN LPCSTR lpFileName,
+               OUT LPWIN32_FIND_DATAA lpFindFileData)
+{
+    HANDLE hSearch;
+    NTSTATUS Status;
+    ANSI_STRING Ansi;
+    UNICODE_STRING UTF8;
+    PUNICODE_STRING lpFileNameW;
+    WIN32_FIND_DATAW FindFileDataW;
+
+    lpFileNameW = Basep8BitStringToStaticUnicodeString(lpFileName);
+    if (!lpFileNameW) return INVALID_HANDLE_VALUE;
+
+    hSearch = FindFirstFileExW(lpFileNameW->Buffer,
+                               FindExInfoStandard,
+                               &FindFileDataW,
+                               FindExSearchNameMatch,
+                               NULL, 0);
+    if (hSearch == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
+
+    RtlCopyMemory(lpFindFileData,
+                  &FindFileDataW,
+                  FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
+
+    RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
+    Ansi.Buffer = lpFindFileData->cFileName;
+    Ansi.Length = 0;
+    Ansi.MaximumLength = sizeof(lpFindFileData->cFileName);
+    Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+    if (!NT_SUCCESS(Status))
+    {
+        FindClose(hSearch);
+        BaseSetLastNTError(Status);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
+    Ansi.Buffer = lpFindFileData->cAlternateFileName;
+    Ansi.Length = 0;
+    Ansi.MaximumLength = sizeof(lpFindFileData->cAlternateFileName);
+    Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+    if (!NT_SUCCESS(Status))
+    {
+        FindClose(hSearch);
+        BaseSetLastNTError(Status);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    return hSearch;
+}
+
+
+/*
+ * @implemented
+ */
+HANDLE
+WINAPI
+FindFirstFileW(IN LPCWSTR lpFileName,
+               OUT LPWIN32_FIND_DATAW lpFindFileData)
+{
+    return FindFirstFileExW(lpFileName,
+                            FindExInfoStandard,
+                            lpFindFileData,
+                            FindExSearchNameMatch,
+                            NULL, 0);
+}
+
+
+/*
+ * @implemented
+ */
+BOOL
+WINAPI
+FindNextFileA(IN HANDLE hFindFile,
+              OUT LPWIN32_FIND_DATAA lpFindFileData)
+{
+    NTSTATUS Status;
+    ANSI_STRING Ansi;
+    UNICODE_STRING UTF8;
+    WIN32_FIND_DATAW FindFileDataW;
+
+    if (!FindNextFileW(hFindFile, &FindFileDataW))
+        return FALSE;
+
+    RtlCopyMemory(lpFindFileData,
+                  &FindFileDataW,
+                  FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
+
+    RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
+    Ansi.Buffer = lpFindFileData->cFileName;
+    Ansi.Length = 0;
+    Ansi.MaximumLength = sizeof(lpFindFileData->cFileName);
+    Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+    if (!NT_SUCCESS(Status))
+    {
+        BaseSetLastNTError(Status);
+        return FALSE;
+    }
+
+    RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
+    Ansi.Buffer = lpFindFileData->cAlternateFileName;
+    Ansi.Length = 0;
+    Ansi.MaximumLength = sizeof(lpFindFileData->cAlternateFileName);
+    Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+    if (!NT_SUCCESS(Status))
+    {
+        BaseSetLastNTError(Status);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+/*
+ * @implemented
+ */
+BOOL
+WINAPI
+FindNextFileW(IN HANDLE hFindFile,
+              OUT LPWIN32_FIND_DATAW lpFindFileData)
+{
+    NTSTATUS Status = STATUS_SUCCESS;
+    DIR_INFORMATION FoundFile = {NULL};
+
+    TRACE("FindNextFileW(%p, 0x%p)\n", hFindFile, lpFindFileData);
+
+    if (hFindFile != FIND_DEVICE_HANDLE)
+    {
+        PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindFile;
+        PFIND_FILE_DATA FindFileData;
+        FINDEX_INFO_LEVELS InfoLevel;
+        IO_STATUS_BLOCK IoStatusBlock;
+        DIR_INFORMATION DirInfo = {NULL}, NextDirInfo = {NULL};
+
+        if (hFindFile == NULL || hFindFile == INVALID_HANDLE_VALUE ||
+            FindDataHandle->Type != FindFile)
+        {
+            SetLastError(ERROR_INVALID_HANDLE);
+            return FALSE;
+        }
+
+        RtlEnterCriticalSection(&FindDataHandle->Lock);
+
+        FindFileData = FindDataHandle->u.FindFileData;
+        InfoLevel = FindFileData->InfoLevel;
+
+        do
+        {
+            if (FindFileData->NextDirInfo.DirInfo == NULL)
+            {
+                Status = NtQueryDirectoryFile(FindFileData->Handle,
+                                              NULL, NULL, NULL,
+                                              &IoStatusBlock,
+                                              &FindFileData->Buffer,
+                                              sizeof(FindFileData->Buffer),
+                                              (InfoLevel == FindExInfoStandard
+                                                          ? FileBothDirectoryInformation
+                                                          : FileFullDirectoryInformation),
+                                              FALSE,
+                                              NULL, /* Use the file pattern from the first call */
+                                              FALSE);
+                if (Status == STATUS_BUFFER_OVERFLOW)
+                {
+                    FindFileData->HasMoreData = TRUE;
+                    Status = STATUS_SUCCESS;
+                }
+                else
+                {
+                    if (!NT_SUCCESS(Status)) break;
+                    FindFileData->HasMoreData = FALSE;
+                }
+
+                FindFileData->NextDirInfo.DirInfo = &FindFileData->Buffer;
+            }
+
+            DirInfo = FindFileData->NextDirInfo;
+
+            if (DirInfo.FullDirInfo->NextEntryOffset != 0)
+            {
+                ULONG_PTR BufferEnd = (ULONG_PTR)&FindFileData->Buffer + sizeof(FindFileData->Buffer);
+                PWSTR pFileName;
+
+                NextDirInfo.DirInfo = FindFileData->NextDirInfo.DirInfo =
+                    (PVOID)((ULONG_PTR)DirInfo.DirInfo + DirInfo.FullDirInfo->NextEntryOffset);
+
+                pFileName = (InfoLevel == FindExInfoStandard
+                                        ? NextDirInfo.BothDirInfo->FileName
+                                        : NextDirInfo.FullDirInfo->FileName);
+
+                /* Be paranoid and make sure that the next entry is completely there */
+                if (BufferEnd < (ULONG_PTR)NextDirInfo.DirInfo ||
+                    BufferEnd < (ULONG_PTR)&NextDirInfo.FullDirInfo->FileNameLength + sizeof(NextDirInfo.FullDirInfo->FileNameLength) ||
+                    BufferEnd <= (ULONG_PTR)((ULONG_PTR)pFileName + NextDirInfo.FullDirInfo->FileNameLength))
+                {
+                    FindFileData->NextDirInfo.DirInfo = NULL;
+                }
+            }
+            else
+            {
+                FindFileData->NextDirInfo.DirInfo = NULL;
+            }
+
+            if ((FindFileData->SearchOp != FindExSearchLimitToDirectories) ||
+                (DirInfo.FullDirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+            {
+                FoundFile = DirInfo;
+            }
+        } while ( FoundFile.DirInfo == NULL && (FindFileData->NextDirInfo.DirInfo || FindFileData->HasMoreData) );
+
+        if (FoundFile.DirInfo != NULL)
+        {
+            /* Return the information */
+            CopyFindData(lpFindFileData, InfoLevel, FoundFile);
+        }
+
+        RtlLeaveCriticalSection(&FindDataHandle->Lock);
+    }
+
+    if (!NT_SUCCESS(Status))
+    {
+        BaseSetLastNTError(Status);
+        return FALSE;
+    }
+    else if (FoundFile.DirInfo == NULL)
+    {
+        SetLastError(ERROR_NO_MORE_FILES);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+/*
+ * @implemented
+ */
+BOOL
+WINAPI
+FindClose(HANDLE hFindFile)
+{
+    TRACE("FindClose(hFindFile %p)\n", hFindFile);
+
+    if (hFindFile == FIND_DEVICE_HANDLE)
+        return TRUE;
+
+    if (!hFindFile || hFindFile == INVALID_HANDLE_VALUE)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return FALSE;
+    }
+
+    /* Protect with SEH against closing attempts on invalid handles. */
+    _SEH2_TRY
+    {
+        PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindFile;
+
+        switch (FindDataHandle->Type)
+        {
+            case FindFile:
+            {
+                RtlEnterCriticalSection(&FindDataHandle->Lock);
+                NtClose(FindDataHandle->u.FindFileData->Handle);
+                RtlLeaveCriticalSection(&FindDataHandle->Lock);
+                RtlDeleteCriticalSection(&FindDataHandle->Lock);
+                break;
+            }
+
+            case FindStream:
+            {
+                RtlEnterCriticalSection(&FindDataHandle->Lock);
+                if (FindDataHandle->u.FindStreamData->FileStreamInfo != NULL)
+                {
+                    RtlFreeHeap(RtlGetProcessHeap(), 0,
+                                FindDataHandle->u.FindStreamData->FileStreamInfo);
+                }
+                RtlLeaveCriticalSection(&FindDataHandle->Lock);
+                RtlDeleteCriticalSection(&FindDataHandle->Lock);
+                break;
+            }
+
+            default:
+            {
+                SetLastError(ERROR_INVALID_HANDLE);
+                _SEH2_YIELD(return FALSE);
+            }
+        }
+
+        RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        BaseSetLastNTError(_SEH2_GetExceptionCode());
+        _SEH2_YIELD(return FALSE);
+    }
+    _SEH2_END;
+    return TRUE;
+}
+
+
+/*
+ * @unimplemented
+ */
+HANDLE
+WINAPI
+FindFirstFileExA(IN LPCSTR lpFileName,
+                 IN FINDEX_INFO_LEVELS fInfoLevelId,
+                 OUT LPVOID lpFindFileData,
+                 IN FINDEX_SEARCH_OPS fSearchOp,
+                 LPVOID lpSearchFilter,
+                 IN DWORD dwAdditionalFlags)
+{
+    HANDLE hSearch;
+    NTSTATUS Status;
+    ANSI_STRING Ansi;
+    UNICODE_STRING UTF8;
+    PUNICODE_STRING lpFileNameW;
+    WIN32_FIND_DATAW FindFileDataW;
+    LPWIN32_FIND_DATAA lpFindFileDataA = (LPWIN32_FIND_DATAA)lpFindFileData;
+
+    if ((fInfoLevelId != FindExInfoStandard && fInfoLevelId != FindExInfoBasic) ||
+        fSearchOp == FindExSearchLimitToDevices ||
+        dwAdditionalFlags & ~FIND_FIRST_EX_CASE_SENSITIVE /* only supported flag for now */)
+    {
+        SetLastError(fSearchOp == FindExSearchLimitToDevices
+                                ? ERROR_NOT_SUPPORTED
+                                : ERROR_INVALID_PARAMETER);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    lpFileNameW = Basep8BitStringToStaticUnicodeString(lpFileName);
+    if (!lpFileNameW) return INVALID_HANDLE_VALUE;
+
+    hSearch = FindFirstFileExW(lpFileNameW->Buffer,
+                               fInfoLevelId,
+                               &FindFileDataW,
+                               fSearchOp,
+                               lpSearchFilter,
+                               dwAdditionalFlags);
+    if (hSearch == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
+
+    RtlCopyMemory(lpFindFileDataA,
+                  &FindFileDataW,
+                  FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
+
+    RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
+    Ansi.Buffer = lpFindFileDataA->cFileName;
+    Ansi.Length = 0;
+    Ansi.MaximumLength = sizeof(lpFindFileDataA->cFileName);
+    Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+    if (!NT_SUCCESS(Status))
+    {
+        FindClose(hSearch);
+        BaseSetLastNTError(Status);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    if (fInfoLevelId != FindExInfoBasic)
+    {
+        RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
+        Ansi.Buffer = lpFindFileDataA->cAlternateFileName;
+        Ansi.Length = 0;
+        Ansi.MaximumLength = sizeof(lpFindFileDataA->cAlternateFileName);
+        Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
+        if (!NT_SUCCESS(Status))
+        {
+            FindClose(hSearch);
+            BaseSetLastNTError(Status);
+            return INVALID_HANDLE_VALUE;
+        }
+    }
+    else
+    {
+        lpFindFileDataA->cAlternateFileName[0] = ANSI_NULL;
+    }
+
+    return hSearch;
+}
+
+
+/*
+ * @unimplemented
+ */
+HANDLE
+WINAPI
+FindFirstFileExW(IN LPCWSTR lpFileName,
+                 IN FINDEX_INFO_LEVELS fInfoLevelId,
+                 OUT LPVOID lpFindFileData,
+                 IN FINDEX_SEARCH_OPS fSearchOp,
+                 LPVOID lpSearchFilter,
+                 IN DWORD dwAdditionalFlags)
+{
+    TRACE("FindFirstFileExW(lpFileName %S)\n", lpFileName);
+
+    if ((fInfoLevelId != FindExInfoStandard && fInfoLevelId != FindExInfoBasic) ||
+        fSearchOp == FindExSearchLimitToDevices ||
+        dwAdditionalFlags & ~FIND_FIRST_EX_CASE_SENSITIVE /* only supported flag for now */)
+    {
+        SetLastError(fSearchOp == FindExSearchLimitToDevices
+                                ? ERROR_NOT_SUPPORTED
+                                : ERROR_INVALID_PARAMETER);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    if (fSearchOp == FindExSearchNameMatch ||
+        fSearchOp == FindExSearchLimitToDirectories)
+    {
+        LPWIN32_FIND_DATAW Win32FindData = (LPWIN32_FIND_DATAW)lpFindFileData;
+        PFIND_DATA_HANDLE FindDataHandle;
+        PFIND_FILE_DATA FindFileData;
+
+        UNICODE_STRING NtPath, FilePattern, FileName;
+        PWSTR NtPathBuffer;
+        RTL_RELATIVE_NAME_U RelativePath;
+        ULONG DeviceNameInfo = 0;
+
+        NTSTATUS Status;
+        OBJECT_ATTRIBUTES ObjectAttributes;
+        IO_STATUS_BLOCK IoStatusBlock;
+        HANDLE hDirectory = NULL;
+
+        BOOLEAN HadADot = FALSE;
+
+        /*
+         * May represent many FILE_BOTH_DIR_INFORMATION
+         * or many FILE_FULL_DIR_INFORMATION structures.
+         */
+        BYTE DirectoryInfo[FIND_DATA_SIZE];
+        DIR_INFORMATION DirInfo = {&DirectoryInfo};
+
+        /* The search filter is always unused */
+        if (lpSearchFilter)
+        {
+            SetLastError(ERROR_INVALID_PARAMETER);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        RtlInitUnicodeString(&FileName, lpFileName);
+        if (FileName.Length != 0 && FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] == L'.')
+        {
+            HadADot = TRUE;
+        }
+
+        if (!RtlDosPathNameToNtPathName_U(lpFileName,
+                                          &NtPath,
+                                          (PCWSTR*)&FilePattern.Buffer,
+                                          &RelativePath))
+        {
+            SetLastError(ERROR_PATH_NOT_FOUND);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        DPRINT("lpFileName = '%S'\n", lpFileName);
+        DPRINT("FilePattern.Buffer = '%S'\n", FilePattern.Buffer);
+        DPRINT("RelativePath.RelativeName = '%wZ'\n", &RelativePath.RelativeName);
+        DPRINT("NtPath.Buffer = '%S'\n", NtPath.Buffer);
+        DPRINT("NtPath - Before = '%wZ'\n", &NtPath);
+
+        /* Save the buffer pointer for later, we need to free it! */
+        NtPathBuffer = NtPath.Buffer;
+
+        /*
+         * Contrary to what Windows does, check NOW whether or not
+         * lpFileName is a DOS driver. Therefore we don't have to
+         * write broken code to check that.
+         */
+        if (!FilePattern.Buffer || !*FilePattern.Buffer)
+        {
+            /* No file pattern specified, or DOS device */
+
+            DeviceNameInfo = RtlIsDosDeviceName_U(lpFileName);
+            if (DeviceNameInfo != 0)
+            {
+                RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
+
+                /* OK, it's really a DOS device */
+                CopyDeviceFindData(Win32FindData, lpFileName, DeviceNameInfo);
+                return FIND_DEVICE_HANDLE;
+            }
+        }
+
+        /* A file pattern was specified, or it was not a DOS device */
+
+        /* If there is a file pattern then determine its length */
+        if (FilePattern.Buffer != NULL)
+        {
+            FilePattern.Length = NtPath.Length -
+                (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)NtPath.Buffer);
+        }
+        else
+        {
+            FilePattern.Length = 0;
+        }
+        FilePattern.MaximumLength = FilePattern.Length;
+
+        if (RelativePath.RelativeName.Length != 0 &&
+            RelativePath.RelativeName.Buffer != FilePattern.Buffer)
+        {
+            if (FilePattern.Buffer != NULL)
+            {
+                /* This is a relative path to RelativePath.ContainingDirectory, adjust NtPath! */
+                NtPath.Length = NtPath.MaximumLength =
+                    (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)RelativePath.RelativeName.Buffer);
+                NtPath.Buffer = RelativePath.RelativeName.Buffer;
+            }
+        }
+        else
+        {
+            /* This is an absolute path, NtPath receives the full path */
+            RelativePath.ContainingDirectory = NULL;
+            if (FilePattern.Buffer != NULL)
+            {
+                NtPath.Length = NtPath.MaximumLength =
+                    (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)NtPath.Buffer);
+            }
+        }
+
+        DPRINT("NtPath - After = '%wZ'\n", &NtPath);
+        DPRINT("FilePattern = '%wZ'\n", &FilePattern);
+        DPRINT("RelativeTo = 0x%p\n", RelativePath.ContainingDirectory);
+
+        InitializeObjectAttributes(&ObjectAttributes,
+                                   &NtPath,
+                                   (dwAdditionalFlags & FIND_FIRST_EX_CASE_SENSITIVE) ? 0 : OBJ_CASE_INSENSITIVE,
+                                   RelativePath.ContainingDirectory,
+                                   NULL);
+
+        Status = NtOpenFile(&hDirectory,
+                            FILE_LIST_DIRECTORY | SYNCHRONIZE,
+                            &ObjectAttributes,
+                            &IoStatusBlock,
+                            FILE_SHARE_READ | FILE_SHARE_WRITE,
+                            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
+
+        if (!NT_SUCCESS(Status))
+        {
+            RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
+
+            /* Adjust the last error codes */
+            if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
+                Status = STATUS_OBJECT_PATH_NOT_FOUND;
+            else if (Status == STATUS_OBJECT_TYPE_MISMATCH)
+                Status = STATUS_OBJECT_PATH_NOT_FOUND;
+
+            BaseSetLastNTError(Status);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        /*
+         * Fail if there is not any file pattern,
+         * since we are not looking for a device.
+         */
+        if (FilePattern.Length == 0)
+        {
+            NtClose(hDirectory);
+            RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
+
+            SetLastError(ERROR_FILE_NOT_FOUND);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        /* Change pattern: "*.*" --> "*" */
+        if (FilePattern.Length == 6 &&
+            RtlCompareMemory(FilePattern.Buffer, L"*.*", 6) == 6)
+        {
+            FilePattern.Length = 2;
+        }
+        else
+        {
+            /* Translate wildcard from "real" world to DOS world for lower interpretation */
+            USHORT PatternIndex = 0;
+            while (PatternIndex < FilePattern.Length / sizeof(WCHAR))
+            {
+                if (PatternIndex > 0)
+                {
+                    if (FilePattern.Buffer[PatternIndex] == L'.' &&
+                        FilePattern.Buffer[PatternIndex - 1] == L'*')
+                    {
+                        FilePattern.Buffer[PatternIndex - 1] = L'<';
+                    }
+                }
+
+                if (FilePattern.Buffer[PatternIndex] == L'?')
+                {
+                    FilePattern.Buffer[PatternIndex] = L'>';
+                    if (PatternIndex > 0)
+                    {
+                        if (FilePattern.Buffer[PatternIndex - 1] == L'.')
+                        {
+                            FilePattern.Buffer[PatternIndex - 1] = L'\"';
+                        }
+                    }
+                }
+                else if (FilePattern.Buffer[PatternIndex] == L'*')
+                {
+                    if (PatternIndex > 0)
+                    {
+                        if (FilePattern.Buffer[PatternIndex - 1] == L'.')
+                        {
+                            FilePattern.Buffer[PatternIndex - 1] = L'\"';
+                        }
+                    }
+                }
+
+                PatternIndex++;
+            }
+
+            /* Handle partial wc if our last dot was eaten */
+            if (HadADot)
+            {
+                if (FilePattern.Buffer[FilePattern.Length / sizeof(WCHAR) - 1] == L'*')
+                {
+                    FilePattern.Buffer[FilePattern.Length / sizeof(WCHAR) - 1] = L'<';
+                }
+            }
+        }
+
+        Status = NtQueryDirectoryFile(hDirectory,
+                                      NULL, NULL, NULL,
+                                      &IoStatusBlock,
+                                      DirInfo.DirInfo, // == &DirectoryInfo
+                                      sizeof(DirectoryInfo),
+                                      (fInfoLevelId == FindExInfoStandard
+                                                     ? FileBothDirectoryInformation
+                                                     : FileFullDirectoryInformation),
+                                      TRUE, /* Return a single entry */
+                                      &FilePattern,
+                                      TRUE);
+
+        RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
+
+        if (!NT_SUCCESS(Status))
+        {
+            NtClose(hDirectory);
+            BaseSetLastNTError(Status);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        ASSERT(DirInfo.FullDirInfo->NextEntryOffset == 0);
+
+        /* Return the information */
+        CopyFindData(Win32FindData, fInfoLevelId, DirInfo);
+
+        /*
+         * Initialization of the search handle.
+         */
+        FindDataHandle = RtlAllocateHeap(RtlGetProcessHeap(),
+                                         HEAP_ZERO_MEMORY,
+                                         sizeof(FIND_DATA_HANDLE) +
+                                             sizeof(FIND_FILE_DATA));
+        if (!FindDataHandle)
+        {
+            NtClose(hDirectory);
+            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        FindDataHandle->Type = FindFile;
+        FindDataHandle->u.FindFileData = (PFIND_FILE_DATA)(FindDataHandle + 1);
+        FindFileData = FindDataHandle->u.FindFileData;
+
+        FindFileData->Handle = hDirectory;
+        FindFileData->InfoLevel = fInfoLevelId;
+        FindFileData->SearchOp = fSearchOp;
+        FindFileData->HasMoreData = FALSE;
+        FindFileData->NextDirInfo.DirInfo = NULL;
+
+        /* The critical section must always be initialized */
+        Status = RtlInitializeCriticalSection(&FindDataHandle->Lock);
+        if (!NT_SUCCESS(Status))
+        {
+            NtClose(hDirectory);
+            RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
+
+            BaseSetLastNTError(Status);
+            return INVALID_HANDLE_VALUE;
+        }
+
+        return (HANDLE)FindDataHandle;
+    }
+    else
+    {
+        SetLastError(ERROR_NOT_SUPPORTED);
+        return INVALID_HANDLE_VALUE;
+    }
+}
+
+
+/*
+ * @implemented
+ */
+HANDLE
+WINAPI
+FindFirstStreamW(IN LPCWSTR lpFileName,
+                 IN STREAM_INFO_LEVELS InfoLevel,
+                 OUT LPVOID lpFindStreamData,
+                 IN DWORD dwFlags)
+{
+    PFIND_DATA_HANDLE FindDataHandle = NULL;
+    PFIND_STREAM_DATA FindStreamData;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    IO_STATUS_BLOCK IoStatusBlock;
+    UNICODE_STRING NtFilePath;
+    HANDLE FileHandle = NULL;
+    NTSTATUS Status;
+    ULONG BufferSize = 0;
+
+    if (dwFlags != 0 || InfoLevel != FindStreamInfoStandard ||
+        lpFindStreamData == NULL)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /* Validate and translate the filename */
+    if (!RtlDosPathNameToNtPathName_U(lpFileName,
+                                      &NtFilePath,
+                                      NULL, NULL))
+    {
+        SetLastError(ERROR_PATH_NOT_FOUND);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /* Open the file */
+    InitializeObjectAttributes(&ObjectAttributes,
+                               &NtFilePath,
+                               OBJ_CASE_INSENSITIVE,
+                               NULL,
+                               NULL);
+
+    Status = NtCreateFile(&FileHandle,
+                          0,
+                          &ObjectAttributes,
+                          &IoStatusBlock,
+                          NULL, 0,
+                          FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+                          FILE_OPEN,
+                          0, NULL, 0);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    /*
+     * Initialization of the search handle.
+     */
+    FindDataHandle = RtlAllocateHeap(RtlGetProcessHeap(),
+                                     HEAP_ZERO_MEMORY,
+                                     sizeof(FIND_DATA_HANDLE) +
+                                         sizeof(FIND_STREAM_DATA));
+    if (!FindDataHandle)
+    {
+        Status = STATUS_NO_MEMORY;
+        goto Cleanup;
+    }
+
+    FindDataHandle->Type = FindStream;
+    FindDataHandle->u.FindStreamData = (PFIND_STREAM_DATA)(FindDataHandle + 1);
+    FindStreamData = FindDataHandle->u.FindStreamData;
+
+    FindStreamData->InfoLevel = InfoLevel;
+    FindStreamData->FileStreamInfo = NULL;
+    FindStreamData->CurrentInfo = NULL;
+
+    /* The critical section must always be initialized */
+    Status = RtlInitializeCriticalSection(&FindDataHandle->Lock);
+    if (!NT_SUCCESS(Status))
+    {
+        RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
+        goto Cleanup;
+    }
+
+    /* Capture all information about the streams */
+    do
+    {
+        BufferSize += 0x1000;
+
+        if (FindStreamData->FileStreamInfo == NULL)
+        {
+            FindStreamData->FileStreamInfo = RtlAllocateHeap(RtlGetProcessHeap(),
+                                                              HEAP_ZERO_MEMORY,
+                                                              BufferSize);
+            if (FindStreamData->FileStreamInfo == NULL)
+            {
+                Status = STATUS_NO_MEMORY;
+                break;
+            }
+        }
+        else
+        {
+            PFILE_STREAM_INFORMATION pfsi;
+
+            pfsi = RtlReAllocateHeap(RtlGetProcessHeap(),
+                                     0, // HEAP_ZERO_MEMORY,
+                                     FindStreamData->FileStreamInfo,
+                                     BufferSize);
+            if (pfsi == NULL)
+            {
+                Status = STATUS_NO_MEMORY;
+                break;
+            }
+
+            FindStreamData->FileStreamInfo = pfsi;
+        }
+
+        Status = NtQueryInformationFile(FileHandle,
+                                        &IoStatusBlock,
+                                        FindStreamData->FileStreamInfo,
+                                        BufferSize,
+                                        FileStreamInformation);
+
+    } while (Status == STATUS_BUFFER_TOO_SMALL);
+
+    if (NT_SUCCESS(Status))
+    {
+        /* Select the first stream and return the information */
+        FindStreamData->CurrentInfo = FindStreamData->FileStreamInfo;
+        CopyStreamData(FindStreamData, lpFindStreamData);
+
+        /* All done */
+        Status = STATUS_SUCCESS;
+    }
+    else
+    {
+        if (FindStreamData->FileStreamInfo)
+        {
+            RtlFreeHeap(RtlGetProcessHeap(), 0, FindStreamData->FileStreamInfo);
+        }
+
+        RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
+    }
+
+Cleanup:
+    if (FileHandle) NtClose(FileHandle);
+
+    RtlFreeHeap(RtlGetProcessHeap(), 0, NtFilePath.Buffer);
+
+    if (NT_SUCCESS(Status))
+    {
+        return (HANDLE)FindDataHandle;
+    }
+    else
+    {
+        BaseSetLastNTError(Status);
+        return INVALID_HANDLE_VALUE;
+    }
+}
+
+
+/*
+ * @implemented
+ */
+BOOL
+WINAPI
+FindNextStreamW(IN HANDLE hFindStream,
+                OUT LPVOID lpFindStreamData)
+{
+    PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindStream;
+    PFIND_STREAM_DATA FindStreamData;
+
+    if (hFindStream == NULL || hFindStream == INVALID_HANDLE_VALUE ||
+        FindDataHandle->Type != FindStream)
+    {
+        SetLastError(ERROR_INVALID_HANDLE);
+        return FALSE;
+    }
+
+    RtlEnterCriticalSection(&FindDataHandle->Lock);
+
+    FindStreamData = FindDataHandle->u.FindStreamData;
+
+    /* Select next stream if possible */
+    if (FindStreamData->CurrentInfo->NextEntryOffset != 0)
+    {
+        FindStreamData->CurrentInfo = (PFILE_STREAM_INFORMATION)((ULONG_PTR)FindStreamData->FileStreamInfo +
+                                                                 FindStreamData->CurrentInfo->NextEntryOffset);
+
+        /* Return the information */
+        CopyStreamData(FindStreamData, lpFindStreamData);
+
+        RtlLeaveCriticalSection(&FindDataHandle->Lock);
+        return TRUE;
+    }
+    else
+    {
+        RtlLeaveCriticalSection(&FindDataHandle->Lock);
+
+        SetLastError(ERROR_HANDLE_EOF);
+        return FALSE;
+    }
+}
+
+/* EOF */