reactos/dll/win32/kernel32/client/dosdev.c
2019-05-08 18:34:28 +02:00

1031 lines
33 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS system libraries
* FILE: dll/win32/kernel32/client/dosdev.c
* PURPOSE: Dos device functions
* PROGRAMMER: Ariadne (ariadne@xs4all.nl)
* Pierre Schweitzer
* UPDATE HISTORY:
* Created 01/11/98
*/
/* INCLUDES ******************************************************************/
#include <k32.h>
#define NDEBUG
#include <debug.h>
#include <dbt.h>
DEBUG_CHANNEL(kernel32file);
/* FUNCTIONS *****************************************************************/
/*
* @implemented
*/
NTSTATUS
IsGlobalDeviceMap(
HANDLE DirectoryHandle,
PBOOLEAN IsGlobal)
{
NTSTATUS Status;
DWORD ReturnLength;
UNICODE_STRING GlobalString;
OBJECT_NAME_INFORMATION NameInfo, *PNameInfo;
/* We need both parameters */
if (DirectoryHandle == 0 || IsGlobal == NULL)
{
return STATUS_INVALID_PARAMETER;
}
PNameInfo = NULL;
_SEH2_TRY
{
/* Query handle information */
Status = NtQueryObject(DirectoryHandle,
ObjectNameInformation,
&NameInfo,
0,
&ReturnLength);
/* Only failure we tolerate is length mismatch */
if (NT_SUCCESS(Status) || Status == STATUS_INFO_LENGTH_MISMATCH)
{
/* Allocate big enough buffer */
PNameInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, ReturnLength);
if (PNameInfo == NULL)
{
Status = STATUS_NO_MEMORY;
_SEH2_LEAVE;
}
/* Query again handle information */
Status = NtQueryObject(DirectoryHandle,
ObjectNameInformation,
PNameInfo,
ReturnLength,
&ReturnLength);
/*
* If it succeed, check we have Global??
* If so, return success
*/
if (NT_SUCCESS(Status))
{
RtlInitUnicodeString(&GlobalString, L"\\GLOBAL??");
*IsGlobal = RtlEqualUnicodeString(&GlobalString, &PNameInfo->Name, FALSE);
Status = STATUS_SUCCESS;
}
}
}
_SEH2_FINALLY
{
if (PNameInfo != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, PNameInfo);
}
}
_SEH2_END;
return Status;
}
/*
* @implemented
*/
DWORD
FindSymbolicLinkEntry(
PWSTR NameToFind,
PWSTR NamesList,
DWORD TotalEntries,
PBOOLEAN Found)
{
WCHAR Current;
DWORD Entries;
PWSTR PartialNamesList;
/* We need all parameters to be set */
if (NameToFind == NULL || NamesList == NULL || Found == NULL)
{
return ERROR_INVALID_PARAMETER;
}
/* Assume failure */
*Found = FALSE;
/* If no entries, job done, nothing found */
if (TotalEntries == 0)
{
return ERROR_SUCCESS;
}
/* Start browsing the names list */
Entries = 0;
PartialNamesList = NamesList;
/* As long as we didn't find the name... */
while (wcscmp(NameToFind, PartialNamesList) != 0)
{
/* We chomped an entry! */
++Entries;
/* We're out of entries, bail out not to overrun */
if (Entries > TotalEntries)
{
/*
* Even though we found nothing,
* the function ran fine
*/
return ERROR_SUCCESS;
}
/* Jump to the next string */
do
{
Current = *PartialNamesList;
++PartialNamesList;
} while (Current != UNICODE_NULL);
}
/*
* We're here because the loop stopped:
* it means we found the name in the list
*/
*Found = TRUE;
return ERROR_SUCCESS;
}
/*
* @implemented
*/
BOOL
WINAPI
DefineDosDeviceA(
DWORD dwFlags,
LPCSTR lpDeviceName,
LPCSTR lpTargetPath
)
{
BOOL Result;
NTSTATUS Status;
ANSI_STRING AnsiString;
PWSTR TargetPathBuffer;
UNICODE_STRING TargetPathU;
PUNICODE_STRING DeviceNameU;
/* Convert DeviceName using static unicode string */
RtlInitAnsiString(&AnsiString, lpDeviceName);
DeviceNameU = &NtCurrentTeb()->StaticUnicodeString;
Status = RtlAnsiStringToUnicodeString(DeviceNameU, &AnsiString, FALSE);
if (!NT_SUCCESS(Status))
{
/*
* If the static unicode string is too small,
* it's because the name is too long...
* so, return appropriate status!
*/
if (Status == STATUS_BUFFER_OVERFLOW)
{
SetLastError(ERROR_FILENAME_EXCED_RANGE);
return FALSE;
}
BaseSetLastNTError(Status);
return FALSE;
}
/* Convert target path if existing */
if (lpTargetPath != NULL)
{
RtlInitAnsiString(&AnsiString, lpTargetPath);
Status = RtlAnsiStringToUnicodeString(&TargetPathU, &AnsiString, TRUE);
if (!NT_SUCCESS(Status))
{
BaseSetLastNTError(Status);
return FALSE;
}
TargetPathBuffer = TargetPathU.Buffer;
}
else
{
TargetPathBuffer = NULL;
}
/* Call W */
Result = DefineDosDeviceW(dwFlags, DeviceNameU->Buffer, TargetPathBuffer);
/* Free target path if allocated */
if (TargetPathBuffer != NULL)
{
RtlFreeUnicodeString(&TargetPathU);
}
return Result;
}
/*
* @implemented
*/
BOOL
WINAPI
DefineDosDeviceW(
DWORD dwFlags,
LPCWSTR lpDeviceName,
LPCWSTR lpTargetPath
)
{
ULONG ArgumentCount;
ULONG BufferSize;
BASE_API_MESSAGE ApiMessage;
PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest = &ApiMessage.Data.DefineDosDeviceRequest;
PCSR_CAPTURE_BUFFER CaptureBuffer;
UNICODE_STRING NtTargetPathU;
UNICODE_STRING DeviceNameU;
HANDLE hUser32;
DEV_BROADCAST_VOLUME dbcv;
DWORD dwRecipients;
typedef long (WINAPI *BSM_type)(DWORD, LPDWORD, UINT, WPARAM, LPARAM);
BSM_type BSM_ptr;
BOOLEAN LUIDDeviceMapsEnabled;
WCHAR Letter;
WPARAM wParam;
/* Get status about local device mapping */
LUIDDeviceMapsEnabled = BaseStaticServerData->LUIDDeviceMapsEnabled;
/* Validate input & flags */
if ((dwFlags & 0xFFFFFFE0) ||
((dwFlags & DDD_EXACT_MATCH_ON_REMOVE) &&
!(dwFlags & DDD_REMOVE_DEFINITION)) ||
(lpTargetPath == NULL && !(dwFlags & (DDD_LUID_BROADCAST_DRIVE | DDD_REMOVE_DEFINITION))) ||
((dwFlags & DDD_LUID_BROADCAST_DRIVE) &&
(lpDeviceName == NULL || lpTargetPath != NULL || dwFlags & (DDD_NO_BROADCAST_SYSTEM | DDD_EXACT_MATCH_ON_REMOVE | DDD_RAW_TARGET_PATH) || !LUIDDeviceMapsEnabled)))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
/* Initialize device unicode string to ease its use */
RtlInitUnicodeString(&DeviceNameU, lpDeviceName);
/* The buffer for CSR call will contain it */
BufferSize = DeviceNameU.MaximumLength;
ArgumentCount = 1;
/* If we don't have target path, use empty string */
if (lpTargetPath == NULL)
{
RtlInitUnicodeString(&NtTargetPathU, NULL);
}
else
{
/* Else, use it raw if asked to */
if (dwFlags & DDD_RAW_TARGET_PATH)
{
RtlInitUnicodeString(&NtTargetPathU, lpTargetPath);
}
else
{
/* Otherwise, use it converted */
if (!RtlDosPathNameToNtPathName_U(lpTargetPath,
&NtTargetPathU,
NULL,
NULL))
{
WARN("RtlDosPathNameToNtPathName_U() failed\n");
BaseSetLastNTError(STATUS_OBJECT_NAME_INVALID);
return FALSE;
}
}
/* This target path will be the second arg */
ArgumentCount = 2;
BufferSize += NtTargetPathU.MaximumLength;
}
/* Allocate the capture buffer for our strings */
CaptureBuffer = CsrAllocateCaptureBuffer(ArgumentCount,
BufferSize);
if (CaptureBuffer == NULL)
{
if (!(dwFlags & DDD_RAW_TARGET_PATH))
{
RtlFreeUnicodeString(&NtTargetPathU);
}
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
/* Set the flags */
DefineDosDeviceRequest->Flags = dwFlags;
/* Allocate a buffer for the device name */
DefineDosDeviceRequest->DeviceName.MaximumLength = CsrAllocateMessagePointer(CaptureBuffer,
DeviceNameU.MaximumLength,
(PVOID*)&DefineDosDeviceRequest->DeviceName.Buffer);
/* And copy it while upcasing it */
RtlUpcaseUnicodeString(&DefineDosDeviceRequest->DeviceName, &DeviceNameU, FALSE);
/* If we have a target path, copy it too, and free it if allocated */
if (NtTargetPathU.Length != 0)
{
DefineDosDeviceRequest->TargetPath.MaximumLength = CsrAllocateMessagePointer(CaptureBuffer,
NtTargetPathU.MaximumLength,
(PVOID*)&DefineDosDeviceRequest->TargetPath.Buffer);
RtlCopyUnicodeString(&DefineDosDeviceRequest->TargetPath, &NtTargetPathU);
if (!(dwFlags & DDD_RAW_TARGET_PATH))
{
RtlFreeUnicodeString(&NtTargetPathU);
}
}
/* Otherwise, null initialize the string */
else
{
RtlInitUnicodeString(&DefineDosDeviceRequest->TargetPath, NULL);
}
/* Finally, call the server */
CsrClientCallServer((PCSR_API_MESSAGE)&ApiMessage,
CaptureBuffer,
CSR_CREATE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepDefineDosDevice),
sizeof(*DefineDosDeviceRequest));
CsrFreeCaptureBuffer(CaptureBuffer);
/* Return failure if any */
if (!NT_SUCCESS(ApiMessage.Status))
{
WARN("CsrClientCallServer() failed (Status %lx)\n", ApiMessage.Status);
BaseSetLastNTError(ApiMessage.Status);
return FALSE;
}
/* Here is the success path, we will always return true */
/* Should broadcast the event? Only do if not denied and if drive letter */
if (!(dwFlags & DDD_NO_BROADCAST_SYSTEM) &&
DeviceNameU.Length == 2 * sizeof(WCHAR) &&
DeviceNameU.Buffer[1] == L':')
{
/* Make sure letter is valid and there are no local device mappings */
Letter = RtlUpcaseUnicodeChar(DeviceNameU.Buffer[0]) - L'A';
if (Letter < 26 && !LUIDDeviceMapsEnabled)
{
/* Rely on user32 for broadcasting */
hUser32 = LoadLibraryW(L"user32.dll");
if (hUser32 != 0)
{
/* Get the function pointer */
BSM_ptr = (BSM_type)GetProcAddress(hUser32, "BroadcastSystemMessageW");
if (BSM_ptr)
{
/* Set our target */
dwRecipients = BSM_APPLICATIONS;
/* And initialize our structure */
dbcv.dbcv_size = sizeof(DEV_BROADCAST_VOLUME);
dbcv.dbcv_devicetype = DBT_DEVTYP_VOLUME;
dbcv.dbcv_reserved = 0;
/* Set the volume which had the event */
dbcv.dbcv_unitmask = 1 << Letter;
dbcv.dbcv_flags = DBTF_NET;
/* And properly set the event (removal or arrival?) */
wParam = (dwFlags & DDD_REMOVE_DEFINITION) ? DBT_DEVICEREMOVECOMPLETE : DBT_DEVICEARRIVAL;
/* And broadcast! */
BSM_ptr(BSF_SENDNOTIFYMESSAGE | BSF_FLUSHDISK,
&dwRecipients,
WM_DEVICECHANGE,
wParam,
(LPARAM)&dbcv);
}
/* We're done! */
FreeLibrary(hUser32);
}
}
}
return TRUE;
}
/*
* @implemented
*/
DWORD
WINAPI
QueryDosDeviceA(
LPCSTR lpDeviceName,
LPSTR lpTargetPath,
DWORD ucchMax
)
{
NTSTATUS Status;
USHORT CurrentPosition;
ANSI_STRING AnsiString;
UNICODE_STRING TargetPathU;
PUNICODE_STRING DeviceNameU;
DWORD RetLength, CurrentLength, Length;
PWSTR DeviceNameBuffer, TargetPathBuffer;
/* If we want a specific device name, convert it */
if (lpDeviceName != NULL)
{
/* Convert DeviceName using static unicode string */
RtlInitAnsiString(&AnsiString, lpDeviceName);
DeviceNameU = &NtCurrentTeb()->StaticUnicodeString;
Status = RtlAnsiStringToUnicodeString(DeviceNameU, &AnsiString, FALSE);
if (!NT_SUCCESS(Status))
{
/*
* If the static unicode string is too small,
* it's because the name is too long...
* so, return appropriate status!
*/
if (Status == STATUS_BUFFER_OVERFLOW)
{
SetLastError(ERROR_FILENAME_EXCED_RANGE);
return FALSE;
}
BaseSetLastNTError(Status);
return FALSE;
}
DeviceNameBuffer = DeviceNameU->Buffer;
}
else
{
DeviceNameBuffer = NULL;
}
/* Allocate the output buffer for W call */
TargetPathBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, ucchMax * sizeof(WCHAR));
if (TargetPathBuffer == NULL)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return 0;
}
/* Call W */
Length = QueryDosDeviceW(DeviceNameBuffer, TargetPathBuffer, ucchMax);
/* We'll return that length in case of a success */
RetLength = Length;
/* Handle the case where we would fill output buffer completly */
if (Length != 0 && Length == ucchMax)
{
/* This will be our work length (but not the one we return) */
--Length;
/* Already 0 the last char */
lpTargetPath[Length] = ANSI_NULL;
}
/* If we had an output, start the convert loop */
if (Length != 0)
{
/*
* We'll have to loop because TargetPathBuffer may contain
* several strings (NULL separated)
* We'll start at position 0
*/
CurrentPosition = 0;
while (CurrentPosition < Length)
{
/* Get the maximum length */
CurrentLength = min(Length - CurrentPosition, MAXUSHORT / 2);
/* Initialize our output string */
AnsiString.Length = 0;
AnsiString.MaximumLength = CurrentLength + sizeof(ANSI_NULL);
AnsiString.Buffer = &lpTargetPath[CurrentPosition];
/* Initialize input string that will be converted */
TargetPathU.Length = CurrentLength * sizeof(WCHAR);
TargetPathU.MaximumLength = CurrentLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
TargetPathU.Buffer = &TargetPathBuffer[CurrentPosition];
/* Convert to ANSI */
Status = RtlUnicodeStringToAnsiString(&AnsiString, &TargetPathU, FALSE);
if (!NT_SUCCESS(Status))
{
BaseSetLastNTError(Status);
/* In case of a failure, forget about everything */
RetLength = 0;
goto Leave;
}
/* Move to the next string */
CurrentPosition += CurrentLength;
}
}
Leave:
/* Free our intermediate buffer and leave */
RtlFreeHeap(RtlGetProcessHeap(), 0, TargetPathBuffer);
return RetLength;
}
/*
* @implemented
*/
DWORD
WINAPI
QueryDosDeviceW(
LPCWSTR lpDeviceName,
LPWSTR lpTargetPath,
DWORD ucchMax
)
{
PWSTR Ptr;
PVOID Buffer;
NTSTATUS Status;
USHORT i, TotalEntries;
UNICODE_STRING UnicodeString;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE DirectoryHandle, DeviceHandle;
BOOLEAN IsGlobal, GlobalNeeded, Found;
POBJECT_DIRECTORY_INFORMATION DirInfo;
OBJECT_DIRECTORY_INFORMATION NullEntry = {{0}};
ULONG ReturnLength, NameLength, Length, Context, BufferLength;
/* Open the '\??' directory */
RtlInitUnicodeString(&UnicodeString, L"\\??");
InitializeObjectAttributes(&ObjectAttributes,
&UnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtOpenDirectoryObject(&DirectoryHandle,
DIRECTORY_QUERY,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
WARN("NtOpenDirectoryObject() failed (Status %lx)\n", Status);
BaseSetLastNTError(Status);
return 0;
}
Buffer = NULL;
_SEH2_TRY
{
if (lpDeviceName != NULL)
{
/* Open the lpDeviceName link object */
RtlInitUnicodeString(&UnicodeString, lpDeviceName);
InitializeObjectAttributes(&ObjectAttributes,
&UnicodeString,
OBJ_CASE_INSENSITIVE,
DirectoryHandle,
NULL);
Status = NtOpenSymbolicLinkObject(&DeviceHandle,
SYMBOLIC_LINK_QUERY,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
WARN("NtOpenSymbolicLinkObject() failed (Status %lx)\n", Status);
_SEH2_LEAVE;
}
/*
* Make sure we don't overrun the output buffer, so convert our DWORD
* size to USHORT size properly
*/
Length = (ucchMax <= MAXULONG / sizeof(WCHAR)) ? (ucchMax * sizeof(WCHAR)) : MAXULONG;
/* Query link target */
UnicodeString.Length = 0;
UnicodeString.MaximumLength = Length <= MAXUSHORT ? Length : MAXUSHORT;
UnicodeString.Buffer = lpTargetPath;
ReturnLength = 0;
Status = NtQuerySymbolicLinkObject(DeviceHandle,
&UnicodeString,
&ReturnLength);
NtClose(DeviceHandle);
if (!NT_SUCCESS(Status))
{
WARN("NtQuerySymbolicLinkObject() failed (Status %lx)\n", Status);
_SEH2_LEAVE;
}
TRACE("ReturnLength: %lu\n", ReturnLength);
TRACE("TargetLength: %hu\n", UnicodeString.Length);
TRACE("Target: '%wZ'\n", &UnicodeString);
Length = ReturnLength / sizeof(WCHAR);
/* Make sure we null terminate output buffer */
if (Length == 0 || lpTargetPath[Length - 1] != UNICODE_NULL)
{
if (Length >= ucchMax)
{
TRACE("Buffer is too small\n");
Status = STATUS_BUFFER_TOO_SMALL;
_SEH2_LEAVE;
}
/* Append null-character */
lpTargetPath[Length] = UNICODE_NULL;
Length++;
}
if (Length < ucchMax)
{
/* Append null-character */
lpTargetPath[Length] = UNICODE_NULL;
Length++;
}
_SEH2_LEAVE;
}
/*
* If LUID device maps are enabled,
* ?? may not point to BaseNamedObjects
* It may only be local DOS namespace.
* And thus, it might be required to browse
* Global?? for global devices
*/
GlobalNeeded = FALSE;
if (BaseStaticServerData->LUIDDeviceMapsEnabled)
{
/* Assume ?? == Global?? */
IsGlobal = TRUE;
/* Check if it's the case */
Status = IsGlobalDeviceMap(DirectoryHandle, &IsGlobal);
if (NT_SUCCESS(Status) && !IsGlobal)
{
/* It's not, we'll have to browse Global?? too! */
GlobalNeeded = TRUE;
}
}
/*
* Make sure we don't overrun the output buffer, so convert our DWORD
* size to USHORT size properly
*/
BufferLength = (ucchMax <= MAXULONG / sizeof(WCHAR)) ? (ucchMax * sizeof(WCHAR)) : MAXULONG;
Length = 0;
Ptr = lpTargetPath;
Context = 0;
TotalEntries = 0;
/*
* We'll query all entries at once, with a rather big buffer
* If it's too small, we'll grow it by 2.
* Limit the number of attempts to 3.
*/
for (i = 0; i < 3; ++i)
{
/* Allocate the query buffer */
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
if (Buffer == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
_SEH2_LEAVE;
}
/* Perform the query */
Status = NtQueryDirectoryObject(DirectoryHandle,
Buffer,
BufferLength,
FALSE,
TRUE,
&Context,
&ReturnLength);
/* Only failure accepted is: no more entries */
if (!NT_SUCCESS(Status))
{
if (Status != STATUS_NO_MORE_ENTRIES)
{
_SEH2_LEAVE;
}
/*
* Which is a success! But break out,
* it means our query returned no results
* so, nothing to parse.
*/
Status = STATUS_SUCCESS;
break;
}
/* In case we had them all, start browsing for devices */
if (Status != STATUS_MORE_ENTRIES)
{
DirInfo = Buffer;
/* Loop until we find the nul entry (terminating entry) */
while (TRUE)
{
/* It's an entry full of zeroes */
if (RtlCompareMemory(&NullEntry, DirInfo, sizeof(NullEntry)) == sizeof(NullEntry))
{
break;
}
/* Only handle symlinks */
if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink"))
{
TRACE("Name: '%wZ'\n", &DirInfo->Name);
/* Get name length in chars to only comparisons */
NameLength = DirInfo->Name.Length / sizeof(WCHAR);
/* Make sure we don't overrun output buffer */
if (Length > ucchMax ||
NameLength > ucchMax - Length ||
ucchMax - NameLength - Length < sizeof(WCHAR))
{
Status = STATUS_BUFFER_TOO_SMALL;
_SEH2_LEAVE;
}
/* Copy and NULL terminate string */
memcpy(Ptr, DirInfo->Name.Buffer, DirInfo->Name.Length);
Ptr[NameLength] = UNICODE_NULL;
Ptr += (NameLength + 1);
Length += (NameLength + 1);
/*
* Keep the entries count, in case we would have to
* handle GLOBAL?? too
*/
++TotalEntries;
}
/* Move to the next entry */
++DirInfo;
}
/*
* No need to loop again here, we got all the entries
* Note: we don't free the buffer here, because we may
* need it for GLOBAL??, so we save a few cycles here.
*/
break;
}
/* Failure path here, we'll need bigger buffer */
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
Buffer = NULL;
/* We can't have bigger than that one, so leave */
if (BufferLength == MAXULONG)
{
break;
}
/* Prevent any overflow while computing new size */
if (MAXULONG - BufferLength < BufferLength)
{
BufferLength = MAXULONG;
}
else
{
BufferLength *= 2;
}
}
/*
* Out of the hot loop, but with more entries left?
* that's an error case, leave here!
*/
if (Status == STATUS_MORE_ENTRIES)
{
Status = STATUS_BUFFER_TOO_SMALL;
_SEH2_LEAVE;
}
/* Now, if we had to handle GLOBAL??, go for it! */
if (BaseStaticServerData->LUIDDeviceMapsEnabled && NT_SUCCESS(Status) && GlobalNeeded)
{
NtClose(DirectoryHandle);
DirectoryHandle = 0;
RtlInitUnicodeString(&UnicodeString, L"\\GLOBAL??");
InitializeObjectAttributes(&ObjectAttributes,
&UnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = NtOpenDirectoryObject(&DirectoryHandle,
DIRECTORY_QUERY,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
WARN("NtOpenDirectoryObject() failed (Status %lx)\n", Status);
_SEH2_LEAVE;
}
/*
* We'll query all entries at once, with a rather big buffer
* If it's too small, we'll grow it by 2.
* Limit the number of attempts to 3.
*/
for (i = 0; i < 3; ++i)
{
/* If we had no buffer from previous attempt, allocate one */
if (Buffer == NULL)
{
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
if (Buffer == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
_SEH2_LEAVE;
}
}
/* Perform the query */
Status = NtQueryDirectoryObject(DirectoryHandle,
Buffer,
BufferLength,
FALSE,
TRUE,
&Context,
&ReturnLength);
/* Only failure accepted is: no more entries */
if (!NT_SUCCESS(Status))
{
if (Status != STATUS_NO_MORE_ENTRIES)
{
_SEH2_LEAVE;
}
/*
* Which is a success! But break out,
* it means our query returned no results
* so, nothing to parse.
*/
Status = STATUS_SUCCESS;
break;
}
/* In case we had them all, start browsing for devices */
if (Status != STATUS_MORE_ENTRIES)
{
DirInfo = Buffer;
/* Loop until we find the nul entry (terminating entry) */
while (TRUE)
{
/* It's an entry full of zeroes */
if (RtlCompareMemory(&NullEntry, DirInfo, sizeof(NullEntry)) == sizeof(NullEntry))
{
break;
}
/* Only handle symlinks */
if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink"))
{
TRACE("Name: '%wZ'\n", &DirInfo->Name);
/*
* Now, we previously already browsed ??, and we
* don't want to devices twice, so we'll check
* the output buffer for duplicates.
* We'll add our entry only if we don't have already
* returned it.
*/
if (FindSymbolicLinkEntry(DirInfo->Name.Buffer,
lpTargetPath,
TotalEntries,
&Found) == ERROR_SUCCESS &&
!Found)
{
/* Get name length in chars to only comparisons */
NameLength = DirInfo->Name.Length / sizeof(WCHAR);
/* Make sure we don't overrun output buffer */
if (Length > ucchMax ||
NameLength > ucchMax - Length ||
ucchMax - NameLength - Length < sizeof(WCHAR))
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
NtClose(DirectoryHandle);
BaseSetLastNTError(STATUS_BUFFER_TOO_SMALL);
return 0;
}
/* Copy and NULL terminate string */
memcpy(Ptr, DirInfo->Name.Buffer, DirInfo->Name.Length);
Ptr[NameLength] = UNICODE_NULL;
Ptr += (NameLength + 1);
Length += (NameLength + 1);
}
}
/* Move to the next entry */
++DirInfo;
}
/* No need to loop again here, we got all the entries */
break;
}
/* Failure path here, we'll need bigger buffer */
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
Buffer = NULL;
/* We can't have bigger than that one, so leave */
if (BufferLength == MAXULONG)
{
break;
}
/* Prevent any overflow while computing new size */
if (MAXULONG - BufferLength < BufferLength)
{
BufferLength = MAXULONG;
}
else
{
BufferLength *= 2;
}
}
/*
* Out of the hot loop, but with more entries left?
* that's an error case, leave here!
*/
if (Status == STATUS_MORE_ENTRIES)
{
Status = STATUS_BUFFER_TOO_SMALL;
_SEH2_LEAVE;
}
}
/* If we failed somewhere, just leave */
if (!NT_SUCCESS(Status))
{
_SEH2_LEAVE;
}
/* If we returned no entries, time to write the empty string */
if (Length == 0)
{
/* Unless output buffer is too small! */
if (ucchMax <= 0)
{
Status = STATUS_BUFFER_TOO_SMALL;
_SEH2_LEAVE;
}
/* Emptry string is one char (terminator!) */
*Ptr = UNICODE_NULL;
++Ptr;
Length = 1;
}
/*
* If we have enough room, we need to double terminate the buffer:
* that's a MULTI_SZ buffer, its end is marked by double NULL.
* One was already added during the "copy string" process.
* If we don't have enough room: that's a failure case.
*/
if (Length < ucchMax)
{
*Ptr = UNICODE_NULL;
++Ptr;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
}
_SEH2_FINALLY
{
if (DirectoryHandle != 0)
{
NtClose(DirectoryHandle);
}
if (Buffer != NULL)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
}
if (!NT_SUCCESS(Status))
{
Length = 0;
BaseSetLastNTError(Status);
}
}
_SEH2_END;
return Length;
}
/* EOF */