diff --git a/modules/rostests/apitests/ntdll/CMakeLists.txt b/modules/rostests/apitests/ntdll/CMakeLists.txt index 545d935e144..85dba02c31d 100644 --- a/modules/rostests/apitests/ntdll/CMakeLists.txt +++ b/modules/rostests/apitests/ntdll/CMakeLists.txt @@ -62,6 +62,7 @@ list(APPEND SOURCE RtlGetFullPathName_U.c RtlGetFullPathName_Ustr.c RtlGetFullPathName_UstrEx.c + RtlGetLengthWithoutLastFullDosOrNtPathElement.c RtlGetLengthWithoutTrailingPathSeperators.c RtlGetLongestNtPathLength.c RtlGetNtProductType.c diff --git a/modules/rostests/apitests/ntdll/RtlGetLengthWithoutLastFullDosOrNtPathElement.c b/modules/rostests/apitests/ntdll/RtlGetLengthWithoutLastFullDosOrNtPathElement.c new file mode 100644 index 00000000000..d6d7c0dff9b --- /dev/null +++ b/modules/rostests/apitests/ntdll/RtlGetLengthWithoutLastFullDosOrNtPathElement.c @@ -0,0 +1,109 @@ +/* + * PROJECT: ReactOS API Tests + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Test for RtlGetLengthWithoutLastFullDosOrNtPathElement + * COPYRIGHT: Copyright 2021 Mark Jansen + */ + +#include "precomp.h" + + +NTSTATUS +NTAPI +RtlGetLengthWithoutLastFullDosOrNtPathElement( + IN ULONG Flags, + IN PCUNICODE_STRING Path, + OUT PULONG LengthOut); + + +typedef struct _rtl_test_data +{ + LPCWSTR Path; + ULONG Length; + NTSTATUS Status; +} rtl_test_data; + +// Based on http://undoc.airesoft.co.uk/ntdll.dll/RtlGetLengthWithoutLastFullDosOrNtPathElement.php +rtl_test_data tests[] = { + { L"", 0, STATUS_SUCCESS, }, + { L"C", 0, STATUS_INVALID_PARAMETER, }, + { L"C:", 0, STATUS_INVALID_PARAMETER, }, + { L"C:\\", 0, STATUS_SUCCESS, }, + { L"C:\\test", 3, STATUS_SUCCESS, }, + { L"C:\\test\\", 3, STATUS_SUCCESS, }, + { L"C:\\test\\a", 8, STATUS_SUCCESS, }, + { L"C:\\test\\a\\", 8, STATUS_SUCCESS, }, + { L"C://test", 3, STATUS_SUCCESS, }, + { L"C://test\\", 3, STATUS_SUCCESS, }, + { L"C://test\\\\", 3, STATUS_SUCCESS, }, + { L"C://test/", 3, STATUS_SUCCESS, }, + { L"C://test//", 3, STATUS_SUCCESS, }, + { L"C://test\\a", 9, STATUS_SUCCESS, }, + { L"C://test\\\\a", 9, STATUS_SUCCESS, }, + { L"C://test/a", 9, STATUS_SUCCESS, }, + { L"C://test//a", 9, STATUS_SUCCESS, }, + { L"C://test\\a\\", 9, STATUS_SUCCESS, }, + { L"C://test//a//", 9, STATUS_SUCCESS, }, + { L"C://test//a/", 9, STATUS_SUCCESS, }, + { L"X", 0, STATUS_INVALID_PARAMETER, }, + { L"X:", 0, STATUS_INVALID_PARAMETER, }, + { L"X:\\", 0, STATUS_SUCCESS, }, + { L"D:\\Test\\hello.ext", 8, STATUS_SUCCESS, }, + { L"\\\\?\\C", 0, STATUS_INVALID_PARAMETER, }, + { L"\\\\?\\C:", 0, STATUS_INVALID_PARAMETER, }, + { L"\\\\?\\CC", 0, STATUS_INVALID_PARAMETER, }, + { L"\\\\?\\C:\\", 4, STATUS_SUCCESS, }, + { L"\\\\?\\::\\", 4, STATUS_SUCCESS, }, + { L"\\\\?\\CCC", 0, STATUS_INVALID_PARAMETER, }, + { L"\\\\?\\CCC\\", 0, STATUS_INVALID_PARAMETER, }, + { L"\\??\\UNC\\Mytest", 8, STATUS_SUCCESS, }, + { L"\\SystemRoot", 0, STATUS_SUCCESS, }, + { L"\\SystemRoot\\", 0, STATUS_SUCCESS, }, + { L"\\SystemRoot\\ntdll.dll", 12, STATUS_SUCCESS, }, + { L"\\Device\\HarddiskVolume9000", 8, STATUS_SUCCESS, }, + { L"\\Stuff\\doesnt\\really\\matter", 21, STATUS_SUCCESS, }, + { L"this\\doesnt\\really\\work", 0, STATUS_INVALID_PARAMETER, }, + { L"multi(0)disk(0)rdisk(0)partition(1)", 0, STATUS_INVALID_PARAMETER, }, + { L"multi(0)disk(0)rdisk(0)partition(1)\\test", 0, STATUS_INVALID_PARAMETER, }, + { L"xyz", 0, STATUS_INVALID_PARAMETER, }, + { L"CON", 0, STATUS_INVALID_PARAMETER, }, + { L":", 0, STATUS_INVALID_PARAMETER, }, + { L"\\\\", 0, STATUS_SUCCESS, }, +}; + + +START_TEST(RtlGetLengthWithoutLastFullDosOrNtPathElement) +{ + UNICODE_STRING Dum; + NTSTATUS Status; + ULONG Length; + RtlInitUnicodeString(&Dum, L"c:\\test\\"); + + Length = 333; + Status = RtlGetLengthWithoutLastFullDosOrNtPathElement(0, NULL, &Length); + ok_int(Length, 0); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + Status = RtlGetLengthWithoutLastFullDosOrNtPathElement(0, &Dum, NULL); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + for (ULONG n = 0; n < 32; ++n) + { + Length = 333; + Status = RtlGetLengthWithoutLastFullDosOrNtPathElement((1 << n), &Dum, &Length); + ok_int(Length, 0); + ok_hex(Status, STATUS_INVALID_PARAMETER); + } + + for (ULONG n = 0; n < ARRAYSIZE(tests); ++n) + { + UNICODE_STRING Str; + Length = 333; + + RtlInitUnicodeString(&Str, tests[n].Path); + + Status = RtlGetLengthWithoutLastFullDosOrNtPathElement(0, &Str, &Length); + ok(Status == tests[n].Status, "Got Status=0x%lx, expected 0x%lx (%S)\n", Status, tests[n].Status, Str.Buffer); + ok(Length == tests[n].Length, "Got Length=0x%lx, expected 0x%lx (%S)\n", Length, tests[n].Length, Str.Buffer); + } +} diff --git a/modules/rostests/apitests/ntdll/testlist.c b/modules/rostests/apitests/ntdll/testlist.c index 7c9cdb2f694..141a1305f61 100644 --- a/modules/rostests/apitests/ntdll/testlist.c +++ b/modules/rostests/apitests/ntdll/testlist.c @@ -59,6 +59,7 @@ extern void func_RtlGenerate8dot3Name(void); extern void func_RtlGetFullPathName_U(void); extern void func_RtlGetFullPathName_Ustr(void); extern void func_RtlGetFullPathName_UstrEx(void); +extern void func_RtlGetLengthWithoutLastFullDosOrNtPathElement(void); extern void func_RtlGetLengthWithoutTrailingPathSeperators(void); extern void func_RtlGetLongestNtPathLength(void); extern void func_RtlGetNtProductType(void); @@ -138,6 +139,7 @@ const struct test winetest_testlist[] = { "RtlGetFullPathName_U", func_RtlGetFullPathName_U }, { "RtlGetFullPathName_Ustr", func_RtlGetFullPathName_Ustr }, { "RtlGetFullPathName_UstrEx", func_RtlGetFullPathName_UstrEx }, + { "RtlGetLengthWithoutLastFullDosOrNtPathElement", func_RtlGetLengthWithoutLastFullDosOrNtPathElement }, { "RtlGetLengthWithoutTrailingPathSeperators", func_RtlGetLengthWithoutTrailingPathSeperators }, { "RtlGetLongestNtPathLength", func_RtlGetLongestNtPathLength }, { "RtlGetNtProductType", func_RtlGetNtProductType }, diff --git a/sdk/lib/rtl/path.c b/sdk/lib/rtl/path.c index fb28160141b..360c68df735 100644 --- a/sdk/lib/rtl/path.c +++ b/sdk/lib/rtl/path.c @@ -494,11 +494,91 @@ RtlpApplyLengthFunction(IN ULONG Flags, NTSTATUS NTAPI RtlGetLengthWithoutLastFullDosOrNtPathElement(IN ULONG Flags, - IN PWCHAR Path, + IN PCUNICODE_STRING Path, OUT PULONG LengthOut) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + static const UNICODE_STRING PathDividers = RTL_CONSTANT_STRING(L"\\/"); + USHORT Position; + RTL_PATH_TYPE PathType; + + /* All failure paths have this in common, so simplify code */ + if (LengthOut) + *LengthOut = 0; + + if (Flags || !Path || !LengthOut) + { + return STATUS_INVALID_PARAMETER; + } + + if ((Path->Length / sizeof(WCHAR)) == 0) + { + /* Nothing to do here */ + return STATUS_SUCCESS; + } + + + PathType = RtlDetermineDosPathNameType_Ustr(Path); + switch (PathType) + { + case RtlPathTypeLocalDevice: + // Handle \\\\?\\C:\\ with the last ':' or '\\' missing: + if (Path->Length / sizeof(WCHAR) < 7 || + Path->Buffer[5] != ':' || + !IS_PATH_SEPARATOR(Path->Buffer[6])) + { + return STATUS_INVALID_PARAMETER; + } + break; + case RtlPathTypeRooted: + // "\\??\\" + break; + case RtlPathTypeUncAbsolute: + // "\\\\" + break; + case RtlPathTypeDriveAbsolute: + // "C:\\" + break; + default: + return STATUS_INVALID_PARAMETER; + } + + /* Find the last path separator */ + if (!NT_SUCCESS(RtlFindCharInUnicodeString(RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END, Path, &PathDividers, &Position))) + Position = 0; + + /* Is it the last char of the string? */ + if (Position && Position + sizeof(WCHAR) == Path->Length) + { + UNICODE_STRING Tmp = *Path; + Tmp.Length = Position; + + /* Keep walking path separators to eliminate multiple next to eachother */ + while (Tmp.Length > sizeof(WCHAR) && IS_PATH_SEPARATOR(Tmp.Buffer[Tmp.Length / sizeof(WCHAR)])) + Tmp.Length -= sizeof(WCHAR); + + /* Find the previous occurence */ + if (!NT_SUCCESS(RtlFindCharInUnicodeString(RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END, &Tmp, &PathDividers, &Position))) + Position = 0; + } + + /* Simplify code by working in chars instead of bytes */ + if (Position) + Position /= sizeof(WCHAR); + + if (Position) + { + // Keep walking path separators to eliminate multiple next to eachother, but ensure we leave one in place! + while (Position > 1 && IS_PATH_SEPARATOR(Path->Buffer[Position - 1])) + Position--; + } + + if (Position > 0) + { + /* Return a length, not an index */ + *LengthOut = Position + 1; + } + + return STATUS_SUCCESS; } NTSTATUS