1 /*
2  * PROJECT:         ReactOS api tests
3  * LICENSE:         GPLv2+ - See COPYING in the top level directory
4  * PURPOSE:         Test for RtlGetFullPathName_UstrEx
5  * PROGRAMMER:      Thomas Faber <thomas.faber@reactos.org>
6  */
7 
8 #include "precomp.h"
9 
10 /*
11 NTSTATUS
12 NTAPI
13 RtlGetFullPathName_UstrEx(
14     IN PUNICODE_STRING FileName,
15     IN PUNICODE_STRING StaticString,
16     IN PUNICODE_STRING DynamicString,
17     IN PUNICODE_STRING *StringUsed,
18     IN PSIZE_T FilePartSize OPTIONAL,
19     OUT PBOOLEAN NameInvalid,
20     OUT RTL_PATH_TYPE* PathType,
21     OUT PSIZE_T LengthNeeded OPTIONAL
22 );
23 */
24 
25 NTSTATUS
26 (NTAPI
27 *pRtlGetFullPathName_UstrEx)(
28     IN PUNICODE_STRING FileName,
29     IN PUNICODE_STRING StaticString,
30     IN PUNICODE_STRING DynamicString,
31     IN PUNICODE_STRING *StringUsed,
32     IN PSIZE_T FilePartSize OPTIONAL,
33     OUT PBOOLEAN NameInvalid,
34     OUT RTL_PATH_TYPE* PathType,
35     OUT PSIZE_T LengthNeeded OPTIONAL
36 );
37 
38 #define ok_eq_ustr(str1, str2) do {                                                     \
39         ok((str1)->Buffer        == (str2)->Buffer,        "Buffer modified\n");        \
40         ok((str1)->Length        == (str2)->Length,        "Length modified\n");        \
41         ok((str1)->MaximumLength == (str2)->MaximumLength, "MaximumLength modified\n"); \
42     } while (0)
43 
44 static
45 BOOLEAN
46 CheckStringBuffer(
47     PCUNICODE_STRING String,
48     PCWSTR Expected)
49 {
50     SIZE_T ExpectedLength = wcslen(Expected) * sizeof(WCHAR);
51     SIZE_T EqualLength;
52     BOOLEAN Result = TRUE;
53     SIZE_T i;
54 
55     if (String->Length != ExpectedLength)
56     {
57         ok(0, "String length is %u, expected %lu\n", String->Length, (ULONG)ExpectedLength);
58         Result = FALSE;
59     }
60 
61     EqualLength = RtlCompareMemory(String->Buffer, Expected, ExpectedLength);
62     if (EqualLength != ExpectedLength)
63     {
64         ok(0, "String is '%wZ', expected '%S'\n", String, Expected);
65         Result = FALSE;
66     }
67 
68     if (String->Buffer[String->Length / sizeof(WCHAR)] != UNICODE_NULL)
69     {
70         ok(0, "Not null terminated\n");
71         Result = FALSE;
72     }
73 
74     /* the function nulls the rest of the buffer! */
75     for (i = String->Length + sizeof(UNICODE_NULL); i < String->MaximumLength; i++)
76     {
77         UCHAR Char = ((PUCHAR)String->Buffer)[i];
78         if (Char != 0)
79         {
80             ok(0, "Found 0x%x at offset %lu, expected 0x%x\n", Char, (ULONG)i, 0);
81             /* don't count this as a failure unless the string was actually wrong */
82             //Result = FALSE;
83             /* don't flood the log */
84             break;
85         }
86     }
87 
88     return Result;
89 }
90 
91 static
92 BOOLEAN
93 CheckBuffer(
94     PVOID Buffer,
95     SIZE_T Size,
96     UCHAR Value)
97 {
98     PUCHAR Array = Buffer;
99     SIZE_T i;
100 
101     for (i = 0; i < Size; i++)
102     {
103         if (Array[i] != Value)
104         {
105             trace("Expected %x, found %x at offset %lu\n", Value, Array[i], (ULONG)i);
106             return FALSE;
107         }
108     }
109     return TRUE;
110 }
111 
112 #define RtlPathTypeNotSet 123
113 
114 /* winetest_platform is "windows" for us, so broken() doesn't do what it should :( */
115 #undef broken
116 #define broken(x) 0
117 
118 typedef enum
119 {
120     PrefixNone,
121     PrefixCurrentDrive,
122     PrefixCurrentPath,
123     PrefixCurrentPathWithoutLastPart
124 } PREFIX_TYPE;
125 
126 static
127 VOID
128 RunTestCases(VOID)
129 {
130     /* TODO: don't duplicate this in the other tests */
131     /* TODO: Drive Relative tests don't work yet if the current drive isn't C: */
132     struct
133     {
134         PCWSTR FileName;
135         PREFIX_TYPE PrefixType;
136         PCWSTR FullPathName;
137         RTL_PATH_TYPE PathType;
138         PREFIX_TYPE FilePartPrefixType;
139         SIZE_T FilePartSize;
140     } TestCases[] =
141     {
142         { L"C:",                 PrefixCurrentPath, L"", RtlPathTypeDriveRelative, PrefixCurrentPathWithoutLastPart },
143         { L"C:\\",               PrefixNone, L"C:\\", RtlPathTypeDriveAbsolute },
144         { L"C:\\test",           PrefixNone, L"C:\\test", RtlPathTypeDriveAbsolute, PrefixCurrentDrive },
145         { L"C:\\test\\",         PrefixNone, L"C:\\test\\", RtlPathTypeDriveAbsolute },
146         { L"C:/test/",           PrefixNone, L"C:\\test\\", RtlPathTypeDriveAbsolute },
147 
148         { L"C:\\\\test",         PrefixNone, L"C:\\test", RtlPathTypeDriveAbsolute, PrefixCurrentDrive },
149         { L"test",               PrefixCurrentPath, L"\\test", RtlPathTypeRelative, PrefixCurrentPath, sizeof(WCHAR) },
150         { L"\\test",             PrefixCurrentDrive, L"test", RtlPathTypeRooted, PrefixCurrentDrive },
151         { L"/test",              PrefixCurrentDrive, L"test", RtlPathTypeRooted, PrefixCurrentDrive },
152         { L".\\test",            PrefixCurrentPath, L"\\test", RtlPathTypeRelative, PrefixCurrentPath, sizeof(WCHAR) },
153 
154         { L"\\.",                PrefixCurrentDrive, L"", RtlPathTypeRooted },
155         { L"\\.\\",              PrefixCurrentDrive, L"", RtlPathTypeRooted },
156         { L"\\\\.",              PrefixNone, L"\\\\.\\", RtlPathTypeRootLocalDevice },
157         { L"\\\\.\\",            PrefixNone, L"\\\\.\\", RtlPathTypeLocalDevice },
158         { L"\\\\.\\Something\\", PrefixNone, L"\\\\.\\Something\\", RtlPathTypeLocalDevice },
159 
160         { L"\\??\\",             PrefixCurrentDrive, L"??\\", RtlPathTypeRooted },
161         { L"\\??\\C:",           PrefixCurrentDrive, L"??\\C:", RtlPathTypeRooted, PrefixCurrentDrive, 3 * sizeof(WCHAR) },
162         { L"\\??\\C:\\",         PrefixCurrentDrive, L"??\\C:\\", RtlPathTypeRooted },
163         { L"\\??\\C:\\test",     PrefixCurrentDrive, L"??\\C:\\test", RtlPathTypeRooted, PrefixCurrentDrive, 6 * sizeof(WCHAR) },
164         { L"\\??\\C:\\test\\",   PrefixCurrentDrive, L"??\\C:\\test\\", RtlPathTypeRooted },
165 
166         { L"\\\\??\\",           PrefixNone, L"\\\\??\\", RtlPathTypeUncAbsolute },
167         { L"\\\\??\\C:",         PrefixNone, L"\\\\??\\C:", RtlPathTypeUncAbsolute },
168         { L"\\\\??\\C:\\",       PrefixNone, L"\\\\??\\C:\\", RtlPathTypeUncAbsolute },
169         { L"\\\\??\\C:\\test",   PrefixNone, L"\\\\??\\C:\\test", RtlPathTypeUncAbsolute, PrefixNone, sizeof(L"\\\\??\\C:\\") },
170         { L"\\\\??\\C:\\test\\", PrefixNone, L"\\\\??\\C:\\test\\", RtlPathTypeUncAbsolute },
171     };
172     NTSTATUS Status;
173     UNICODE_STRING FileName;
174     UNICODE_STRING FullPathName;
175     WCHAR FullPathNameBuffer[MAX_PATH];
176     UNICODE_STRING TempString;
177     PUNICODE_STRING StringUsed;
178     SIZE_T FilePartSize;
179     BOOLEAN NameInvalid;
180     RTL_PATH_TYPE PathType;
181     SIZE_T LengthNeeded;
182     WCHAR ExpectedPathName[MAX_PATH];
183     SIZE_T ExpectedFilePartSize;
184     const INT TestCount = sizeof(TestCases) / sizeof(TestCases[0]);
185     INT i;
186     BOOLEAN Okay;
187 
188     for (i = 0; i < TestCount; i++)
189     {
190         switch (TestCases[i].PrefixType)
191         {
192             case PrefixNone:
193                 ExpectedPathName[0] = UNICODE_NULL;
194                 break;
195             case PrefixCurrentDrive:
196                 GetCurrentDirectoryW(sizeof(ExpectedPathName) / sizeof(WCHAR), ExpectedPathName);
197                 ExpectedPathName[3] = UNICODE_NULL;
198                 break;
199             case PrefixCurrentPath:
200             {
201                 ULONG Length;
202                 Length = GetCurrentDirectoryW(sizeof(ExpectedPathName) / sizeof(WCHAR), ExpectedPathName);
203                 if (Length == 3 && TestCases[i].FullPathName[0])
204                     ExpectedPathName[2] = UNICODE_NULL;
205                 break;
206             }
207             default:
208                 skip("Invalid test!\n");
209                 continue;
210         }
211         wcscat(ExpectedPathName, TestCases[i].FullPathName);
212         RtlInitUnicodeString(&FileName, TestCases[i].FileName);
213         RtlInitEmptyUnicodeString(&FullPathName, FullPathNameBuffer, sizeof(FullPathNameBuffer));
214         RtlFillMemory(FullPathName.Buffer, FullPathName.MaximumLength, 0xAA);
215         TempString = FileName;
216         PathType = RtlPathTypeNotSet;
217         StringUsed = InvalidPointer;
218         FilePartSize = 1234;
219         NameInvalid = (BOOLEAN)-1;
220         LengthNeeded = 1234;
221         StartSeh()
222             Status = pRtlGetFullPathName_UstrEx(&FileName,
223                                                &FullPathName,
224                                                NULL,
225                                                &StringUsed,
226                                                &FilePartSize,
227                                                &NameInvalid,
228                                                &PathType,
229                                                &LengthNeeded);
230             ok(Status == STATUS_SUCCESS, "status = %lx\n", Status);
231         EndSeh(STATUS_SUCCESS);
232         ok_eq_ustr(&FileName, &TempString);
233         ok(FullPathName.Buffer        == FullPathNameBuffer,         "Buffer modified\n");
234         ok(FullPathName.MaximumLength == sizeof(FullPathNameBuffer), "MaximumLength modified\n");
235         Okay = CheckStringBuffer(&FullPathName, ExpectedPathName);
236         ok(Okay, "Wrong path name '%wZ', expected '%S'\n", &FullPathName, ExpectedPathName);
237         ok(StringUsed == &FullPathName, "StringUsed = %p, expected %p\n", StringUsed, &FullPathName);
238         switch (TestCases[i].FilePartPrefixType)
239         {
240             case PrefixNone:
241                 ExpectedFilePartSize = 0;
242                 break;
243             case PrefixCurrentDrive:
244                 ExpectedFilePartSize = sizeof(L"C:\\");
245                 break;
246             case PrefixCurrentPath:
247                 ExpectedFilePartSize = GetCurrentDirectoryW(0, NULL) * sizeof(WCHAR);
248                 if (ExpectedFilePartSize == sizeof(L"C:\\"))
249                     ExpectedFilePartSize -= sizeof(WCHAR);
250                 break;
251             case PrefixCurrentPathWithoutLastPart:
252             {
253                 WCHAR CurrentPath[MAX_PATH];
254                 PCWSTR BackSlash;
255                 ExpectedFilePartSize = GetCurrentDirectoryW(sizeof(CurrentPath) / sizeof(WCHAR), CurrentPath) * sizeof(WCHAR) + sizeof(UNICODE_NULL);
256                 if (ExpectedFilePartSize == sizeof(L"C:\\"))
257                     ExpectedFilePartSize = 0;
258                 else
259                 {
260                     BackSlash = wcsrchr(CurrentPath, L'\\');
261                     if (BackSlash)
262                         ExpectedFilePartSize -= wcslen(BackSlash + 1) * sizeof(WCHAR);
263                     else
264                         ok(0, "GetCurrentDirectory returned %S\n", CurrentPath);
265                 }
266                 break;
267             }
268             default:
269                 skip("Invalid test!\n");
270                 continue;
271         }
272         ExpectedFilePartSize += TestCases[i].FilePartSize;
273         if (ExpectedFilePartSize != 0)
274             ExpectedFilePartSize = (ExpectedFilePartSize - sizeof(UNICODE_NULL)) / sizeof(WCHAR);
275         ok(FilePartSize == ExpectedFilePartSize,
276             "FilePartSize = %lu, expected %lu\n", (ULONG)FilePartSize, (ULONG)ExpectedFilePartSize);
277         ok(NameInvalid == FALSE, "NameInvalid = %u\n", NameInvalid);
278         ok(PathType == TestCases[i].PathType, "PathType = %d, expected %d\n", PathType, TestCases[i].PathType);
279         ok(LengthNeeded == 0, "LengthNeeded = %lu\n", (ULONG)LengthNeeded);
280     }
281 }
282 
283 START_TEST(RtlGetFullPathName_UstrEx)
284 {
285     NTSTATUS Status;
286     UNICODE_STRING FileName;
287     UNICODE_STRING TempString;
288     UNICODE_STRING StaticString;
289     PUNICODE_STRING StringUsed;
290     SIZE_T FilePartSize;
291     BOOLEAN NameInvalid;
292     BOOLEAN NameInvalidArray[sizeof(ULONGLONG)];
293     RTL_PATH_TYPE PathType;
294     SIZE_T LengthNeeded;
295     BOOLEAN Okay;
296 
297     pRtlGetFullPathName_UstrEx = (PVOID)GetProcAddress(GetModuleHandleW(L"ntdll"), "RtlGetFullPathName_UstrEx");
298     if (!pRtlGetFullPathName_UstrEx)
299     {
300         skip("RtlGetFullPathName_UstrEx unavailable\n");
301         return;
302     }
303 
304     /* NULL parameters */
305     StartSeh()
306         pRtlGetFullPathName_UstrEx(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
307     EndSeh(STATUS_ACCESS_VIOLATION);
308 
309     RtlInitUnicodeString(&FileName, NULL);
310     TempString = FileName;
311     StartSeh()
312         pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
313     EndSeh(STATUS_ACCESS_VIOLATION);
314     ok_eq_ustr(&FileName, &TempString);
315 
316     RtlInitUnicodeString(&FileName, L"");
317     TempString = FileName;
318     StartSeh()
319         pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
320     EndSeh(STATUS_ACCESS_VIOLATION);
321     ok_eq_ustr(&FileName, &TempString);
322 
323     PathType = RtlPathTypeNotSet;
324     StartSeh()
325         pRtlGetFullPathName_UstrEx(NULL, NULL, NULL, NULL, NULL, NULL, &PathType, NULL);
326     EndSeh(STATUS_ACCESS_VIOLATION);
327     ok(PathType == RtlPathTypeUnknown ||
328        broken(PathType == RtlPathTypeNotSet) /* Win7 */, "PathType = %d\n", PathType);
329 
330     /* Check what else is initialized before it crashes */
331     PathType = RtlPathTypeNotSet;
332     StringUsed = InvalidPointer;
333     FilePartSize = 1234;
334     NameInvalid = (BOOLEAN)-1;
335     LengthNeeded = 1234;
336     StartSeh()
337         pRtlGetFullPathName_UstrEx(NULL, NULL, NULL, &StringUsed, &FilePartSize, &NameInvalid, &PathType, &LengthNeeded);
338     EndSeh(STATUS_ACCESS_VIOLATION);
339     ok(StringUsed == NULL, "StringUsed = %p\n", StringUsed);
340     ok(FilePartSize == 0, "FilePartSize = %lu\n", (ULONG)FilePartSize);
341     ok(NameInvalid == FALSE, "NameInvalid = %u\n", NameInvalid);
342     ok(PathType == RtlPathTypeUnknown ||
343        broken(PathType == RtlPathTypeNotSet) /* Win7 */, "PathType = %d\n", PathType);
344     ok(LengthNeeded == 0, "LengthNeeded = %lu\n", (ULONG)LengthNeeded);
345 
346     RtlInitUnicodeString(&FileName, L"");
347     TempString = FileName;
348     StringUsed = InvalidPointer;
349     FilePartSize = 1234;
350     NameInvalid = (BOOLEAN)-1;
351     LengthNeeded = 1234;
352     StartSeh()
353         pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, &StringUsed, &FilePartSize, &NameInvalid, NULL, &LengthNeeded);
354     EndSeh(STATUS_ACCESS_VIOLATION);
355     ok_eq_ustr(&FileName, &TempString);
356     ok(StringUsed == NULL, "StringUsed = %p\n", StringUsed);
357     ok(FilePartSize == 0, "FilePartSize = %lu\n", (ULONG)FilePartSize);
358     ok(NameInvalid == FALSE ||
359        broken(NameInvalid == (BOOLEAN)-1) /* Win7 */, "NameInvalid = %u\n", NameInvalid);
360     ok(LengthNeeded == 0, "LengthNeeded = %lu\n", (ULONG)LengthNeeded);
361 
362     /* This is the first one that doesn't crash. FileName and PathType cannot be NULL */
363     RtlInitUnicodeString(&FileName, NULL);
364     TempString = FileName;
365     PathType = RtlPathTypeNotSet;
366     StartSeh()
367         Status = pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NULL, &PathType, NULL);
368         ok(Status == STATUS_OBJECT_NAME_INVALID, "status = %lx\n", Status);
369     EndSeh(STATUS_SUCCESS);
370     ok_eq_ustr(&FileName, &TempString);
371     ok(PathType == RtlPathTypeUnknown, "PathType = %d\n", PathType);
372 
373     RtlInitUnicodeString(&FileName, L"");
374     TempString = FileName;
375     PathType = RtlPathTypeNotSet;
376     StartSeh()
377         Status = pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NULL, &PathType, NULL);
378         ok(Status == STATUS_OBJECT_NAME_INVALID, "status = %lx\n", Status);
379     EndSeh(STATUS_SUCCESS);
380     ok_eq_ustr(&FileName, &TempString);
381     ok(PathType == RtlPathTypeUnknown, "PathType = %d\n", PathType);
382 
383     /* Show that NameInvalid is indeed BOOLEAN */
384     RtlInitUnicodeString(&FileName, L"");
385     TempString = FileName;
386     PathType = RtlPathTypeNotSet;
387     RtlFillMemory(NameInvalidArray, sizeof(NameInvalidArray), 0x55);
388     StartSeh()
389         Status = pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NameInvalidArray, &PathType, NULL);
390         ok(Status == STATUS_OBJECT_NAME_INVALID, "status = %lx\n", Status);
391     EndSeh(STATUS_SUCCESS);
392     ok_eq_ustr(&FileName, &TempString);
393     ok(PathType == RtlPathTypeUnknown, "PathType = %d\n", PathType);
394     ok(NameInvalidArray[0] == FALSE, "NameInvalid = %u\n", NameInvalidArray[0]);
395     Okay = CheckBuffer(NameInvalidArray + 1, sizeof(NameInvalidArray) - sizeof(NameInvalidArray[0]), 0x55);
396     ok(Okay, "CheckBuffer failed\n");
397 
398     /* Give it a valid path */
399     RtlInitUnicodeString(&FileName, L"C:\\test");
400     TempString = FileName;
401     PathType = RtlPathTypeNotSet;
402     StartSeh()
403         Status = pRtlGetFullPathName_UstrEx(&FileName, NULL, NULL, NULL, NULL, NULL, &PathType, NULL);
404         ok(Status == STATUS_BUFFER_TOO_SMALL, "status = %lx\n", Status);
405     EndSeh(STATUS_SUCCESS);
406     ok_eq_ustr(&FileName, &TempString);
407     ok(PathType == RtlPathTypeDriveAbsolute, "PathType = %d\n", PathType);
408 
409     /* Zero-length static string */
410     RtlInitUnicodeString(&FileName, L"C:\\test");
411     TempString = FileName;
412     RtlInitUnicodeString(&StaticString, NULL);
413     PathType = RtlPathTypeNotSet;
414     StartSeh()
415         Status = pRtlGetFullPathName_UstrEx(&FileName, &StaticString, NULL, NULL, NULL, NULL, &PathType, NULL);
416         ok(Status == STATUS_BUFFER_TOO_SMALL, "status = %lx\n", Status);
417     EndSeh(STATUS_SUCCESS);
418     ok_eq_ustr(&FileName, &TempString);
419     ok(PathType == RtlPathTypeDriveAbsolute, "PathType = %d\n", PathType);
420 
421     /* TODO: play around with StaticString and DynamicString */
422 
423     /* Check the actual functionality with different paths */
424     RunTestCases();
425 }
426