https://git.reactos.org/?p=reactos.git;a=commitdiff;h=5d20d512beed5b3bdd10f3...
commit 5d20d512beed5b3bdd10f3d5af525d780cd0e7d9 Author: Pierre Schweitzer pierre@reactos.org AuthorDate: Sun May 5 14:18:09 2019 +0200 Commit: Pierre Schweitzer pierre@reactos.org CommitDate: Sun May 5 14:18:09 2019 +0200
[KERNEL32] Rewrite QueryDosDeviceW to handle global and local MS-DOS namespaces
It also comes with a small performance boost: instead of performing object queries one after another, we query them all at once. --- dll/win32/kernel32/client/dosdev.c | 653 +++++++++++++++++++++++++++++++------ 1 file changed, 560 insertions(+), 93 deletions(-)
diff --git a/dll/win32/kernel32/client/dosdev.c b/dll/win32/kernel32/client/dosdev.c index b32d434195..67fd60859d 100644 --- a/dll/win32/kernel32/client/dosdev.c +++ b/dll/win32/kernel32/client/dosdev.c @@ -4,6 +4,7 @@ * FILE: dll/win32/kernel32/client/dosdev.c * PURPOSE: Dos device functions * PROGRAMMER: Ariadne (ariadne@xs4all.nl) + * Pierre Schweitzer * UPDATE HISTORY: * Created 01/11/98 */ @@ -19,6 +20,142 @@ 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 */ @@ -381,19 +518,17 @@ QueryDosDeviceW( DWORD ucchMax ) { - POBJECT_DIRECTORY_INFORMATION DirInfo; - OBJECT_ATTRIBUTES ObjectAttributes; - UNICODE_STRING UnicodeString; - HANDLE DirectoryHandle; - HANDLE DeviceHandle; - ULONG ReturnLength; - ULONG NameLength; - ULONG Length; - ULONG Context; - BOOLEAN RestartScan; - NTSTATUS Status; - UCHAR Buffer[512]; 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"\??"); @@ -412,123 +547,455 @@ QueryDosDeviceW( return 0; }
- Length = 0; - - if (lpDeviceName != NULL) + Buffer = NULL; + _SEH2_TRY { - /* Open the lpDeviceName link object */ - RtlInitUnicodeString(&UnicodeString, (PWSTR)lpDeviceName); - InitializeObjectAttributes(&ObjectAttributes, - &UnicodeString, - OBJ_CASE_INSENSITIVE, - DirectoryHandle, - NULL); - Status = NtOpenSymbolicLinkObject(&DeviceHandle, - SYMBOLIC_LINK_QUERY, - &ObjectAttributes); - if (!NT_SUCCESS(Status)) + if (lpDeviceName != NULL) { - WARN("NtOpenSymbolicLinkObject() failed (Status %lx)\n", Status); - NtClose(DirectoryHandle); - BaseSetLastNTError(Status); - return 0; - } + /* 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; + }
- /* Query link target */ - UnicodeString.Length = 0; - UnicodeString.MaximumLength = (USHORT)ucchMax * sizeof(WCHAR); - UnicodeString.Buffer = lpTargetPath; - - ReturnLength = 0; - Status = NtQuerySymbolicLinkObject(DeviceHandle, - &UnicodeString, - &ReturnLength); - NtClose(DeviceHandle); - NtClose(DirectoryHandle); - if (!NT_SUCCESS(Status)) - { - WARN("NtQuerySymbolicLinkObject() failed (Status %lx)\n", Status); - BaseSetLastNTError(Status); - return 0; - } + /* + * 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); + TRACE("ReturnLength: %lu\n", ReturnLength); + TRACE("TargetLength: %hu\n", UnicodeString.Length); + TRACE("Target: '%wZ'\n", &UnicodeString);
- Length = UnicodeString.Length / sizeof(WCHAR); - if (Length < ucchMax) - { - /* Append null-character */ - lpTargetPath[Length] = UNICODE_NULL; - Length++; + 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; } - else + + /* + * 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) { - TRACE("Buffer is too small\n"); - BaseSetLastNTError(STATUS_BUFFER_TOO_SMALL); - return 0; + /* 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; + } } - } - else - { - RestartScan = TRUE; - Context = 0; + + /* + * 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; - DirInfo = (POBJECT_DIRECTORY_INFORMATION)Buffer;
- while (TRUE) + 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, - sizeof(Buffer), + BufferLength, + FALSE, TRUE, - RestartScan, &Context, &ReturnLength); + /* Only failure accepted is: no more entries */ if (!NT_SUCCESS(Status)) { - if (Status == STATUS_NO_MORE_ENTRIES) + if (Status != STATUS_NO_MORE_ENTRIES) { - /* Terminate the buffer */ - *Ptr = UNICODE_NULL; - Length++; - - Status = STATUS_SUCCESS; + _SEH2_LEAVE; } - else + + /* + * 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) { - Length = 0; + /* 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; } - BaseSetLastNTError(Status); + + /* + * 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; }
- if (!wcscmp(DirInfo->TypeName.Buffer, L"SymbolicLink")) + /* Prevent any overflow while computing new size */ + if (MAXULONG - BufferLength < BufferLength) { - TRACE("Name: '%wZ'\n", &DirInfo->Name); + 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; + }
- NameLength = DirInfo->Name.Length / sizeof(WCHAR); - if (Length + NameLength + 1 >= ucchMax) + /* + * 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) { - Length = 0; - BaseSetLastNTError(STATUS_BUFFER_TOO_SMALL); break; }
- memcpy(Ptr, DirInfo->Name.Buffer, DirInfo->Name.Length); - Ptr += NameLength; - Length += NameLength; - *Ptr = UNICODE_NULL; - Ptr++; - Length++; + /* 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; }
- RestartScan = FALSE; + /* Emptry string is one char (terminator!) */ + *Ptr = UNICODE_NULL; + ++Ptr; + Length = 1; }
- NtClose(DirectoryHandle); + /* + * 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; }