xref: /reactos/dll/win32/kernel32/client/file/find.c (revision 3c5a56ed)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/file/find.c
5  * PURPOSE:         Find functions
6  * PROGRAMMERS:     Ariadne (ariadne@xs4all.nl)
7  *                  Pierre Schweitzer (pierre.schweitzer@reactos.org)
8  *                  Hermes Belusca-Maito
9  */
10 
11 /* INCLUDES *******************************************************************/
12 
13 #include <k32.h>
14 #include <ntstrsafe.h>
15 
16 #define NDEBUG
17 #include <debug.h>
18 DEBUG_CHANNEL(kernel32file);
19 
20 
21 /* TYPES **********************************************************************/
22 
23 #define FIND_DATA_SIZE      0x4000
24 #define FIND_DEVICE_HANDLE  ((HANDLE)0x1)
25 
26 typedef enum _FIND_DATA_TYPE
27 {
28     FindFile   = 1,
29     FindStream = 2
30 } FIND_DATA_TYPE;
31 
32 /*
33  * FILE_FULL_DIR_INFORMATION and FILE_BOTH_DIR_INFORMATION structures layout.
34  *
35  *
36  *  struct FILE_FULL_DIR_INFORMATION   |   struct FILE_BOTH_DIR_INFORMATION
37  * ------------------------------------+---------------------------------------
38  *     ULONG NextEntryOffset;          |       ULONG NextEntryOffset;
39  *     ULONG FileIndex;                |       ULONG FileIndex;
40  *     LARGE_INTEGER CreationTime;     |       LARGE_INTEGER CreationTime;
41  *     LARGE_INTEGER LastAccessTime;   |       LARGE_INTEGER LastAccessTime;
42  *     LARGE_INTEGER LastWriteTime;    |       LARGE_INTEGER LastWriteTime;
43  *     LARGE_INTEGER ChangeTime;       |       LARGE_INTEGER ChangeTime;
44  *     LARGE_INTEGER EndOfFile;        |       LARGE_INTEGER EndOfFile;
45  *     LARGE_INTEGER AllocationSize;   |       LARGE_INTEGER AllocationSize;
46  *     ULONG FileAttributes;           |       ULONG FileAttributes;
47  *     ULONG FileNameLength;           |       ULONG FileNameLength;
48  *     ULONG EaSize;                   |       ULONG EaSize;
49  * ------------------------------------+---------------------------------------
50  *     WCHAR FileName[1];              |       CCHAR ShortNameLength;
51  *                                     |       WCHAR ShortName[12];
52  *                                     |       WCHAR FileName[1];
53  *
54  * Therefore we can use pointers to FILE_FULL_DIR_INFORMATION when one doesn't
55  * want to refer to the ShortName* fields and FileName (useful for implementing
56  * the FindExInfoBasic functionality for FindFirstFileEx), however a cast to
57  * FILE_BOTH_DIR_INFORMATION is required when one wants to use FileName and
58  * ShortName* fields (needed for the FindExInfoStandard functionality).
59  *
60  */
61 typedef union _DIR_INFORMATION
62 {
63     PVOID DirInfo;
64     PFILE_FULL_DIR_INFORMATION FullDirInfo;
65     PFILE_BOTH_DIR_INFORMATION BothDirInfo;
66 } DIR_INFORMATION;
67 
68 typedef struct _FIND_FILE_DATA
69 {
70     HANDLE Handle;
71     FINDEX_INFO_LEVELS InfoLevel;
72     FINDEX_SEARCH_OPS SearchOp;
73 
74     /*
75      * For handling STATUS_BUFFER_OVERFLOW errors emitted by
76      * NtQueryDirectoryFile in the FindNextFile function.
77      */
78     BOOLEAN HasMoreData;
79 
80     /*
81      * "Pointer" to the next file info structure in the buffer.
82      * The type is defined by the 'InfoLevel' parameter.
83      */
84     DIR_INFORMATION NextDirInfo;
85 
86     BYTE Buffer[FIND_DATA_SIZE];
87 } FIND_FILE_DATA, *PFIND_FILE_DATA;
88 
89 typedef struct _FIND_STREAM_DATA
90 {
91     STREAM_INFO_LEVELS InfoLevel;
92     PFILE_STREAM_INFORMATION FileStreamInfo;
93     PFILE_STREAM_INFORMATION CurrentInfo;
94 } FIND_STREAM_DATA, *PFIND_STREAM_DATA;
95 
96 typedef struct _FIND_DATA_HANDLE
97 {
98     FIND_DATA_TYPE Type;
99     RTL_CRITICAL_SECTION Lock;
100 
101     /*
102      * Pointer to the following finding data, located at
103      * (this + 1). The type is defined by the 'Type' parameter.
104      */
105     union
106     {
107         PFIND_FILE_DATA FindFileData;
108         PFIND_STREAM_DATA FindStreamData;
109     } u;
110 
111 } FIND_DATA_HANDLE, *PFIND_DATA_HANDLE;
112 
113 
114 /* PRIVATE FUNCTIONS **********************************************************/
115 
116 static VOID
117 CopyDeviceFindData(OUT LPWIN32_FIND_DATAW lpFindFileData,
118                    IN LPCWSTR lpFileName,
119                    IN ULONG DeviceNameInfo)
120 {
121     LPCWSTR DeviceName;
122     SIZE_T Length;
123 
124     _SEH2_TRY
125     {
126         /* DeviceNameInfo == { USHORT Offset; USHORT Length } */
127         Length     =  (SIZE_T)(DeviceNameInfo & 0xFFFF);
128         DeviceName = (LPCWSTR)((ULONG_PTR)lpFileName + ((DeviceNameInfo >> 16) & 0xFFFF));
129 
130         /* Return the data */
131         RtlZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
132         lpFindFileData->dwFileAttributes = FILE_ATTRIBUTE_ARCHIVE;
133         RtlStringCbCopyNW(lpFindFileData->cFileName,
134                           sizeof(lpFindFileData->cFileName),
135                           DeviceName, Length);
136     }
137     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
138     {
139     }
140     _SEH2_END;
141 
142     return;
143 }
144 
145 static VOID
146 CopyFindData(OUT LPWIN32_FIND_DATAW lpFindFileData,
147              IN FINDEX_INFO_LEVELS fInfoLevelId,
148              IN DIR_INFORMATION DirInfo)
149 {
150 #define ULARGE_INTEGER_2_FILETIME(ft, ul) \
151 do { \
152     (ft).dwHighDateTime = (ul).u.HighPart; \
153     (ft).dwLowDateTime  = (ul).u.LowPart ; \
154 } while(0)
155 
156     _SEH2_TRY
157     {
158         RtlZeroMemory(lpFindFileData, sizeof(*lpFindFileData));
159 
160         lpFindFileData->dwFileAttributes = DirInfo.FullDirInfo->FileAttributes;
161 
162         ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftCreationTime, DirInfo.FullDirInfo->CreationTime);
163         ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftLastAccessTime, DirInfo.FullDirInfo->LastAccessTime);
164         ULARGE_INTEGER_2_FILETIME(lpFindFileData->ftLastWriteTime, DirInfo.FullDirInfo->LastWriteTime);
165 
166         lpFindFileData->nFileSizeHigh = DirInfo.FullDirInfo->EndOfFile.u.HighPart;
167         lpFindFileData->nFileSizeLow = DirInfo.FullDirInfo->EndOfFile.u.LowPart;
168 
169         /* dwReserved0 contains the NTFS reparse point tag, if any. */
170         if (DirInfo.FullDirInfo->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
171             lpFindFileData->dwReserved0 = DirInfo.FullDirInfo->EaSize;
172         else
173             lpFindFileData->dwReserved0 = 0;
174 
175         /* Unused dwReserved1 field */
176         lpFindFileData->dwReserved1 = 0;
177 
178         if (fInfoLevelId == FindExInfoStandard)
179         {
180             RtlStringCbCopyNW(lpFindFileData->cFileName,
181                               sizeof(lpFindFileData->cFileName),
182                               DirInfo.BothDirInfo->FileName,
183                               DirInfo.BothDirInfo->FileNameLength);
184 
185             RtlStringCbCopyNW(lpFindFileData->cAlternateFileName,
186                               sizeof(lpFindFileData->cAlternateFileName),
187                               DirInfo.BothDirInfo->ShortName,
188                               DirInfo.BothDirInfo->ShortNameLength);
189         }
190         else if (fInfoLevelId == FindExInfoBasic)
191         {
192             RtlStringCbCopyNW(lpFindFileData->cFileName,
193                               sizeof(lpFindFileData->cFileName),
194                               DirInfo.FullDirInfo->FileName,
195                               DirInfo.FullDirInfo->FileNameLength);
196 
197             lpFindFileData->cAlternateFileName[0] = UNICODE_NULL;
198         }
199         else
200         {
201             /* Invalid InfoLevelId */
202             ASSERT(FALSE);
203         }
204     }
205     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
206     {
207     }
208     _SEH2_END;
209 
210     return;
211 }
212 
213 static VOID
214 CopyStreamData(IN OUT PFIND_STREAM_DATA FindStreamData,
215                OUT PWIN32_FIND_STREAM_DATA lpFindStreamData)
216 {
217     _SEH2_TRY
218     {
219         ASSERT(FindStreamData->CurrentInfo);
220 
221         switch (FindStreamData->InfoLevel)
222         {
223             case FindStreamInfoStandard:
224             {
225                 ULONG StreamNameLen = min(FindStreamData->CurrentInfo->StreamNameLength,
226                                           sizeof(lpFindStreamData->cStreamName) - sizeof(WCHAR));
227 
228                 RtlZeroMemory(lpFindStreamData, sizeof(*lpFindStreamData));
229 
230                 lpFindStreamData->StreamSize.QuadPart = FindStreamData->CurrentInfo->StreamSize.QuadPart;
231                 RtlCopyMemory(lpFindStreamData->cStreamName,
232                               FindStreamData->CurrentInfo->StreamName,
233                               StreamNameLen);
234                 lpFindStreamData->cStreamName[StreamNameLen / sizeof(WCHAR)] = UNICODE_NULL;
235 
236                 break;
237             }
238 
239             default:
240             {
241                 /* Invalid InfoLevel */
242                 ASSERT(FALSE);
243                 break;
244             }
245         }
246     }
247     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
248     {
249     }
250     _SEH2_END;
251 
252     return;
253 }
254 
255 
256 /* PUBLIC FUNCTIONS ***********************************************************/
257 
258 /*
259  * @implemented
260  */
261 HANDLE
262 WINAPI
263 FindFirstFileA(IN LPCSTR lpFileName,
264                OUT LPWIN32_FIND_DATAA lpFindFileData)
265 {
266     HANDLE hSearch;
267     NTSTATUS Status;
268     ANSI_STRING Ansi;
269     UNICODE_STRING UTF8;
270     PUNICODE_STRING lpFileNameW;
271     WIN32_FIND_DATAW FindFileDataW;
272 
273     lpFileNameW = Basep8BitStringToStaticUnicodeString(lpFileName);
274     if (!lpFileNameW) return INVALID_HANDLE_VALUE;
275 
276     hSearch = FindFirstFileExW(lpFileNameW->Buffer,
277                                FindExInfoStandard,
278                                &FindFileDataW,
279                                FindExSearchNameMatch,
280                                NULL, 0);
281     if (hSearch == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
282 
283     RtlCopyMemory(lpFindFileData,
284                   &FindFileDataW,
285                   FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
286 
287     RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
288     Ansi.Buffer = lpFindFileData->cFileName;
289     Ansi.Length = 0;
290     Ansi.MaximumLength = sizeof(lpFindFileData->cFileName);
291     Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
292     if (!NT_SUCCESS(Status))
293     {
294         FindClose(hSearch);
295         BaseSetLastNTError(Status);
296         return INVALID_HANDLE_VALUE;
297     }
298 
299     RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
300     Ansi.Buffer = lpFindFileData->cAlternateFileName;
301     Ansi.Length = 0;
302     Ansi.MaximumLength = sizeof(lpFindFileData->cAlternateFileName);
303     Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
304     if (!NT_SUCCESS(Status))
305     {
306         FindClose(hSearch);
307         BaseSetLastNTError(Status);
308         return INVALID_HANDLE_VALUE;
309     }
310 
311     return hSearch;
312 }
313 
314 
315 /*
316  * @implemented
317  */
318 HANDLE
319 WINAPI
320 FindFirstFileW(IN LPCWSTR lpFileName,
321                OUT LPWIN32_FIND_DATAW lpFindFileData)
322 {
323     return FindFirstFileExW(lpFileName,
324                             FindExInfoStandard,
325                             lpFindFileData,
326                             FindExSearchNameMatch,
327                             NULL, 0);
328 }
329 
330 
331 /*
332  * @implemented
333  */
334 BOOL
335 WINAPI
336 FindNextFileA(IN HANDLE hFindFile,
337               OUT LPWIN32_FIND_DATAA lpFindFileData)
338 {
339     NTSTATUS Status;
340     ANSI_STRING Ansi;
341     UNICODE_STRING UTF8;
342     WIN32_FIND_DATAW FindFileDataW;
343 
344     if (!FindNextFileW(hFindFile, &FindFileDataW))
345         return FALSE;
346 
347     RtlCopyMemory(lpFindFileData,
348                   &FindFileDataW,
349                   FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
350 
351     RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
352     Ansi.Buffer = lpFindFileData->cFileName;
353     Ansi.Length = 0;
354     Ansi.MaximumLength = sizeof(lpFindFileData->cFileName);
355     Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
356     if (!NT_SUCCESS(Status))
357     {
358         BaseSetLastNTError(Status);
359         return FALSE;
360     }
361 
362     RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
363     Ansi.Buffer = lpFindFileData->cAlternateFileName;
364     Ansi.Length = 0;
365     Ansi.MaximumLength = sizeof(lpFindFileData->cAlternateFileName);
366     Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
367     if (!NT_SUCCESS(Status))
368     {
369         BaseSetLastNTError(Status);
370         return FALSE;
371     }
372 
373     return TRUE;
374 }
375 
376 
377 /*
378  * @implemented
379  */
380 BOOL
381 WINAPI
382 FindNextFileW(IN HANDLE hFindFile,
383               OUT LPWIN32_FIND_DATAW lpFindFileData)
384 {
385     NTSTATUS Status = STATUS_SUCCESS;
386     DIR_INFORMATION FoundFile = {NULL};
387 
388     TRACE("FindNextFileW(%p, 0x%p)\n", hFindFile, lpFindFileData);
389 
390     if (hFindFile != FIND_DEVICE_HANDLE)
391     {
392         PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindFile;
393         PFIND_FILE_DATA FindFileData;
394         FINDEX_INFO_LEVELS InfoLevel;
395         IO_STATUS_BLOCK IoStatusBlock;
396         DIR_INFORMATION DirInfo = {NULL}, NextDirInfo = {NULL};
397 
398         if (hFindFile == NULL || hFindFile == INVALID_HANDLE_VALUE ||
399             FindDataHandle->Type != FindFile)
400         {
401             SetLastError(ERROR_INVALID_HANDLE);
402             return FALSE;
403         }
404 
405         RtlEnterCriticalSection(&FindDataHandle->Lock);
406 
407         FindFileData = FindDataHandle->u.FindFileData;
408         InfoLevel = FindFileData->InfoLevel;
409 
410         do
411         {
412             if (FindFileData->NextDirInfo.DirInfo == NULL)
413             {
414                 Status = NtQueryDirectoryFile(FindFileData->Handle,
415                                               NULL, NULL, NULL,
416                                               &IoStatusBlock,
417                                               &FindFileData->Buffer,
418                                               sizeof(FindFileData->Buffer),
419                                               (InfoLevel == FindExInfoStandard
420                                                           ? FileBothDirectoryInformation
421                                                           : FileFullDirectoryInformation),
422                                               FALSE,
423                                               NULL, /* Use the file pattern from the first call */
424                                               FALSE);
425                 if (Status == STATUS_BUFFER_OVERFLOW)
426                 {
427                     FindFileData->HasMoreData = TRUE;
428                     Status = STATUS_SUCCESS;
429                 }
430                 else
431                 {
432                     if (!NT_SUCCESS(Status)) break;
433                     FindFileData->HasMoreData = FALSE;
434                 }
435 
436                 FindFileData->NextDirInfo.DirInfo = &FindFileData->Buffer;
437             }
438 
439             DirInfo = FindFileData->NextDirInfo;
440 
441             if (DirInfo.FullDirInfo->NextEntryOffset != 0)
442             {
443                 ULONG_PTR BufferEnd = (ULONG_PTR)&FindFileData->Buffer + sizeof(FindFileData->Buffer);
444                 PWSTR pFileName;
445 
446                 NextDirInfo.DirInfo = FindFileData->NextDirInfo.DirInfo =
447                     (PVOID)((ULONG_PTR)DirInfo.DirInfo + DirInfo.FullDirInfo->NextEntryOffset);
448 
449                 pFileName = (InfoLevel == FindExInfoStandard
450                                         ? NextDirInfo.BothDirInfo->FileName
451                                         : NextDirInfo.FullDirInfo->FileName);
452 
453                 /* Be paranoid and make sure that the next entry is completely there */
454                 if (BufferEnd < (ULONG_PTR)NextDirInfo.DirInfo ||
455                     BufferEnd < (ULONG_PTR)&NextDirInfo.FullDirInfo->FileNameLength + sizeof(NextDirInfo.FullDirInfo->FileNameLength) ||
456                     BufferEnd <= (ULONG_PTR)((ULONG_PTR)pFileName + NextDirInfo.FullDirInfo->FileNameLength))
457                 {
458                     FindFileData->NextDirInfo.DirInfo = NULL;
459                 }
460             }
461             else
462             {
463                 FindFileData->NextDirInfo.DirInfo = NULL;
464             }
465 
466             if ((FindFileData->SearchOp != FindExSearchLimitToDirectories) ||
467                 (DirInfo.FullDirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
468             {
469                 FoundFile = DirInfo;
470             }
471         } while ( FoundFile.DirInfo == NULL && (FindFileData->NextDirInfo.DirInfo || FindFileData->HasMoreData) );
472 
473         if (FoundFile.DirInfo != NULL)
474         {
475             /* Return the information */
476             CopyFindData(lpFindFileData, InfoLevel, FoundFile);
477         }
478 
479         RtlLeaveCriticalSection(&FindDataHandle->Lock);
480     }
481 
482     if (!NT_SUCCESS(Status))
483     {
484         BaseSetLastNTError(Status);
485         return FALSE;
486     }
487     else if (FoundFile.DirInfo == NULL)
488     {
489         SetLastError(ERROR_NO_MORE_FILES);
490         return FALSE;
491     }
492 
493     return TRUE;
494 }
495 
496 
497 /*
498  * @implemented
499  */
500 BOOL
501 WINAPI
502 FindClose(HANDLE hFindFile)
503 {
504     TRACE("FindClose(hFindFile %p)\n", hFindFile);
505 
506     if (hFindFile == FIND_DEVICE_HANDLE)
507         return TRUE;
508 
509     if (!hFindFile || hFindFile == INVALID_HANDLE_VALUE)
510     {
511         SetLastError(ERROR_INVALID_HANDLE);
512         return FALSE;
513     }
514 
515     /* Protect with SEH against closing attempts on invalid handles. */
516     _SEH2_TRY
517     {
518         PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindFile;
519 
520         switch (FindDataHandle->Type)
521         {
522             case FindFile:
523             {
524                 RtlEnterCriticalSection(&FindDataHandle->Lock);
525                 NtClose(FindDataHandle->u.FindFileData->Handle);
526                 RtlLeaveCriticalSection(&FindDataHandle->Lock);
527                 RtlDeleteCriticalSection(&FindDataHandle->Lock);
528                 break;
529             }
530 
531             case FindStream:
532             {
533                 RtlEnterCriticalSection(&FindDataHandle->Lock);
534                 if (FindDataHandle->u.FindStreamData->FileStreamInfo != NULL)
535                 {
536                     RtlFreeHeap(RtlGetProcessHeap(), 0,
537                                 FindDataHandle->u.FindStreamData->FileStreamInfo);
538                 }
539                 RtlLeaveCriticalSection(&FindDataHandle->Lock);
540                 RtlDeleteCriticalSection(&FindDataHandle->Lock);
541                 break;
542             }
543 
544             default:
545             {
546                 SetLastError(ERROR_INVALID_HANDLE);
547                 _SEH2_YIELD(return FALSE);
548             }
549         }
550 
551         RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
552     }
553     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
554     {
555         BaseSetLastNTError(_SEH2_GetExceptionCode());
556         _SEH2_YIELD(return FALSE);
557     }
558     _SEH2_END;
559 
560     return TRUE;
561 }
562 
563 
564 /*
565  * @unimplemented
566  */
567 HANDLE
568 WINAPI
569 FindFirstFileExA(IN LPCSTR lpFileName,
570                  IN FINDEX_INFO_LEVELS fInfoLevelId,
571                  OUT LPVOID lpFindFileData,
572                  IN FINDEX_SEARCH_OPS fSearchOp,
573                  LPVOID lpSearchFilter,
574                  IN DWORD dwAdditionalFlags)
575 {
576     HANDLE hSearch;
577     NTSTATUS Status;
578     ANSI_STRING Ansi;
579     UNICODE_STRING UTF8;
580     PUNICODE_STRING lpFileNameW;
581     WIN32_FIND_DATAW FindFileDataW;
582     LPWIN32_FIND_DATAA lpFindFileDataA = (LPWIN32_FIND_DATAA)lpFindFileData;
583 
584     if ((fInfoLevelId != FindExInfoStandard && fInfoLevelId != FindExInfoBasic) ||
585         fSearchOp == FindExSearchLimitToDevices ||
586         dwAdditionalFlags & ~FIND_FIRST_EX_CASE_SENSITIVE /* only supported flag for now */)
587     {
588         SetLastError(fSearchOp == FindExSearchLimitToDevices
589                                 ? ERROR_NOT_SUPPORTED
590                                 : ERROR_INVALID_PARAMETER);
591         return INVALID_HANDLE_VALUE;
592     }
593 
594     lpFileNameW = Basep8BitStringToStaticUnicodeString(lpFileName);
595     if (!lpFileNameW) return INVALID_HANDLE_VALUE;
596 
597     hSearch = FindFirstFileExW(lpFileNameW->Buffer,
598                                fInfoLevelId,
599                                &FindFileDataW,
600                                fSearchOp,
601                                lpSearchFilter,
602                                dwAdditionalFlags);
603     if (hSearch == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE;
604 
605     RtlCopyMemory(lpFindFileDataA,
606                   &FindFileDataW,
607                   FIELD_OFFSET(WIN32_FIND_DATAA, cFileName));
608 
609     RtlInitUnicodeString(&UTF8, FindFileDataW.cFileName);
610     Ansi.Buffer = lpFindFileDataA->cFileName;
611     Ansi.Length = 0;
612     Ansi.MaximumLength = sizeof(lpFindFileDataA->cFileName);
613     Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
614     if (!NT_SUCCESS(Status))
615     {
616         FindClose(hSearch);
617         BaseSetLastNTError(Status);
618         return INVALID_HANDLE_VALUE;
619     }
620 
621     if (fInfoLevelId != FindExInfoBasic)
622     {
623         RtlInitUnicodeString(&UTF8, FindFileDataW.cAlternateFileName);
624         Ansi.Buffer = lpFindFileDataA->cAlternateFileName;
625         Ansi.Length = 0;
626         Ansi.MaximumLength = sizeof(lpFindFileDataA->cAlternateFileName);
627         Status = BasepUnicodeStringTo8BitString(&Ansi, &UTF8, FALSE);
628         if (!NT_SUCCESS(Status))
629         {
630             FindClose(hSearch);
631             BaseSetLastNTError(Status);
632             return INVALID_HANDLE_VALUE;
633         }
634     }
635     else
636     {
637         lpFindFileDataA->cAlternateFileName[0] = ANSI_NULL;
638     }
639 
640     return hSearch;
641 }
642 
643 
644 /*
645  * @unimplemented
646  */
647 HANDLE
648 WINAPI
649 FindFirstFileExW(IN LPCWSTR lpFileName,
650                  IN FINDEX_INFO_LEVELS fInfoLevelId,
651                  OUT LPVOID lpFindFileData,
652                  IN FINDEX_SEARCH_OPS fSearchOp,
653                  LPVOID lpSearchFilter,
654                  IN DWORD dwAdditionalFlags)
655 {
656     TRACE("FindFirstFileExW(lpFileName %S)\n", lpFileName);
657 
658     if ((fInfoLevelId != FindExInfoStandard && fInfoLevelId != FindExInfoBasic) ||
659         fSearchOp == FindExSearchLimitToDevices ||
660         dwAdditionalFlags & ~FIND_FIRST_EX_CASE_SENSITIVE /* only supported flag for now */)
661     {
662         SetLastError(fSearchOp == FindExSearchLimitToDevices
663                                 ? ERROR_NOT_SUPPORTED
664                                 : ERROR_INVALID_PARAMETER);
665         return INVALID_HANDLE_VALUE;
666     }
667 
668     if (fSearchOp == FindExSearchNameMatch ||
669         fSearchOp == FindExSearchLimitToDirectories)
670     {
671         LPWIN32_FIND_DATAW Win32FindData = (LPWIN32_FIND_DATAW)lpFindFileData;
672         PFIND_DATA_HANDLE FindDataHandle;
673         PFIND_FILE_DATA FindFileData;
674 
675         UNICODE_STRING NtPath, FilePattern, FileName;
676         PWSTR NtPathBuffer;
677         RTL_RELATIVE_NAME_U RelativePath;
678         ULONG DeviceNameInfo = 0;
679 
680         NTSTATUS Status;
681         OBJECT_ATTRIBUTES ObjectAttributes;
682         IO_STATUS_BLOCK IoStatusBlock;
683         HANDLE hDirectory = NULL;
684 
685         BOOLEAN HadADot = FALSE;
686 
687         /*
688          * May represent many FILE_BOTH_DIR_INFORMATION
689          * or many FILE_FULL_DIR_INFORMATION structures.
690          * NOTE: NtQueryDirectoryFile requires the buffer to be ULONG-aligned
691          */
692         DECLSPEC_ALIGN(4) BYTE DirectoryInfo[FIND_DATA_SIZE];
693         DIR_INFORMATION DirInfo = { .DirInfo = &DirectoryInfo };
694 
695         /* The search filter is always unused */
696         if (lpSearchFilter)
697         {
698             SetLastError(ERROR_INVALID_PARAMETER);
699             return INVALID_HANDLE_VALUE;
700         }
701 
702         RtlInitUnicodeString(&FileName, lpFileName);
703         if (FileName.Length != 0 && FileName.Buffer[FileName.Length / sizeof(WCHAR) - 1] == L'.')
704         {
705             HadADot = TRUE;
706         }
707 
708         if (!RtlDosPathNameToNtPathName_U(lpFileName,
709                                           &NtPath,
710                                           (PCWSTR*)&FilePattern.Buffer,
711                                           &RelativePath))
712         {
713             SetLastError(ERROR_PATH_NOT_FOUND);
714             return INVALID_HANDLE_VALUE;
715         }
716 
717         DPRINT("lpFileName = '%S'\n", lpFileName);
718         DPRINT("FilePattern.Buffer = '%S'\n", FilePattern.Buffer);
719         DPRINT("RelativePath.RelativeName = '%wZ'\n", &RelativePath.RelativeName);
720         DPRINT("NtPath.Buffer = '%S'\n", NtPath.Buffer);
721         DPRINT("NtPath - Before = '%wZ'\n", &NtPath);
722 
723         /* Save the buffer pointer for later, we need to free it! */
724         NtPathBuffer = NtPath.Buffer;
725 
726         /*
727          * Contrary to what Windows does, check NOW whether or not
728          * lpFileName is a DOS driver. Therefore we don't have to
729          * write broken code to check that.
730          */
731         if (!FilePattern.Buffer || !*FilePattern.Buffer)
732         {
733             /* No file pattern specified, or DOS device */
734 
735             DeviceNameInfo = RtlIsDosDeviceName_U(lpFileName);
736             if (DeviceNameInfo != 0)
737             {
738                 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
739 
740                 /* OK, it's really a DOS device */
741                 CopyDeviceFindData(Win32FindData, lpFileName, DeviceNameInfo);
742                 return FIND_DEVICE_HANDLE;
743             }
744         }
745 
746         /* A file pattern was specified, or it was not a DOS device */
747 
748         /* If there is a file pattern then determine its length */
749         if (FilePattern.Buffer != NULL)
750         {
751             FilePattern.Length = NtPath.Length -
752                 (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)NtPath.Buffer);
753         }
754         else
755         {
756             FilePattern.Length = 0;
757         }
758         FilePattern.MaximumLength = FilePattern.Length;
759 
760         if (RelativePath.RelativeName.Length != 0 &&
761             RelativePath.RelativeName.Buffer != FilePattern.Buffer)
762         {
763             if (FilePattern.Buffer != NULL)
764             {
765                 /* This is a relative path to RelativePath.ContainingDirectory, adjust NtPath! */
766                 NtPath.Length = NtPath.MaximumLength =
767                     (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)RelativePath.RelativeName.Buffer);
768                 NtPath.Buffer = RelativePath.RelativeName.Buffer;
769             }
770         }
771         else
772         {
773             /* This is an absolute path, NtPath receives the full path */
774             RelativePath.ContainingDirectory = NULL;
775             if (FilePattern.Buffer != NULL)
776             {
777                 NtPath.Length = NtPath.MaximumLength =
778                     (USHORT)((ULONG_PTR)FilePattern.Buffer - (ULONG_PTR)NtPath.Buffer);
779             }
780         }
781 
782         DPRINT("NtPath - After = '%wZ'\n", &NtPath);
783         DPRINT("FilePattern = '%wZ'\n", &FilePattern);
784         DPRINT("RelativeTo = 0x%p\n", RelativePath.ContainingDirectory);
785 
786         InitializeObjectAttributes(&ObjectAttributes,
787                                    &NtPath,
788                                    (dwAdditionalFlags & FIND_FIRST_EX_CASE_SENSITIVE) ? 0 : OBJ_CASE_INSENSITIVE,
789                                    RelativePath.ContainingDirectory,
790                                    NULL);
791 
792         Status = NtOpenFile(&hDirectory,
793                             FILE_LIST_DIRECTORY | SYNCHRONIZE,
794                             &ObjectAttributes,
795                             &IoStatusBlock,
796                             FILE_SHARE_READ | FILE_SHARE_WRITE,
797                             FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
798 
799         if (!NT_SUCCESS(Status))
800         {
801             RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
802 
803             /* Adjust the last error codes */
804             if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
805                 Status = STATUS_OBJECT_PATH_NOT_FOUND;
806             else if (Status == STATUS_OBJECT_TYPE_MISMATCH)
807                 Status = STATUS_OBJECT_PATH_NOT_FOUND;
808 
809             BaseSetLastNTError(Status);
810             return INVALID_HANDLE_VALUE;
811         }
812 
813         /*
814          * Fail if there is not any file pattern,
815          * since we are not looking for a device.
816          */
817         if (FilePattern.Length == 0)
818         {
819             NtClose(hDirectory);
820             RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
821 
822             SetLastError(ERROR_FILE_NOT_FOUND);
823             return INVALID_HANDLE_VALUE;
824         }
825 
826         /* Change pattern: "*.*" --> "*" */
827         if (FilePattern.Length == 6 &&
828             RtlCompareMemory(FilePattern.Buffer, L"*.*", 6) == 6)
829         {
830             FilePattern.Length = 2;
831         }
832         else
833         {
834             /* Translate wildcard from "real" world to DOS world for lower interpretation */
835             USHORT PatternIndex = 0;
836             while (PatternIndex < FilePattern.Length / sizeof(WCHAR))
837             {
838                 if (PatternIndex > 0)
839                 {
840                     if (FilePattern.Buffer[PatternIndex] == L'.' &&
841                         FilePattern.Buffer[PatternIndex - 1] == L'*')
842                     {
843                         FilePattern.Buffer[PatternIndex - 1] = L'<';
844                     }
845                 }
846 
847                 if (FilePattern.Buffer[PatternIndex] == L'?')
848                 {
849                     FilePattern.Buffer[PatternIndex] = L'>';
850                     if (PatternIndex > 0)
851                     {
852                         if (FilePattern.Buffer[PatternIndex - 1] == L'.')
853                         {
854                             FilePattern.Buffer[PatternIndex - 1] = L'\"';
855                         }
856                     }
857                 }
858                 else if (FilePattern.Buffer[PatternIndex] == L'*')
859                 {
860                     if (PatternIndex > 0)
861                     {
862                         if (FilePattern.Buffer[PatternIndex - 1] == L'.')
863                         {
864                             FilePattern.Buffer[PatternIndex - 1] = L'\"';
865                         }
866                     }
867                 }
868 
869                 PatternIndex++;
870             }
871 
872             /* Handle partial wc if our last dot was eaten */
873             if (HadADot)
874             {
875                 if (FilePattern.Buffer[FilePattern.Length / sizeof(WCHAR) - 1] == L'*')
876                 {
877                     FilePattern.Buffer[FilePattern.Length / sizeof(WCHAR) - 1] = L'<';
878                 }
879             }
880         }
881 
882         Status = NtQueryDirectoryFile(hDirectory,
883                                       NULL, NULL, NULL,
884                                       &IoStatusBlock,
885                                       DirInfo.DirInfo, // == &DirectoryInfo
886                                       sizeof(DirectoryInfo),
887                                       (fInfoLevelId == FindExInfoStandard
888                                                      ? FileBothDirectoryInformation
889                                                      : FileFullDirectoryInformation),
890                                       TRUE, /* Return a single entry */
891                                       &FilePattern,
892                                       TRUE);
893 
894         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathBuffer);
895 
896         if (!NT_SUCCESS(Status))
897         {
898             NtClose(hDirectory);
899             BaseSetLastNTError(Status);
900             return INVALID_HANDLE_VALUE;
901         }
902 
903         ASSERT(DirInfo.FullDirInfo->NextEntryOffset == 0);
904 
905         /* Return the information */
906         CopyFindData(Win32FindData, fInfoLevelId, DirInfo);
907 
908         /*
909          * Initialization of the search handle.
910          */
911         FindDataHandle = RtlAllocateHeap(RtlGetProcessHeap(),
912                                          HEAP_ZERO_MEMORY,
913                                          sizeof(FIND_DATA_HANDLE) +
914                                              sizeof(FIND_FILE_DATA));
915         if (!FindDataHandle)
916         {
917             NtClose(hDirectory);
918             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
919             return INVALID_HANDLE_VALUE;
920         }
921 
922         FindDataHandle->Type = FindFile;
923         FindDataHandle->u.FindFileData = (PFIND_FILE_DATA)(FindDataHandle + 1);
924         FindFileData = FindDataHandle->u.FindFileData;
925 
926         FindFileData->Handle = hDirectory;
927         FindFileData->InfoLevel = fInfoLevelId;
928         FindFileData->SearchOp = fSearchOp;
929         FindFileData->HasMoreData = FALSE;
930         FindFileData->NextDirInfo.DirInfo = NULL;
931 
932         /* The critical section must always be initialized */
933         Status = RtlInitializeCriticalSection(&FindDataHandle->Lock);
934         if (!NT_SUCCESS(Status))
935         {
936             NtClose(hDirectory);
937             RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
938 
939             BaseSetLastNTError(Status);
940             return INVALID_HANDLE_VALUE;
941         }
942 
943         return (HANDLE)FindDataHandle;
944     }
945     else
946     {
947         SetLastError(ERROR_NOT_SUPPORTED);
948         return INVALID_HANDLE_VALUE;
949     }
950 }
951 
952 
953 /*
954  * @implemented
955  */
956 HANDLE
957 WINAPI
958 FindFirstStreamW(IN LPCWSTR lpFileName,
959                  IN STREAM_INFO_LEVELS InfoLevel,
960                  OUT LPVOID lpFindStreamData,
961                  IN DWORD dwFlags)
962 {
963     PFIND_DATA_HANDLE FindDataHandle = NULL;
964     PFIND_STREAM_DATA FindStreamData;
965     OBJECT_ATTRIBUTES ObjectAttributes;
966     IO_STATUS_BLOCK IoStatusBlock;
967     UNICODE_STRING NtFilePath;
968     HANDLE FileHandle = NULL;
969     NTSTATUS Status;
970     ULONG BufferSize = 0;
971 
972     if (dwFlags != 0 || InfoLevel != FindStreamInfoStandard ||
973         lpFindStreamData == NULL)
974     {
975         SetLastError(ERROR_INVALID_PARAMETER);
976         return INVALID_HANDLE_VALUE;
977     }
978 
979     /* Validate and translate the filename */
980     if (!RtlDosPathNameToNtPathName_U(lpFileName,
981                                       &NtFilePath,
982                                       NULL, NULL))
983     {
984         SetLastError(ERROR_PATH_NOT_FOUND);
985         return INVALID_HANDLE_VALUE;
986     }
987 
988     /* Open the file */
989     InitializeObjectAttributes(&ObjectAttributes,
990                                &NtFilePath,
991                                OBJ_CASE_INSENSITIVE,
992                                NULL,
993                                NULL);
994 
995     Status = NtCreateFile(&FileHandle,
996                           0,
997                           &ObjectAttributes,
998                           &IoStatusBlock,
999                           NULL, 0,
1000                           FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1001                           FILE_OPEN,
1002                           0, NULL, 0);
1003     if (!NT_SUCCESS(Status)) goto Cleanup;
1004 
1005     /*
1006      * Initialization of the search handle.
1007      */
1008     FindDataHandle = RtlAllocateHeap(RtlGetProcessHeap(),
1009                                      HEAP_ZERO_MEMORY,
1010                                      sizeof(FIND_DATA_HANDLE) +
1011                                          sizeof(FIND_STREAM_DATA));
1012     if (!FindDataHandle)
1013     {
1014         Status = STATUS_NO_MEMORY;
1015         goto Cleanup;
1016     }
1017 
1018     FindDataHandle->Type = FindStream;
1019     FindDataHandle->u.FindStreamData = (PFIND_STREAM_DATA)(FindDataHandle + 1);
1020     FindStreamData = FindDataHandle->u.FindStreamData;
1021 
1022     FindStreamData->InfoLevel = InfoLevel;
1023     FindStreamData->FileStreamInfo = NULL;
1024     FindStreamData->CurrentInfo = NULL;
1025 
1026     /* The critical section must always be initialized */
1027     Status = RtlInitializeCriticalSection(&FindDataHandle->Lock);
1028     if (!NT_SUCCESS(Status))
1029     {
1030         RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
1031         goto Cleanup;
1032     }
1033 
1034     /* Capture all information about the streams */
1035     do
1036     {
1037         BufferSize += 0x1000;
1038 
1039         if (FindStreamData->FileStreamInfo == NULL)
1040         {
1041             FindStreamData->FileStreamInfo = RtlAllocateHeap(RtlGetProcessHeap(),
1042                                                               HEAP_ZERO_MEMORY,
1043                                                               BufferSize);
1044             if (FindStreamData->FileStreamInfo == NULL)
1045             {
1046                 Status = STATUS_NO_MEMORY;
1047                 break;
1048             }
1049         }
1050         else
1051         {
1052             PFILE_STREAM_INFORMATION pfsi;
1053 
1054             pfsi = RtlReAllocateHeap(RtlGetProcessHeap(),
1055                                      0, // HEAP_ZERO_MEMORY,
1056                                      FindStreamData->FileStreamInfo,
1057                                      BufferSize);
1058             if (pfsi == NULL)
1059             {
1060                 Status = STATUS_NO_MEMORY;
1061                 break;
1062             }
1063 
1064             FindStreamData->FileStreamInfo = pfsi;
1065         }
1066 
1067         Status = NtQueryInformationFile(FileHandle,
1068                                         &IoStatusBlock,
1069                                         FindStreamData->FileStreamInfo,
1070                                         BufferSize,
1071                                         FileStreamInformation);
1072 
1073     } while (Status == STATUS_BUFFER_TOO_SMALL);
1074 
1075     if (NT_SUCCESS(Status))
1076     {
1077         /* Select the first stream and return the information */
1078         FindStreamData->CurrentInfo = FindStreamData->FileStreamInfo;
1079         CopyStreamData(FindStreamData, lpFindStreamData);
1080 
1081         /* All done */
1082         Status = STATUS_SUCCESS;
1083     }
1084     else
1085     {
1086         if (FindStreamData->FileStreamInfo)
1087         {
1088             RtlFreeHeap(RtlGetProcessHeap(), 0, FindStreamData->FileStreamInfo);
1089         }
1090 
1091         RtlFreeHeap(RtlGetProcessHeap(), 0, FindDataHandle);
1092     }
1093 
1094 Cleanup:
1095     if (FileHandle) NtClose(FileHandle);
1096 
1097     RtlFreeHeap(RtlGetProcessHeap(), 0, NtFilePath.Buffer);
1098 
1099     if (NT_SUCCESS(Status))
1100     {
1101         return (HANDLE)FindDataHandle;
1102     }
1103     else
1104     {
1105         BaseSetLastNTError(Status);
1106         return INVALID_HANDLE_VALUE;
1107     }
1108 }
1109 
1110 
1111 /*
1112  * @implemented
1113  */
1114 BOOL
1115 WINAPI
1116 FindNextStreamW(IN HANDLE hFindStream,
1117                 OUT LPVOID lpFindStreamData)
1118 {
1119     PFIND_DATA_HANDLE FindDataHandle = (PFIND_DATA_HANDLE)hFindStream;
1120     PFIND_STREAM_DATA FindStreamData;
1121 
1122     if (hFindStream == NULL || hFindStream == INVALID_HANDLE_VALUE ||
1123         FindDataHandle->Type != FindStream)
1124     {
1125         SetLastError(ERROR_INVALID_HANDLE);
1126         return FALSE;
1127     }
1128 
1129     RtlEnterCriticalSection(&FindDataHandle->Lock);
1130 
1131     FindStreamData = FindDataHandle->u.FindStreamData;
1132 
1133     /* Select next stream if possible */
1134     if (FindStreamData->CurrentInfo->NextEntryOffset != 0)
1135     {
1136         FindStreamData->CurrentInfo = (PFILE_STREAM_INFORMATION)((ULONG_PTR)FindStreamData->CurrentInfo +
1137                                                                  FindStreamData->CurrentInfo->NextEntryOffset);
1138 
1139         /* Return the information */
1140         CopyStreamData(FindStreamData, lpFindStreamData);
1141 
1142         RtlLeaveCriticalSection(&FindDataHandle->Lock);
1143         return TRUE;
1144     }
1145     else
1146     {
1147         RtlLeaveCriticalSection(&FindDataHandle->Lock);
1148 
1149         SetLastError(ERROR_HANDLE_EOF);
1150         return FALSE;
1151     }
1152 }
1153 
1154 /* EOF */
1155