mirror of
https://github.com/reactos/reactos.git
synced 2024-11-10 00:34:39 +00:00
f66525ee02
svn path=/branches/ntvdm/; revision=59665
1150 lines
37 KiB
C
1150 lines
37 KiB
C
/*
|
|
* 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(%lx, 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 %x)\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 */
|