[RTL] Add internal RtlpDowncaseUnicodeChar function and use instead RtlDowncaseUnicod...
[reactos.git] / reactos / sdk / lib / rtl / path.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: lib/rtl/path.c
5 * PURPOSE: Path and current directory functions
6 * PROGRAMMERS: Wine team
7 * Thomas Weidenmueller
8 * Gunnar Dalsnes
9 * Alex Ionescu (alex.ionescu@reactos.org)
10 * Pierre Schweitzer (pierre@reactos.org)
11 */
12
13 /* INCLUDES *******************************************************************/
14
15 #include <rtl.h>
16
17 #define NDEBUG
18 #include <debug.h>
19
20 /* DEFINITONS and MACROS ******************************************************/
21
22 #define MAX_PFX_SIZE 16
23
24 #define IS_PATH_SEPARATOR(x) (((x)==L'\\')||((x)==L'/'))
25
26 #define RTL_CURDIR_IS_REMOVABLE 0x1
27 #define RTL_CURDIR_DROP_OLD_HANDLE 0x2
28 #define RTL_CURDIR_ALL_FLAGS (RTL_CURDIR_DROP_OLD_HANDLE | RTL_CURDIR_IS_REMOVABLE) // 0x3
29 C_ASSERT(RTL_CURDIR_ALL_FLAGS == OBJ_HANDLE_TAGBITS);
30
31
32 /* GLOBALS ********************************************************************/
33
34 const UNICODE_STRING DeviceRootString = RTL_CONSTANT_STRING(L"\\\\.\\");
35
36 const UNICODE_STRING RtlpDosDevicesUncPrefix = RTL_CONSTANT_STRING(L"\\??\\UNC\\");
37 const UNICODE_STRING RtlpWin32NtRootSlash = RTL_CONSTANT_STRING(L"\\\\?\\");
38 const UNICODE_STRING RtlpDosSlashCONDevice = RTL_CONSTANT_STRING(L"\\\\.\\CON");
39 const UNICODE_STRING RtlpDosDevicesPrefix = RTL_CONSTANT_STRING(L"\\??\\");
40
41 const UNICODE_STRING RtlpDosLPTDevice = RTL_CONSTANT_STRING(L"LPT");
42 const UNICODE_STRING RtlpDosCOMDevice = RTL_CONSTANT_STRING(L"COM");
43 const UNICODE_STRING RtlpDosPRNDevice = RTL_CONSTANT_STRING(L"PRN");
44 const UNICODE_STRING RtlpDosAUXDevice = RTL_CONSTANT_STRING(L"AUX");
45 const UNICODE_STRING RtlpDosCONDevice = RTL_CONSTANT_STRING(L"CON");
46 const UNICODE_STRING RtlpDosNULDevice = RTL_CONSTANT_STRING(L"NUL");
47
48 PRTLP_CURDIR_REF RtlpCurDirRef;
49
50 /* PRIVATE FUNCTIONS **********************************************************/
51
52 RTL_PATH_TYPE
53 NTAPI
54 RtlDetermineDosPathNameType_Ustr(IN PCUNICODE_STRING PathString)
55 {
56 PWCHAR Path;
57 ULONG Chars;
58
59 Path = PathString->Buffer;
60 Chars = PathString->Length / sizeof(WCHAR);
61
62 /* Return if there are no characters */
63 if (!Chars) return RtlPathTypeRelative;
64
65 /*
66 * The algorithm is similar to RtlDetermineDosPathNameType_U but here we
67 * actually check for the path length before touching the characters
68 */
69 if (IS_PATH_SEPARATOR(Path[0]))
70 {
71 if ((Chars < 2) || !(IS_PATH_SEPARATOR(Path[1]))) return RtlPathTypeRooted; /* \x */
72 if ((Chars < 3) || ((Path[2] != L'.') && (Path[2] != L'?'))) return RtlPathTypeUncAbsolute;/* \\x */
73 if ((Chars >= 4) && (IS_PATH_SEPARATOR(Path[3]))) return RtlPathTypeLocalDevice; /* \\.\x or \\?\x */
74 if (Chars != 3) return RtlPathTypeUncAbsolute; /* \\.x or \\?x */
75 return RtlPathTypeRootLocalDevice; /* \\. or \\? */
76 }
77 else
78 {
79 if ((Chars < 2) || (Path[1] != L':')) return RtlPathTypeRelative; /* x */
80 if ((Chars < 3) || !(IS_PATH_SEPARATOR(Path[2]))) return RtlPathTypeDriveRelative; /* x: */
81 return RtlPathTypeDriveAbsolute; /* x:\ */
82 }
83 }
84
85 ULONG
86 NTAPI
87 RtlIsDosDeviceName_Ustr(IN PCUNICODE_STRING PathString)
88 {
89 UNICODE_STRING PathCopy;
90 PWCHAR Start, End;
91 USHORT PathChars, ColonCount = 0;
92 USHORT ReturnOffset = 0, ReturnLength, OriginalLength;
93 WCHAR c;
94
95 /* Validate the input */
96 if (!PathString) return 0;
97
98 /* Check what type of path this is */
99 switch (RtlDetermineDosPathNameType_Ustr(PathString))
100 {
101 /* Fail for UNC or unknown paths */
102 case RtlPathTypeUnknown:
103 case RtlPathTypeUncAbsolute:
104 return 0;
105
106 /* Make special check for the CON device */
107 case RtlPathTypeLocalDevice:
108 if (RtlEqualUnicodeString(PathString, &RtlpDosSlashCONDevice, TRUE))
109 {
110 /* This should return 0x80006 */
111 return MAKELONG(RtlpDosCONDevice.Length, DeviceRootString.Length);
112 }
113 return 0;
114
115 default:
116 break;
117 }
118
119 /* Make a copy of the string */
120 PathCopy = *PathString;
121 OriginalLength = PathString->Length;
122
123 /* Return if there's no characters */
124 PathChars = PathCopy.Length / sizeof(WCHAR);
125 if (!PathChars) return 0;
126
127 /* Check for drive path and truncate */
128 if (PathCopy.Buffer[PathChars - 1] == L':')
129 {
130 /* Fixup the lengths */
131 PathCopy.Length -= sizeof(WCHAR);
132 if (!--PathChars) return 0;
133
134 /* Remember this for later */
135 ColonCount = 1;
136 }
137
138 /* Check for extension or space, and truncate */
139 do
140 {
141 /* Stop if we hit something else than a space or period */
142 c = PathCopy.Buffer[PathChars - 1];
143 if ((c != L'.') && (c != L' ')) break;
144
145 /* Fixup the lengths */
146 PathCopy.Length -= sizeof(WCHAR);
147
148 /* Remember this for later */
149 ColonCount++;
150 } while (--PathChars);
151
152 /* Anything still left? */
153 if (PathChars)
154 {
155 /* Loop from the end */
156 for (End = &PathCopy.Buffer[PathChars - 1];
157 End >= PathCopy.Buffer;
158 --End)
159 {
160 /* Check if the character is a path or drive separator */
161 c = *End;
162 if (IS_PATH_SEPARATOR(c) || ((c == L':') && (End == PathCopy.Buffer + 1)))
163 {
164 /* Get the next lower case character */
165 End++;
166 c = RtlpDowncaseUnicodeChar(*End);
167
168 /* Check if it's a DOS device (LPT, COM, PRN, AUX, or NUL) */
169 if ((End < &PathCopy.Buffer[OriginalLength / sizeof(WCHAR)]) &&
170 ((c == L'l') || (c == L'c') || (c == L'p') || (c == L'a') || (c == L'n')))
171 {
172 /* Calculate the offset */
173 ReturnOffset = (USHORT)((PCHAR)End - (PCHAR)PathCopy.Buffer);
174
175 /* Build the final string */
176 PathCopy.Length = OriginalLength - ReturnOffset - (ColonCount * sizeof(WCHAR));
177 PathCopy.Buffer = End;
178
179 /* Save new amount of chars in the path */
180 PathChars = PathCopy.Length / sizeof(WCHAR);
181
182 break;
183 }
184 else
185 {
186 return 0;
187 }
188 }
189 }
190
191 /* Get the next lower case character and check if it's a DOS device */
192 c = RtlpDowncaseUnicodeChar(*PathCopy.Buffer);
193 if ((c != L'l') && (c != L'c') && (c != L'p') && (c != L'a') && (c != L'n'))
194 {
195 /* Not LPT, COM, PRN, AUX, or NUL */
196 return 0;
197 }
198 }
199
200 /* Now skip past any extra extension or drive letter characters */
201 Start = PathCopy.Buffer;
202 End = &Start[PathChars];
203 while (Start < End)
204 {
205 c = *Start;
206 if ((c == L'.') || (c == L':')) break;
207 Start++;
208 }
209
210 /* And then go backwards to get rid of spaces */
211 while ((Start > PathCopy.Buffer) && (Start[-1] == L' ')) --Start;
212
213 /* Finally see how many characters are left, and that's our size */
214 PathChars = (USHORT)(Start - PathCopy.Buffer);
215 PathCopy.Length = PathChars * sizeof(WCHAR);
216
217 /* Check if this is a COM or LPT port, which has a digit after it */
218 if ((PathChars == 4) &&
219 (iswdigit(PathCopy.Buffer[3]) && (PathCopy.Buffer[3] != L'0')))
220 {
221 /* Don't compare the number part, just check for LPT or COM */
222 PathCopy.Length -= sizeof(WCHAR);
223 if ((RtlEqualUnicodeString(&PathCopy, &RtlpDosLPTDevice, TRUE)) ||
224 (RtlEqualUnicodeString(&PathCopy, &RtlpDosCOMDevice, TRUE)))
225 {
226 /* Found it */
227 ReturnLength = sizeof(L"COM1") - sizeof(WCHAR);
228 return MAKELONG(ReturnLength, ReturnOffset);
229 }
230 }
231 else if ((PathChars == 3) &&
232 ((RtlEqualUnicodeString(&PathCopy, &RtlpDosPRNDevice, TRUE)) ||
233 (RtlEqualUnicodeString(&PathCopy, &RtlpDosAUXDevice, TRUE)) ||
234 (RtlEqualUnicodeString(&PathCopy, &RtlpDosNULDevice, TRUE)) ||
235 (RtlEqualUnicodeString(&PathCopy, &RtlpDosCONDevice, TRUE))))
236 {
237 /* Otherwise this was something like AUX, NUL, PRN, or CON */
238 ReturnLength = sizeof(L"AUX") - sizeof(WCHAR);
239 return MAKELONG(ReturnLength, ReturnOffset);
240 }
241
242 /* Otherwise, this is not a valid DOS device */
243 return 0;
244 }
245
246 NTSTATUS
247 NTAPI
248 RtlpCheckDeviceName(IN PUNICODE_STRING FileName,
249 IN ULONG Length,
250 OUT PBOOLEAN NameInvalid)
251 {
252 PWCHAR Buffer;
253 NTSTATUS Status;
254
255 /* Allocate a large enough buffer */
256 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, FileName->Length);
257 if (Buffer)
258 {
259 /* Assume failure */
260 *NameInvalid = TRUE;
261
262 /* Copy the filename */
263 RtlCopyMemory(Buffer, FileName->Buffer, FileName->Length);
264
265 /* And add a dot at the end */
266 Buffer[Length / sizeof(WCHAR)] = L'.';
267 Buffer[(Length / sizeof(WCHAR)) + 1] = UNICODE_NULL;
268
269 /* Check if the file exists or not */
270 *NameInvalid = RtlDoesFileExists_U(Buffer) ? FALSE: TRUE;
271
272 /* Get rid of the buffer now */
273 Status = RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
274 }
275 else
276 {
277 /* Assume the name is ok, but fail the call */
278 *NameInvalid = FALSE;
279 Status = STATUS_NO_MEMORY;
280 }
281
282 /* Return the status */
283 return Status;
284 }
285
286
287
288 /******************************************************************
289 * RtlpCollapsePath (from WINE)
290 *
291 * Helper for RtlGetFullPathName_U
292 *
293 * 1) Converts slashes into backslashes and gets rid of duplicated ones;
294 * 2) Gets rid of . and .. components in the path.
295 *
296 * Returns the full path length without its terminating NULL character.
297 */
298 static ULONG
299 RtlpCollapsePath(PWSTR Path, /* ULONG PathBufferSize, ULONG PathLength, */ ULONG mark, BOOLEAN SkipTrailingPathSeparators)
300 {
301 PWSTR p, next;
302
303 // FIXME: Do not suppose NULL-terminated strings!!
304
305 ULONG PathLength = wcslen(Path);
306 PWSTR EndBuffer = Path + PathLength; // Path + PathBufferSize / sizeof(WCHAR);
307 PWSTR EndPath;
308
309 /* Convert slashes into backslashes */
310 for (p = Path; *p; p++)
311 {
312 if (*p == L'/') *p = L'\\';
313 }
314
315 /* Collapse duplicate backslashes */
316 next = Path + max( 1, mark );
317 for (p = next; *p; p++)
318 {
319 if (*p != L'\\' || next[-1] != L'\\') *next++ = *p;
320 }
321 *next = UNICODE_NULL;
322 EndPath = next;
323
324 p = Path + mark;
325 while (*p)
326 {
327 if (*p == L'.')
328 {
329 switch (p[1])
330 {
331 case UNICODE_NULL: /* final . */
332 if (p > Path + mark) p--;
333 *p = UNICODE_NULL;
334 EndPath = p;
335 continue;
336
337 case L'\\': /* .\ component */
338 next = p + 2;
339 // ASSERT(EndPath - next == wcslen(next));
340 RtlMoveMemory(p, next, (EndPath - next + 1) * sizeof(WCHAR));
341 EndPath -= (next - p);
342 continue;
343
344 case L'.':
345 if (p[2] == L'\\') /* ..\ component */
346 {
347 next = p + 3;
348 if (p > Path + mark)
349 {
350 p--;
351 while (p > Path + mark && p[-1] != L'\\') p--;
352 }
353 // ASSERT(EndPath - next == wcslen(next));
354 RtlMoveMemory(p, next, (EndPath - next + 1) * sizeof(WCHAR));
355 EndPath -= (next - p);
356 continue;
357 }
358 else if (p[2] == UNICODE_NULL) /* final .. */
359 {
360 if (p > Path + mark)
361 {
362 p--;
363 while (p > Path + mark && p[-1] != L'\\') p--;
364 if (p > Path + mark) p--;
365 }
366 *p = UNICODE_NULL;
367 EndPath = p;
368 continue;
369 }
370 break;
371 }
372 }
373
374 /* Skip to the next component */
375 while (*p && *p != L'\\') p++;
376 if (*p == L'\\')
377 {
378 /* Remove last dot in previous dir name */
379 if (p > Path + mark && p[-1] == L'.')
380 {
381 // ASSERT(EndPath - p == wcslen(p));
382 RtlMoveMemory(p - 1, p, (EndPath - p + 1) * sizeof(WCHAR));
383 EndPath--;
384 }
385 else
386 {
387 p++;
388 }
389 }
390 }
391
392 /* Remove trailing backslashes if needed (after the UNC part if it exists) */
393 if (SkipTrailingPathSeparators)
394 {
395 while (p > Path + mark && IS_PATH_SEPARATOR(p[-1])) p--;
396 }
397
398 /* Remove trailing spaces and dots (for all the path) */
399 while (p > Path && (p[-1] == L' ' || p[-1] == L'.')) p--;
400
401 /*
402 * Zero-out the discarded buffer zone, starting just after
403 * the path string and going up to the end of the buffer.
404 * It also NULL-terminate the path string.
405 */
406 ASSERT(EndBuffer >= p);
407 RtlZeroMemory(p, (EndBuffer - p + 1) * sizeof(WCHAR));
408
409 /* Return the real path length */
410 PathLength = (p - Path);
411 // ASSERT(PathLength == wcslen(Path));
412 return (PathLength * sizeof(WCHAR));
413 }
414
415 /******************************************************************
416 * RtlpSkipUNCPrefix (from WINE)
417 *
418 * Helper for RtlGetFullPathName_U
419 *
420 * Skips the \\share\dir part of a file name and returns the new position
421 * (which can point on the last backslash of "dir\").
422 */
423 static SIZE_T
424 RtlpSkipUNCPrefix(PCWSTR FileNameBuffer)
425 {
426 PCWSTR UncPath = FileNameBuffer + 2;
427 DPRINT("RtlpSkipUNCPrefix(%S)\n", FileNameBuffer);
428
429 while (*UncPath && !IS_PATH_SEPARATOR(*UncPath)) UncPath++; /* share name */
430 while (IS_PATH_SEPARATOR(*UncPath)) UncPath++;
431 while (*UncPath && !IS_PATH_SEPARATOR(*UncPath)) UncPath++; /* dir name */
432 /* while (IS_PATH_SEPARATOR(*UncPath)) UncPath++; */
433
434 return (UncPath - FileNameBuffer);
435 }
436
437 NTSTATUS
438 NTAPI
439 RtlpApplyLengthFunction(IN ULONG Flags,
440 IN ULONG Type,
441 IN PVOID UnicodeStringOrUnicodeStringBuffer,
442 IN PVOID LengthFunction)
443 {
444 UNIMPLEMENTED;
445 return STATUS_NOT_IMPLEMENTED;
446 }
447
448 NTSTATUS
449 NTAPI
450 RtlGetLengthWithoutLastFullDosOrNtPathElement(IN ULONG Flags,
451 IN PWCHAR Path,
452 OUT PULONG LengthOut)
453 {
454 UNIMPLEMENTED;
455 return STATUS_NOT_IMPLEMENTED;
456 }
457
458 NTSTATUS
459 NTAPI
460 RtlComputePrivatizedDllName_U(IN PUNICODE_STRING DllName,
461 IN PUNICODE_STRING a2,
462 IN PUNICODE_STRING a3)
463 {
464 UNIMPLEMENTED;
465 return STATUS_NOT_IMPLEMENTED;
466 }
467
468 ULONG
469 NTAPI
470 RtlGetFullPathName_Ustr(
471 _In_ PUNICODE_STRING FileName,
472 _In_ ULONG Size,
473 _Out_z_bytecap_(Size) PWSTR Buffer,
474 _Out_opt_ PCWSTR *ShortName,
475 _Out_opt_ PBOOLEAN InvalidName,
476 _Out_ RTL_PATH_TYPE *PathType)
477 {
478 NTSTATUS Status;
479 PWCHAR FileNameBuffer;
480 ULONG FileNameLength, FileNameChars, DosLength, DosLengthOffset, FullLength;
481 BOOLEAN SkipTrailingPathSeparators;
482 WCHAR c;
483
484
485 ULONG reqsize = 0;
486 PCWSTR ptr;
487
488 PCUNICODE_STRING CurDirName;
489 UNICODE_STRING EnvVarName, EnvVarValue;
490 WCHAR EnvVarNameBuffer[4];
491
492 ULONG PrefixCut = 0; // Where the path really starts (after the skipped prefix)
493 PWCHAR Prefix = NULL; // pointer to the string to be inserted as the new path prefix
494 ULONG PrefixLength = 0;
495 PWCHAR Source;
496 ULONG SourceLength;
497
498
499 /* For now, assume the name is valid */
500 DPRINT("Filename: %wZ\n", FileName);
501 DPRINT("Size and buffer: %lx %p\n", Size, Buffer);
502 if (InvalidName) *InvalidName = FALSE;
503
504 /* Handle initial path type and failure case */
505 *PathType = RtlPathTypeUnknown;
506 if ((FileName->Length == 0) || (FileName->Buffer[0] == UNICODE_NULL)) return 0;
507
508 /* Break filename into component parts */
509 FileNameBuffer = FileName->Buffer;
510 FileNameLength = FileName->Length;
511 FileNameChars = FileNameLength / sizeof(WCHAR);
512
513 /* Kill trailing spaces */
514 c = FileNameBuffer[FileNameChars - 1];
515 while ((FileNameLength != 0) && (c == L' '))
516 {
517 /* Keep going, ignoring the spaces */
518 FileNameLength -= sizeof(WCHAR);
519 if (FileNameLength != 0) c = FileNameBuffer[FileNameLength / sizeof(WCHAR) - 1];
520 }
521
522 /* Check if anything is left */
523 if (FileNameLength == 0) return 0;
524
525 /*
526 * Check whether we'll need to skip trailing path separators in the
527 * computed full path name. If the original file name already contained
528 * trailing separators, then we keep them in the full path name. On the
529 * other hand, if the original name didn't contain any trailing separators
530 * then we'll skip it in the full path name.
531 */
532 SkipTrailingPathSeparators = !IS_PATH_SEPARATOR(FileNameBuffer[FileNameChars - 1]);
533
534 /* Check if this is a DOS name */
535 DosLength = RtlIsDosDeviceName_Ustr(FileName);
536 DPRINT("DOS length for filename: %lx %wZ\n", DosLength, FileName);
537 if (DosLength != 0)
538 {
539 /* Zero out the short name */
540 if (ShortName) *ShortName = NULL;
541
542 /* See comment for RtlIsDosDeviceName_Ustr if this is confusing... */
543 DosLengthOffset = HIWORD(DosLength);
544 DosLength = LOWORD(DosLength);
545
546 /* Do we have a DOS length, and does the caller want validity? */
547 if (InvalidName && (DosLengthOffset != 0))
548 {
549 /* Do the check */
550 Status = RtlpCheckDeviceName(FileName, DosLengthOffset, InvalidName);
551
552 /* If the check failed, or the name is invalid, fail here */
553 if (!NT_SUCCESS(Status)) return 0;
554 if (*InvalidName) return 0;
555 }
556
557 /* Add the size of the device root and check if it fits in the size */
558 FullLength = DosLength + DeviceRootString.Length;
559 if (FullLength < Size)
560 {
561 /* Add the device string */
562 RtlMoveMemory(Buffer, DeviceRootString.Buffer, DeviceRootString.Length);
563
564 /* Now add the DOS device name */
565 RtlMoveMemory((PCHAR)Buffer + DeviceRootString.Length,
566 (PCHAR)FileNameBuffer + DosLengthOffset,
567 DosLength);
568
569 /* Null terminate */
570 *(PWCHAR)((ULONG_PTR)Buffer + FullLength) = UNICODE_NULL;
571 return FullLength;
572 }
573
574 /* Otherwise, there's no space, so return the buffer size needed */
575 if ((FullLength + sizeof(UNICODE_NULL)) > UNICODE_STRING_MAX_BYTES) return 0;
576 return FullLength + sizeof(UNICODE_NULL);
577 }
578
579 /* Zero-out the destination buffer. FileName must be different from Buffer */
580 RtlZeroMemory(Buffer, Size);
581
582 /* Get the path type */
583 *PathType = RtlDetermineDosPathNameType_U(FileNameBuffer);
584
585
586
587 /**********************************************
588 ** CODE REWRITING IS HAPPENING THERE **
589 **********************************************/
590 Source = FileNameBuffer;
591 SourceLength = FileNameLength;
592 EnvVarValue.Buffer = NULL;
593
594 /* Lock the PEB to get the current directory */
595 RtlAcquirePebLock();
596 CurDirName = &NtCurrentPeb()->ProcessParameters->CurrentDirectory.DosPath;
597
598 switch (*PathType)
599 {
600 case RtlPathTypeUncAbsolute: /* \\foo */
601 {
602 PrefixCut = RtlpSkipUNCPrefix(FileNameBuffer);
603 break;
604 }
605
606 case RtlPathTypeLocalDevice: /* \\.\foo */
607 {
608 PrefixCut = 4;
609 break;
610 }
611
612 case RtlPathTypeDriveAbsolute: /* c:\foo */
613 {
614 ASSERT(FileNameBuffer[1] == L':');
615 ASSERT(IS_PATH_SEPARATOR(FileNameBuffer[2]));
616
617 // FileNameBuffer[0] = RtlpUpcaseUnicodeChar(FileNameBuffer[0]);
618 Prefix = FileNameBuffer;
619 PrefixLength = 3 * sizeof(WCHAR);
620 Source += 3;
621 SourceLength -= 3 * sizeof(WCHAR);
622
623 PrefixCut = 3;
624 break;
625 }
626
627 case RtlPathTypeDriveRelative: /* c:foo */
628 {
629 WCHAR CurDrive, NewDrive;
630
631 Source += 2;
632 SourceLength -= 2 * sizeof(WCHAR);
633
634 CurDrive = RtlpUpcaseUnicodeChar(CurDirName->Buffer[0]);
635 NewDrive = RtlpUpcaseUnicodeChar(FileNameBuffer[0]);
636
637 if ((NewDrive != CurDrive) || CurDirName->Buffer[1] != L':')
638 {
639 EnvVarNameBuffer[0] = L'=';
640 EnvVarNameBuffer[1] = NewDrive;
641 EnvVarNameBuffer[2] = L':';
642 EnvVarNameBuffer[3] = UNICODE_NULL;
643
644 EnvVarName.Length = 3 * sizeof(WCHAR);
645 EnvVarName.MaximumLength = EnvVarName.Length + sizeof(WCHAR);
646 EnvVarName.Buffer = EnvVarNameBuffer;
647
648 // FIXME: Is it possible to use the user-given buffer ?
649 // RtlInitEmptyUnicodeString(&EnvVarValue, NULL, Size);
650 EnvVarValue.Length = 0;
651 EnvVarValue.MaximumLength = (USHORT)Size;
652 EnvVarValue.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Size);
653 if (EnvVarValue.Buffer == NULL)
654 {
655 Prefix = NULL;
656 PrefixLength = 0;
657 goto Quit;
658 }
659
660 Status = RtlQueryEnvironmentVariable_U(NULL, &EnvVarName, &EnvVarValue);
661 switch (Status)
662 {
663 case STATUS_SUCCESS:
664 /*
665 * (From Wine)
666 * FIXME: Win2k seems to check that the environment
667 * variable actually points to an existing directory.
668 * If not, root of the drive is used (this seems also
669 * to be the only place in RtlGetFullPathName that the
670 * existence of a part of a path is checked).
671 */
672 EnvVarValue.Buffer[EnvVarValue.Length / sizeof(WCHAR)] = L'\\';
673 Prefix = EnvVarValue.Buffer;
674 PrefixLength = EnvVarValue.Length + sizeof(WCHAR); /* Append trailing '\\' */
675 break;
676
677 case STATUS_BUFFER_TOO_SMALL:
678 reqsize = EnvVarValue.Length + SourceLength + sizeof(UNICODE_NULL);
679 goto Quit;
680
681 default:
682 DPRINT1("RtlQueryEnvironmentVariable_U returned 0x%08lx\n", Status);
683
684 EnvVarNameBuffer[0] = NewDrive;
685 EnvVarNameBuffer[1] = L':';
686 EnvVarNameBuffer[2] = L'\\';
687 EnvVarNameBuffer[3] = UNICODE_NULL;
688 Prefix = EnvVarNameBuffer;
689 PrefixLength = 3 * sizeof(WCHAR);
690
691 RtlFreeHeap(RtlGetProcessHeap(), 0, EnvVarValue.Buffer);
692 EnvVarValue.Buffer = NULL;
693 break;
694 }
695 PrefixCut = 3;
696 break;
697 }
698 /* Fall through */
699 DPRINT("RtlPathTypeDriveRelative - Using fall-through to RtlPathTypeRelative\n");
700 }
701
702 case RtlPathTypeRelative: /* foo */
703 {
704 Prefix = CurDirName->Buffer;
705 PrefixLength = CurDirName->Length;
706 if (CurDirName->Buffer[1] != L':')
707 {
708 PrefixCut = RtlpSkipUNCPrefix(CurDirName->Buffer);
709 }
710 else
711 {
712 PrefixCut = 3;
713 }
714 break;
715 }
716
717 case RtlPathTypeRooted: /* \xxx */
718 {
719 if (CurDirName->Buffer[1] == L':')
720 {
721 // The path starts with "C:\"
722 ASSERT(CurDirName->Buffer[1] == L':');
723 ASSERT(IS_PATH_SEPARATOR(CurDirName->Buffer[2]));
724
725 Prefix = CurDirName->Buffer;
726 PrefixLength = 3 * sizeof(WCHAR); // Skip "C:\"
727
728 PrefixCut = 3; // Source index location incremented of + 3
729 }
730 else
731 {
732 PrefixCut = RtlpSkipUNCPrefix(CurDirName->Buffer);
733 PrefixLength = PrefixCut * sizeof(WCHAR);
734 Prefix = CurDirName->Buffer;
735 }
736 break;
737 }
738
739 case RtlPathTypeRootLocalDevice: /* \\. */
740 {
741 Prefix = DeviceRootString.Buffer;
742 PrefixLength = DeviceRootString.Length;
743 Source += 3;
744 SourceLength -= 3 * sizeof(WCHAR);
745
746 PrefixCut = 4;
747 break;
748 }
749
750 case RtlPathTypeUnknown:
751 goto Quit;
752 }
753
754 /* Do we have enough space for storing the full path? */
755 reqsize = PrefixLength;
756 if (reqsize + SourceLength + sizeof(WCHAR) > Size)
757 {
758 /* Not enough space, return needed size (including terminating '\0') */
759 reqsize += SourceLength + sizeof(WCHAR);
760 goto Quit;
761 }
762
763 /*
764 * Build the full path
765 */
766 /* Copy the path's prefix */
767 if (PrefixLength) RtlMoveMemory(Buffer, Prefix, PrefixLength);
768 /* Copy the remaining part of the path */
769 RtlMoveMemory(Buffer + PrefixLength / sizeof(WCHAR), Source, SourceLength + sizeof(WCHAR));
770
771 /* Some cleanup */
772 Prefix = NULL;
773 if (EnvVarValue.Buffer)
774 {
775 RtlFreeHeap(RtlGetProcessHeap(), 0, EnvVarValue.Buffer);
776 EnvVarValue.Buffer = NULL;
777 }
778
779 /*
780 * Finally, put the path in canonical form (remove redundant . and ..,
781 * (back)slashes...) and retrieve the length of the full path name
782 * (without its terminating null character) (in chars).
783 */
784 reqsize = RtlpCollapsePath(Buffer, /* Size, reqsize, */ PrefixCut, SkipTrailingPathSeparators);
785
786 /* Find the file part, which is present after the last path separator */
787 if (ShortName)
788 {
789 ptr = wcsrchr(Buffer, L'\\');
790 if (ptr) ++ptr; // Skip it
791
792 /*
793 * For UNC paths, the file part is after the \\share\dir part of the path.
794 */
795 PrefixCut = (*PathType == RtlPathTypeUncAbsolute ? PrefixCut : 3);
796
797 if (ptr && *ptr && (ptr >= Buffer + PrefixCut))
798 {
799 *ShortName = ptr;
800 }
801 else
802 {
803 /* Zero-out the short name */
804 *ShortName = NULL;
805 }
806 }
807
808 Quit:
809 /* Release PEB lock */
810 RtlReleasePebLock();
811
812 return reqsize;
813 }
814
815 NTSTATUS
816 NTAPI
817 RtlpWin32NTNameToNtPathName_U(IN PUNICODE_STRING DosPath,
818 OUT PUNICODE_STRING NtPath,
819 OUT PCWSTR *PartName,
820 OUT PRTL_RELATIVE_NAME_U RelativeName)
821 {
822 ULONG DosLength;
823 PWSTR NewBuffer, p;
824
825 /* Validate the input */
826 if (!DosPath) return STATUS_OBJECT_NAME_INVALID;
827
828 /* Validate the DOS length */
829 DosLength = DosPath->Length;
830 if (DosLength >= UNICODE_STRING_MAX_BYTES) return STATUS_NAME_TOO_LONG;
831
832 /* Make space for the new path */
833 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
834 0,
835 DosLength + sizeof(UNICODE_NULL));
836 if (!NewBuffer) return STATUS_NO_MEMORY;
837
838 /* Copy the prefix, and then the rest of the DOS path, and NULL-terminate */
839 RtlCopyMemory(NewBuffer, RtlpDosDevicesPrefix.Buffer, RtlpDosDevicesPrefix.Length);
840 RtlCopyMemory((PCHAR)NewBuffer + RtlpDosDevicesPrefix.Length,
841 DosPath->Buffer + RtlpDosDevicesPrefix.Length / sizeof(WCHAR),
842 DosPath->Length - RtlpDosDevicesPrefix.Length);
843 NewBuffer[DosLength / sizeof(WCHAR)] = UNICODE_NULL;
844
845 /* Did the caller send a relative name? */
846 if (RelativeName)
847 {
848 /* Zero initialize it */
849 RtlInitEmptyUnicodeString(&RelativeName->RelativeName, NULL, 0);
850 RelativeName->ContainingDirectory = NULL;
851 RelativeName->CurDirRef = 0;
852 }
853
854 /* Did the caller request a partial name? */
855 if (PartName)
856 {
857 /* Loop from the back until we find a path separator */
858 p = &NewBuffer[DosLength / sizeof(WCHAR)];
859 while (--p > NewBuffer)
860 {
861 /* We found a path separator, move past it */
862 if (*p == OBJ_NAME_PATH_SEPARATOR)
863 {
864 ++p;
865 break;
866 }
867 }
868
869 /* Check whether a separator was found and if something remains */
870 if ((p > NewBuffer) && *p)
871 {
872 /* What follows the path separator is the partial name */
873 *PartName = p;
874 }
875 else
876 {
877 /* The path ends with a path separator, no partial name */
878 *PartName = NULL;
879 }
880 }
881
882 /* Build the final NT path string */
883 NtPath->Buffer = NewBuffer;
884 NtPath->Length = (USHORT)DosLength;
885 NtPath->MaximumLength = (USHORT)DosLength + sizeof(UNICODE_NULL);
886 return STATUS_SUCCESS;
887 }
888
889 NTSTATUS
890 NTAPI
891 RtlpDosPathNameToRelativeNtPathName_Ustr(IN BOOLEAN HaveRelative,
892 IN PCUNICODE_STRING DosName,
893 OUT PUNICODE_STRING NtName,
894 OUT PCWSTR *PartName,
895 OUT PRTL_RELATIVE_NAME_U RelativeName)
896 {
897 WCHAR BigBuffer[MAX_PATH + 1];
898 PWCHAR PrefixBuffer, NewBuffer, Buffer;
899 ULONG MaxLength, PathLength, PrefixLength, PrefixCut, LengthChars, Length;
900 UNICODE_STRING CapturedDosName, PartNameString, FullPath;
901 BOOLEAN QuickPath;
902 RTL_PATH_TYPE InputPathType, BufferPathType;
903 NTSTATUS Status;
904 BOOLEAN NameInvalid;
905 PCURDIR CurrentDirectory;
906
907 /* Assume MAX_PATH for now */
908 DPRINT("Relative: %lx DosName: %wZ NtName: %p, PartName: %p, RelativeName: %p\n",
909 HaveRelative, DosName, NtName, PartName, RelativeName);
910 MaxLength = sizeof(BigBuffer);
911
912 /* Validate the input */
913 if (!DosName) return STATUS_OBJECT_NAME_INVALID;
914
915 /* Capture input string */
916 CapturedDosName = *DosName;
917
918 /* Check for the presence or absence of the NT prefix "\\?\" form */
919 // if (!RtlPrefixUnicodeString(&RtlpWin32NtRootSlash, &CapturedDosName, FALSE))
920 if ((CapturedDosName.Length <= RtlpWin32NtRootSlash.Length) ||
921 (CapturedDosName.Buffer[0] != RtlpWin32NtRootSlash.Buffer[0]) ||
922 (CapturedDosName.Buffer[1] != RtlpWin32NtRootSlash.Buffer[1]) ||
923 (CapturedDosName.Buffer[2] != RtlpWin32NtRootSlash.Buffer[2]) ||
924 (CapturedDosName.Buffer[3] != RtlpWin32NtRootSlash.Buffer[3]))
925 {
926 /* NT prefix not present */
927
928 /* Quick path won't be used */
929 QuickPath = FALSE;
930
931 /* Use the static buffer */
932 Buffer = BigBuffer;
933 MaxLength += RtlpDosDevicesUncPrefix.Length;
934
935 /* Allocate a buffer to hold the path */
936 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, MaxLength);
937 DPRINT("MaxLength: %lx\n", MaxLength);
938 if (!NewBuffer) return STATUS_NO_MEMORY;
939 }
940 else
941 {
942 /* NT prefix present */
943
944 /* Use the optimized path after acquiring the lock */
945 QuickPath = TRUE;
946 NewBuffer = NULL;
947 }
948
949 /* Lock the PEB and check if the quick path can be used */
950 RtlAcquirePebLock();
951 if (QuickPath)
952 {
953 /* Some simple fixups will get us the correct path */
954 DPRINT("Quick path\n");
955 Status = RtlpWin32NTNameToNtPathName_U(&CapturedDosName,
956 NtName,
957 PartName,
958 RelativeName);
959
960 /* Release the lock, we're done here */
961 RtlReleasePebLock();
962 return Status;
963 }
964
965 /* Call the main function to get the full path name and length */
966 PathLength = RtlGetFullPathName_Ustr(&CapturedDosName,
967 MAX_PATH * sizeof(WCHAR),
968 Buffer,
969 PartName,
970 &NameInvalid,
971 &InputPathType);
972 if ((NameInvalid) || !(PathLength) || (PathLength > (MAX_PATH * sizeof(WCHAR))))
973 {
974 /* Invalid name, fail */
975 DPRINT("Invalid name: %lx Path Length: %lx\n", NameInvalid, PathLength);
976 RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
977 RtlReleasePebLock();
978 return STATUS_OBJECT_NAME_INVALID;
979 }
980
981 /* Start by assuming the path starts with \??\ (DOS Devices Path) */
982 PrefixLength = RtlpDosDevicesPrefix.Length;
983 PrefixBuffer = RtlpDosDevicesPrefix.Buffer;
984 PrefixCut = 0;
985
986 /* Check where it really is */
987 BufferPathType = RtlDetermineDosPathNameType_U(Buffer);
988 DPRINT("Buffer: %S Type: %lx\n", Buffer, BufferPathType);
989 switch (BufferPathType)
990 {
991 /* It's actually a UNC path in \??\UNC\ */
992 case RtlPathTypeUncAbsolute:
993 PrefixLength = RtlpDosDevicesUncPrefix.Length;
994 PrefixBuffer = RtlpDosDevicesUncPrefix.Buffer;
995 PrefixCut = 2;
996 break;
997
998 case RtlPathTypeLocalDevice:
999 /* We made a good guess, go with it but skip the \??\ */
1000 PrefixCut = 4;
1001 break;
1002
1003 case RtlPathTypeDriveAbsolute:
1004 case RtlPathTypeDriveRelative:
1005 case RtlPathTypeRooted:
1006 case RtlPathTypeRelative:
1007 /* Our guess was good, roll with it */
1008 break;
1009
1010 /* Nothing else is expected */
1011 default:
1012 ASSERT(FALSE);
1013 }
1014
1015 /* Now copy the prefix and the buffer */
1016 RtlCopyMemory(NewBuffer, PrefixBuffer, PrefixLength);
1017 RtlCopyMemory((PCHAR)NewBuffer + PrefixLength,
1018 Buffer + PrefixCut,
1019 PathLength - (PrefixCut * sizeof(WCHAR)));
1020
1021 /* Compute the length */
1022 Length = PathLength + PrefixLength - PrefixCut * sizeof(WCHAR);
1023 LengthChars = Length / sizeof(WCHAR);
1024
1025 /* Setup the actual NT path string and terminate it */
1026 NtName->Buffer = NewBuffer;
1027 NtName->Length = (USHORT)Length;
1028 NtName->MaximumLength = (USHORT)MaxLength;
1029 NewBuffer[LengthChars] = UNICODE_NULL;
1030 DPRINT("New buffer: %S\n", NewBuffer);
1031 DPRINT("NT Name: %wZ\n", NtName);
1032
1033 /* Check if a partial name was requested */
1034 if ((PartName) && (*PartName))
1035 {
1036 /* Convert to Unicode */
1037 Status = RtlInitUnicodeStringEx(&PartNameString, *PartName);
1038 if (NT_SUCCESS(Status))
1039 {
1040 /* Set the partial name */
1041 *PartName = &NewBuffer[LengthChars - (PartNameString.Length / sizeof(WCHAR))];
1042 }
1043 else
1044 {
1045 /* Fail */
1046 RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
1047 RtlReleasePebLock();
1048 return Status;
1049 }
1050 }
1051
1052 /* Check if a relative name was asked for */
1053 if (RelativeName)
1054 {
1055 /* Setup the structure */
1056 RtlInitEmptyUnicodeString(&RelativeName->RelativeName, NULL, 0);
1057 RelativeName->ContainingDirectory = NULL;
1058 RelativeName->CurDirRef = NULL;
1059
1060 /* Check if the input path itself was relative */
1061 if (InputPathType == RtlPathTypeRelative)
1062 {
1063 /* Get current directory */
1064 CurrentDirectory = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
1065 if (CurrentDirectory->Handle)
1066 {
1067 Status = RtlInitUnicodeStringEx(&FullPath, Buffer);
1068 if (!NT_SUCCESS(Status))
1069 {
1070 RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
1071 RtlReleasePebLock();
1072 return Status;
1073 }
1074
1075 /* If current directory is bigger than full path, there's no way */
1076 if (CurrentDirectory->DosPath.Length > FullPath.Length)
1077 {
1078 RtlReleasePebLock();
1079 return Status;
1080 }
1081
1082 /* File is in current directory */
1083 if (RtlEqualUnicodeString(&FullPath, &CurrentDirectory->DosPath, TRUE))
1084 {
1085 /* Make relative name string */
1086 RelativeName->RelativeName.Buffer = (PWSTR)((ULONG_PTR)NewBuffer + PrefixLength + FullPath.Length - PrefixCut * sizeof(WCHAR));
1087 RelativeName->RelativeName.Length = (USHORT)(PathLength - FullPath.Length);
1088 /* If relative name starts with \, skip it */
1089 if (RelativeName->RelativeName.Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
1090 {
1091 RelativeName->RelativeName.Buffer++;
1092 RelativeName->RelativeName.Length -= sizeof(WCHAR);
1093 }
1094 RelativeName->RelativeName.MaximumLength = RelativeName->RelativeName.Length;
1095 DPRINT("RelativeName: %wZ\n", &(RelativeName->RelativeName));
1096
1097 if (!HaveRelative)
1098 {
1099 RelativeName->ContainingDirectory = CurrentDirectory->Handle;
1100 return Status;
1101 }
1102
1103 /* Give back current directory data & reference counter */
1104 RelativeName->CurDirRef = RtlpCurDirRef;
1105 if (RelativeName->CurDirRef)
1106 {
1107 InterlockedIncrement(&RtlpCurDirRef->RefCount);
1108 }
1109
1110 RelativeName->ContainingDirectory = CurrentDirectory->Handle;
1111 }
1112 }
1113 }
1114 }
1115
1116 /* Done */
1117 RtlReleasePebLock();
1118 return STATUS_SUCCESS;
1119 }
1120
1121 NTSTATUS
1122 NTAPI
1123 RtlpDosPathNameToRelativeNtPathName_U(IN BOOLEAN HaveRelative,
1124 IN PCWSTR DosName,
1125 OUT PUNICODE_STRING NtName,
1126 OUT PCWSTR *PartName,
1127 OUT PRTL_RELATIVE_NAME_U RelativeName)
1128 {
1129 NTSTATUS Status;
1130 UNICODE_STRING NameString;
1131
1132 /* Create the unicode name */
1133 Status = RtlInitUnicodeStringEx(&NameString, DosName);
1134 if (NT_SUCCESS(Status))
1135 {
1136 /* Call the unicode function */
1137 Status = RtlpDosPathNameToRelativeNtPathName_Ustr(HaveRelative,
1138 &NameString,
1139 NtName,
1140 PartName,
1141 RelativeName);
1142 }
1143
1144 /* Return status */
1145 return Status;
1146 }
1147
1148 BOOLEAN
1149 NTAPI
1150 RtlDosPathNameToRelativeNtPathName_Ustr(IN PCUNICODE_STRING DosName,
1151 OUT PUNICODE_STRING NtName,
1152 OUT PCWSTR *PartName,
1153 OUT PRTL_RELATIVE_NAME_U RelativeName)
1154 {
1155 /* Call the internal function */
1156 ASSERT(RelativeName);
1157 return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_Ustr(TRUE,
1158 DosName,
1159 NtName,
1160 PartName,
1161 RelativeName));
1162 }
1163
1164 BOOLEAN
1165 NTAPI
1166 RtlDoesFileExists_UstrEx(IN PCUNICODE_STRING FileName,
1167 IN BOOLEAN SucceedIfBusy)
1168 {
1169 BOOLEAN Result;
1170 RTL_RELATIVE_NAME_U RelativeName;
1171 UNICODE_STRING NtPathName;
1172 PVOID Buffer;
1173 OBJECT_ATTRIBUTES ObjectAttributes;
1174 NTSTATUS Status;
1175 FILE_BASIC_INFORMATION BasicInformation;
1176
1177 /* Get the NT Path */
1178 Result = RtlDosPathNameToRelativeNtPathName_Ustr(FileName,
1179 &NtPathName,
1180 NULL,
1181 &RelativeName);
1182 if (!Result) return FALSE;
1183
1184 /* Save the buffer */
1185 Buffer = NtPathName.Buffer;
1186
1187 /* Check if we have a relative name */
1188 if (RelativeName.RelativeName.Length)
1189 {
1190 /* Use it */
1191 NtPathName = RelativeName.RelativeName;
1192 }
1193 else
1194 {
1195 /* Otherwise ignore it */
1196 RelativeName.ContainingDirectory = NULL;
1197 }
1198
1199 /* Initialize the object attributes */
1200 InitializeObjectAttributes(&ObjectAttributes,
1201 &NtPathName,
1202 OBJ_CASE_INSENSITIVE,
1203 RelativeName.ContainingDirectory,
1204 NULL);
1205
1206 /* Query the attributes and free the buffer now */
1207 Status = ZwQueryAttributesFile(&ObjectAttributes, &BasicInformation);
1208 RtlReleaseRelativeName(&RelativeName);
1209 RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
1210
1211 /* Check if we failed */
1212 if (!NT_SUCCESS(Status))
1213 {
1214 /* Check if we failed because the file is in use */
1215 if ((Status == STATUS_SHARING_VIOLATION) ||
1216 (Status == STATUS_ACCESS_DENIED))
1217 {
1218 /* Check if the caller wants this to be considered OK */
1219 Result = SucceedIfBusy ? TRUE : FALSE;
1220 }
1221 else
1222 {
1223 /* A failure because the file didn't exist */
1224 Result = FALSE;
1225 }
1226 }
1227 else
1228 {
1229 /* The file exists */
1230 Result = TRUE;
1231 }
1232
1233 /* Return the result */
1234 return Result;
1235 }
1236
1237 BOOLEAN
1238 NTAPI
1239 RtlDoesFileExists_UStr(IN PUNICODE_STRING FileName)
1240 {
1241 /* Call the updated API */
1242 return RtlDoesFileExists_UstrEx(FileName, TRUE);
1243 }
1244
1245 BOOLEAN
1246 NTAPI
1247 RtlDoesFileExists_UEx(IN PCWSTR FileName,
1248 IN BOOLEAN SucceedIfBusy)
1249 {
1250 UNICODE_STRING NameString;
1251
1252 /* Create the unicode name*/
1253 if (NT_SUCCESS(RtlInitUnicodeStringEx(&NameString, FileName)))
1254 {
1255 /* Call the unicode function */
1256 return RtlDoesFileExists_UstrEx(&NameString, SucceedIfBusy);
1257 }
1258
1259 /* Fail */
1260 return FALSE;
1261 }
1262
1263 /* PUBLIC FUNCTIONS ***********************************************************/
1264
1265 /*
1266 * @implemented
1267 */
1268 VOID
1269 NTAPI
1270 RtlReleaseRelativeName(IN PRTL_RELATIVE_NAME_U RelativeName)
1271 {
1272 /* Check if a directory reference was grabbed */
1273 if (RelativeName->CurDirRef)
1274 {
1275 /* Decrease reference count */
1276 if (!InterlockedDecrement(&RelativeName->CurDirRef->RefCount))
1277 {
1278 /* If no one uses it any longer, close handle & free */
1279 NtClose(RelativeName->CurDirRef->Handle);
1280 RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeName->CurDirRef);
1281 }
1282 RelativeName->CurDirRef = NULL;
1283 }
1284 }
1285
1286 /*
1287 * @implemented
1288 */
1289 ULONG
1290 NTAPI
1291 RtlGetLongestNtPathLength(VOID)
1292 {
1293 /*
1294 * The longest NT path is a DOS path that actually sits on a UNC path (ie:
1295 * a mapped network drive), which is accessed through the DOS Global?? path.
1296 * This is, and has always been equal to, 269 characters, except in Wine
1297 * which claims this is 277. Go figure.
1298 */
1299 return MAX_PATH + RtlpDosDevicesUncPrefix.Length / sizeof(WCHAR) + sizeof(ANSI_NULL);
1300 }
1301
1302 /*
1303 * @implemented
1304 * @note: the export is called RtlGetLengthWithoutTrailingPathSeperators
1305 * (with a 'e' instead of a 'a' in "Seperators").
1306 */
1307 NTSTATUS
1308 NTAPI
1309 RtlGetLengthWithoutTrailingPathSeparators(IN ULONG Flags,
1310 IN PCUNICODE_STRING PathString,
1311 OUT PULONG Length)
1312 {
1313 ULONG NumChars;
1314
1315 /* Parameters validation */
1316 if (Length == NULL) return STATUS_INVALID_PARAMETER;
1317
1318 *Length = 0;
1319
1320 if (PathString == NULL) return STATUS_INVALID_PARAMETER;
1321
1322 /* No flags are supported yet */
1323 if (Flags != 0) return STATUS_INVALID_PARAMETER;
1324
1325 NumChars = PathString->Length / sizeof(WCHAR);
1326
1327 /*
1328 * Notice that we skip the last character, therefore:
1329 * - if we have: "some/path/f" we test for: "some/path/"
1330 * - if we have: "some/path/" we test for: "some/path"
1331 * - if we have: "s" we test for: ""
1332 * - if we have: "" then NumChars was already zero and we aren't there
1333 */
1334
1335 while (NumChars > 0 && IS_PATH_SEPARATOR(PathString->Buffer[NumChars - 1]))
1336 {
1337 --NumChars;
1338 }
1339
1340 *Length = NumChars;
1341 return STATUS_SUCCESS;
1342 }
1343
1344 /*
1345 * @implemented
1346 */
1347 RTL_PATH_TYPE
1348 NTAPI
1349 RtlDetermineDosPathNameType_U(IN PCWSTR Path)
1350 {
1351 DPRINT("RtlDetermineDosPathNameType_U %S\n", Path);
1352
1353 /* Unlike the newer RtlDetermineDosPathNameType_U we assume 4 characters */
1354 if (IS_PATH_SEPARATOR(Path[0]))
1355 {
1356 if (!IS_PATH_SEPARATOR(Path[1])) return RtlPathTypeRooted; /* \x */
1357 if ((Path[2] != L'.') && (Path[2] != L'?')) return RtlPathTypeUncAbsolute;/* \\x */
1358 if (IS_PATH_SEPARATOR(Path[3])) return RtlPathTypeLocalDevice; /* \\.\x or \\?\x */
1359 if (Path[3]) return RtlPathTypeUncAbsolute; /* \\.x or \\?x */
1360 return RtlPathTypeRootLocalDevice; /* \\. or \\? */
1361 }
1362 else
1363 {
1364 if (!(Path[0]) || (Path[1] != L':')) return RtlPathTypeRelative; /* x */
1365 if (IS_PATH_SEPARATOR(Path[2])) return RtlPathTypeDriveAbsolute; /* x:\ */
1366 return RtlPathTypeDriveRelative; /* x: */
1367 }
1368 }
1369
1370 /*
1371 * @implemented
1372 */
1373 ULONG
1374 NTAPI
1375 RtlIsDosDeviceName_U(IN PCWSTR Path)
1376 {
1377 UNICODE_STRING PathString;
1378 NTSTATUS Status;
1379
1380 /* Build the string */
1381 Status = RtlInitUnicodeStringEx(&PathString, Path);
1382 if (!NT_SUCCESS(Status)) return 0;
1383
1384 /*
1385 * Returns 0 if name is not valid DOS device name, or DWORD with
1386 * offset in bytes to DOS device name from beginning of buffer in high word
1387 * and size in bytes of DOS device name in low word
1388 */
1389 return RtlIsDosDeviceName_Ustr(&PathString);
1390 }
1391
1392 /*
1393 * @implemented
1394 */
1395 ULONG
1396 NTAPI
1397 RtlGetCurrentDirectory_U(
1398 _In_ ULONG MaximumLength,
1399 _Out_bytecap_(MaximumLength) PWSTR Buffer)
1400 {
1401 ULONG Length, Bytes;
1402 PCURDIR CurDir;
1403 PWSTR CurDirName;
1404 DPRINT("RtlGetCurrentDirectory %lu %p\n", MaximumLength, Buffer);
1405
1406 /* Lock the PEB to get the current directory */
1407 RtlAcquirePebLock();
1408 CurDir = &NtCurrentPeb()->ProcessParameters->CurrentDirectory;
1409
1410 /* Get the buffer and character length */
1411 CurDirName = CurDir->DosPath.Buffer;
1412 Length = CurDir->DosPath.Length / sizeof(WCHAR);
1413 ASSERT((CurDirName != NULL) && (Length > 0));
1414
1415 /*
1416 * DosPath.Buffer should always have a trailing slash. There is an assert
1417 * below which checks for this.
1418 *
1419 * This function either returns x:\ for a root (keeping the original buffer)
1420 * or it returns x:\path\foo for a directory (replacing the trailing slash
1421 * with a NULL.
1422 */
1423 Bytes = Length * sizeof(WCHAR);
1424 if ((Length <= 1) || (CurDirName[Length - 2] == L':'))
1425 {
1426 /* Check if caller does not have enough space */
1427 if (MaximumLength <= Bytes)
1428 {
1429 /* Call has no space for it, fail, add the trailing slash */
1430 RtlReleasePebLock();
1431 return Bytes + sizeof(OBJ_NAME_PATH_SEPARATOR);
1432 }
1433 }
1434 else
1435 {
1436 /* Check if caller does not have enough space */
1437 if (MaximumLength < Bytes)
1438 {
1439 /* Call has no space for it, fail */
1440 RtlReleasePebLock();
1441 return Bytes;
1442 }
1443 }
1444
1445 /* Copy the buffer since we seem to have space */
1446 RtlCopyMemory(Buffer, CurDirName, Bytes);
1447
1448 /* The buffer should end with a path separator */
1449 ASSERT(Buffer[Length - 1] == OBJ_NAME_PATH_SEPARATOR);
1450
1451 /* Again check for our two cases (drive root vs path) */
1452 if ((Length <= 1) || (Buffer[Length - 2] != L':'))
1453 {
1454 /* Replace the trailing slash with a null */
1455 Buffer[Length - 1] = UNICODE_NULL;
1456 --Length;
1457 }
1458 else
1459 {
1460 /* Append the null char since there's no trailing slash */
1461 Buffer[Length] = UNICODE_NULL;
1462 }
1463
1464 /* Release PEB lock */
1465 RtlReleasePebLock();
1466 DPRINT("CurrentDirectory %S\n", Buffer);
1467 return Length * sizeof(WCHAR);
1468 }
1469
1470 /*
1471 * @implemented
1472 */
1473 NTSTATUS
1474 NTAPI
1475 RtlSetCurrentDirectory_U(IN PUNICODE_STRING Path)
1476 {
1477 PCURDIR CurDir;
1478 NTSTATUS Status;
1479 RTL_PATH_TYPE PathType;
1480 IO_STATUS_BLOCK IoStatusBlock;
1481 UNICODE_STRING FullPath, NtName;
1482 PRTLP_CURDIR_REF OldCurDir = NULL;
1483 OBJECT_ATTRIBUTES ObjectAttributes;
1484 FILE_FS_DEVICE_INFORMATION FileFsDeviceInfo;
1485 ULONG SavedLength, CharLength, FullPathLength;
1486 HANDLE OldHandle = NULL, CurDirHandle = NULL, OldCurDirHandle = NULL;
1487
1488 DPRINT("RtlSetCurrentDirectory_U %wZ\n", Path);
1489
1490 /* Initialize for failure case */
1491 RtlInitEmptyUnicodeString(&NtName, NULL, 0);
1492
1493 /* Can't set current directory on DOS device */
1494 if (RtlIsDosDeviceName_Ustr(Path))
1495 {
1496 return STATUS_NOT_A_DIRECTORY;
1497 }
1498
1499 /* Get current directory */
1500 RtlAcquirePebLock();
1501 CurDir = &NtCurrentPeb()->ProcessParameters->CurrentDirectory;
1502
1503 /* Check if we have to drop current handle */
1504 if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_DROP_OLD_HANDLE)
1505 {
1506 OldHandle = CurDir->Handle;
1507 CurDir->Handle = NULL;
1508 }
1509
1510 /* Allocate a buffer for full path (using max possible length */
1511 FullPath.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, CurDir->DosPath.MaximumLength);
1512 if (!FullPath.Buffer)
1513 {
1514 Status = STATUS_NO_MEMORY;
1515 goto Leave;
1516 }
1517
1518 /* Init string */
1519 FullPath.Length = 0;
1520 FullPath.MaximumLength = CurDir->DosPath.MaximumLength;
1521
1522 /* Get new directory full path */
1523 FullPathLength = RtlGetFullPathName_Ustr(Path, FullPath.MaximumLength, FullPath.Buffer, NULL, NULL, &PathType);
1524 if (!FullPathLength)
1525 {
1526 Status = STATUS_OBJECT_NAME_INVALID;
1527 goto Leave;
1528 }
1529
1530 SavedLength = FullPath.MaximumLength;
1531 CharLength = FullPathLength / sizeof(WCHAR);
1532
1533 if (FullPathLength > FullPath.MaximumLength)
1534 {
1535 Status = STATUS_NAME_TOO_LONG;
1536 goto Leave;
1537 }
1538
1539 /* Translate it to NT name */
1540 if (!RtlDosPathNameToNtPathName_U(FullPath.Buffer, &NtName, NULL, NULL))
1541 {
1542 Status = STATUS_OBJECT_NAME_INVALID;
1543 goto Leave;
1544 }
1545
1546 InitializeObjectAttributes(&ObjectAttributes, &NtName,
1547 OBJ_CASE_INSENSITIVE | OBJ_INHERIT,
1548 NULL, NULL);
1549
1550 /* If previous current directory was removable, then check it for dropping */
1551 if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_ALL_FLAGS)
1552 {
1553 /* Get back normal handle */
1554 CurDirHandle = (HANDLE)((ULONG_PTR)(CurDir->Handle) & ~RTL_CURDIR_ALL_FLAGS);
1555 CurDir->Handle = NULL;
1556
1557 /* Get device information */
1558 Status = NtQueryVolumeInformationFile(CurDirHandle,
1559 &IoStatusBlock,
1560 &FileFsDeviceInfo,
1561 sizeof(FileFsDeviceInfo),
1562 FileFsDeviceInformation);
1563 /* Retry without taking care of removable device */
1564 if (!NT_SUCCESS(Status))
1565 {
1566 Status = RtlSetCurrentDirectory_U(Path);
1567 goto Leave;
1568 }
1569 }
1570 else
1571 {
1572 /* Open directory */
1573 Status = NtOpenFile(&CurDirHandle,
1574 SYNCHRONIZE | FILE_TRAVERSE,
1575 &ObjectAttributes,
1576 &IoStatusBlock,
1577 FILE_SHARE_READ | FILE_SHARE_WRITE,
1578 FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
1579 if (!NT_SUCCESS(Status)) goto Leave;
1580
1581 /* Get device information */
1582 Status = NtQueryVolumeInformationFile(CurDirHandle,
1583 &IoStatusBlock,
1584 &FileFsDeviceInfo,
1585 sizeof(FileFsDeviceInfo),
1586 FileFsDeviceInformation);
1587 if (!NT_SUCCESS(Status)) goto Leave;
1588 }
1589
1590 /* If device is removable, mark handle */
1591 if (FileFsDeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA)
1592 {
1593 CurDirHandle = (HANDLE)((ULONG_PTR)CurDirHandle | RTL_CURDIR_IS_REMOVABLE);
1594 }
1595
1596 FullPath.Length = (USHORT)FullPathLength;
1597
1598 /* If full path isn't \ terminated, do it */
1599 if (FullPath.Buffer[CharLength - 1] != OBJ_NAME_PATH_SEPARATOR)
1600 {
1601 if ((CharLength + 1) * sizeof(WCHAR) > SavedLength)
1602 {
1603 Status = STATUS_NAME_TOO_LONG;
1604 goto Leave;
1605 }
1606
1607 FullPath.Buffer[CharLength] = OBJ_NAME_PATH_SEPARATOR;
1608 FullPath.Buffer[CharLength + 1] = UNICODE_NULL;
1609 FullPath.Length += sizeof(WCHAR);
1610 }
1611
1612 /* If we have previous current directory with only us as reference, save it */
1613 if (RtlpCurDirRef != NULL && RtlpCurDirRef->RefCount == 1)
1614 {
1615 OldCurDirHandle = RtlpCurDirRef->Handle;
1616 }
1617 else
1618 {
1619 /* Allocate new current directory struct saving previous one */
1620 OldCurDir = RtlpCurDirRef;
1621 RtlpCurDirRef = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(RTLP_CURDIR_REF));
1622 if (!RtlpCurDirRef)
1623 {
1624 RtlpCurDirRef = OldCurDir;
1625 OldCurDir = NULL;
1626 Status = STATUS_NO_MEMORY;
1627 goto Leave;
1628 }
1629
1630 /* Set reference to 1 (us) */
1631 RtlpCurDirRef->RefCount = 1;
1632 }
1633
1634 /* Save new data */
1635 CurDir->Handle = CurDirHandle;
1636 RtlpCurDirRef->Handle = CurDirHandle;
1637 CurDirHandle = NULL;
1638
1639 /* Copy full path */
1640 RtlCopyMemory(CurDir->DosPath.Buffer, FullPath.Buffer, FullPath.Length + sizeof(WCHAR));
1641 CurDir->DosPath.Length = FullPath.Length;
1642
1643 Status = STATUS_SUCCESS;
1644
1645 Leave:
1646 RtlReleasePebLock();
1647
1648 if (FullPath.Buffer)
1649 {
1650 RtlFreeHeap(RtlGetProcessHeap(), 0, FullPath.Buffer);
1651 }
1652
1653 if (NtName.Buffer)
1654 {
1655 RtlFreeHeap(RtlGetProcessHeap(), 0, NtName.Buffer);
1656 }
1657
1658 if (CurDirHandle) NtClose(CurDirHandle);
1659
1660 if (OldHandle) NtClose(OldHandle);
1661
1662 if (OldCurDirHandle) NtClose(OldCurDirHandle);
1663
1664 if (OldCurDir && InterlockedDecrement(&OldCurDir->RefCount) == 0)
1665 {
1666 NtClose(OldCurDir->Handle);
1667 RtlFreeHeap(RtlGetProcessHeap(), 0, OldCurDir);
1668 }
1669
1670 return Status;
1671 }
1672
1673
1674 /******************************************************************
1675 * RtlGetFullPathName_U (NTDLL.@)
1676 *
1677 * Returns the number of bytes written to buffer (not including the
1678 * terminating NULL) if the function succeeds, or the required number of bytes
1679 * (including the terminating NULL) if the buffer is too small.
1680 *
1681 * file_part will point to the filename part inside buffer (except if we use
1682 * DOS device name, in which case file_in_buf is NULL)
1683 *
1684 * @implemented
1685 */
1686
1687 /*
1688 * @implemented
1689 */
1690 ULONG
1691 NTAPI
1692 RtlGetFullPathName_U(
1693 _In_ PCWSTR FileName,
1694 _In_ ULONG Size,
1695 _Out_z_bytecap_(Size) PWSTR Buffer,
1696 _Out_opt_ PWSTR *ShortName)
1697 {
1698 NTSTATUS Status;
1699 UNICODE_STRING FileNameString;
1700 RTL_PATH_TYPE PathType;
1701
1702 /* Build the string */
1703 Status = RtlInitUnicodeStringEx(&FileNameString, FileName);
1704 if (!NT_SUCCESS(Status)) return 0;
1705
1706 /* Call the extended function */
1707 return RtlGetFullPathName_Ustr(&FileNameString,
1708 Size,
1709 Buffer,
1710 (PCWSTR*)ShortName,
1711 NULL,
1712 &PathType);
1713 }
1714
1715 /*
1716 * @implemented
1717 */
1718 BOOLEAN
1719 NTAPI
1720 RtlDosPathNameToNtPathName_U(IN PCWSTR DosName,
1721 OUT PUNICODE_STRING NtName,
1722 OUT PCWSTR *PartName,
1723 OUT PRTL_RELATIVE_NAME_U RelativeName)
1724 {
1725 /* Call the internal function */
1726 return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(FALSE,
1727 DosName,
1728 NtName,
1729 PartName,
1730 RelativeName));
1731 }
1732
1733 /*
1734 * @implemented
1735 */
1736 NTSTATUS
1737 NTAPI
1738 RtlDosPathNameToNtPathName_U_WithStatus(IN PCWSTR DosName,
1739 OUT PUNICODE_STRING NtName,
1740 OUT PCWSTR *PartName,
1741 OUT PRTL_RELATIVE_NAME_U RelativeName)
1742 {
1743 /* Call the internal function */
1744 return RtlpDosPathNameToRelativeNtPathName_U(FALSE,
1745 DosName,
1746 NtName,
1747 PartName,
1748 RelativeName);
1749 }
1750
1751 /*
1752 * @implemented
1753 */
1754 BOOLEAN
1755 NTAPI
1756 RtlDosPathNameToRelativeNtPathName_U(IN PCWSTR DosName,
1757 OUT PUNICODE_STRING NtName,
1758 OUT PCWSTR *PartName,
1759 OUT PRTL_RELATIVE_NAME_U RelativeName)
1760 {
1761 /* Call the internal function */
1762 ASSERT(RelativeName);
1763 return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(TRUE,
1764 DosName,
1765 NtName,
1766 PartName,
1767 RelativeName));
1768 }
1769
1770 /*
1771 * @implemented
1772 */
1773 NTSTATUS
1774 NTAPI
1775 RtlDosPathNameToRelativeNtPathName_U_WithStatus(IN PCWSTR DosName,
1776 OUT PUNICODE_STRING NtName,
1777 OUT PCWSTR *PartName,
1778 OUT PRTL_RELATIVE_NAME_U RelativeName)
1779 {
1780 /* Call the internal function */
1781 ASSERT(RelativeName);
1782 return RtlpDosPathNameToRelativeNtPathName_U(TRUE,
1783 DosName,
1784 NtName,
1785 PartName,
1786 RelativeName);
1787 }
1788
1789 /*
1790 * @unimplemented
1791 */
1792 NTSTATUS NTAPI
1793 RtlNtPathNameToDosPathName(ULONG Unknown1, ULONG Unknown2, ULONG Unknown3, ULONG Unknown4)
1794 {
1795 DPRINT1("RtlNtPathNameToDosPathName: stub\n");
1796 return STATUS_NOT_IMPLEMENTED;
1797 }
1798
1799 /*
1800 * @implemented
1801 */
1802 ULONG
1803 NTAPI
1804 RtlDosSearchPath_U(IN PCWSTR Path,
1805 IN PCWSTR FileName,
1806 IN PCWSTR Extension,
1807 IN ULONG Size,
1808 IN PWSTR Buffer,
1809 OUT PWSTR *PartName)
1810 {
1811 NTSTATUS Status;
1812 ULONG ExtensionLength, Length, FileNameLength, PathLength;
1813 UNICODE_STRING TempString;
1814 PWCHAR NewBuffer, BufferStart;
1815 PCWSTR p;
1816
1817 /* Check if this is an absolute path */
1818 if (RtlDetermineDosPathNameType_U(FileName) != RtlPathTypeRelative)
1819 {
1820 /* Check if the file exists */
1821 if (RtlDoesFileExists_UEx(FileName, TRUE))
1822 {
1823 /* Get the full name, which does the DOS lookup */
1824 return RtlGetFullPathName_U(FileName, Size, Buffer, PartName);
1825 }
1826
1827 /* Doesn't exist, so fail */
1828 return 0;
1829 }
1830
1831 /* Scan the filename */
1832 p = FileName;
1833 while (*p)
1834 {
1835 /* Looking for an extension */
1836 if (*p == L'.')
1837 {
1838 /* No extension string needed -- it's part of the filename */
1839 Extension = NULL;
1840 break;
1841 }
1842
1843 /* Next character */
1844 p++;
1845 }
1846
1847 /* Do we have an extension? */
1848 if (!Extension)
1849 {
1850 /* Nope, don't worry about one */
1851 ExtensionLength = 0;
1852 }
1853 else
1854 {
1855 /* Build a temporary string to get the extension length */
1856 Status = RtlInitUnicodeStringEx(&TempString, Extension);
1857 if (!NT_SUCCESS(Status)) return 0;
1858 ExtensionLength = TempString.Length;
1859 }
1860
1861 /* Build a temporary string to get the path length */
1862 Status = RtlInitUnicodeStringEx(&TempString, Path);
1863 if (!NT_SUCCESS(Status)) return 0;
1864 PathLength = TempString.Length;
1865
1866 /* Build a temporary string to get the filename length */
1867 Status = RtlInitUnicodeStringEx(&TempString, FileName);
1868 if (!NT_SUCCESS(Status)) return 0;
1869 FileNameLength = TempString.Length;
1870
1871 /* Allocate the buffer for the new string name */
1872 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
1873 0,
1874 FileNameLength +
1875 ExtensionLength +
1876 PathLength +
1877 3 * sizeof(WCHAR));
1878 if (!NewBuffer)
1879 {
1880 /* Fail the call */
1881 DbgPrint("%s: Failing due to out of memory (RtlAllocateHeap failure)\n",
1882 __FUNCTION__);
1883 return 0;
1884 }
1885
1886 /* Final loop to build the path */
1887 while (TRUE)
1888 {
1889 /* Check if we have a valid character */
1890 BufferStart = NewBuffer;
1891 if (*Path)
1892 {
1893 /* Loop as long as there's no semicolon */
1894 while (*Path != L';')
1895 {
1896 /* Copy the next character */
1897 *BufferStart++ = *Path++;
1898 if (!*Path) break;
1899 }
1900
1901 /* We found a semi-colon, to stop path processing on this loop */
1902 if (*Path == L';') ++Path;
1903 }
1904
1905 /* Add a terminating slash if needed */
1906 if ((BufferStart != NewBuffer) && (BufferStart[-1] != OBJ_NAME_PATH_SEPARATOR))
1907 {
1908 *BufferStart++ = OBJ_NAME_PATH_SEPARATOR;
1909 }
1910
1911 /* Bail out if we reached the end */
1912 if (!*Path) Path = NULL;
1913
1914 /* Copy the file name and check if an extension is needed */
1915 RtlCopyMemory(BufferStart, FileName, FileNameLength);
1916 if (ExtensionLength)
1917 {
1918 /* Copy the extension too */
1919 RtlCopyMemory((PCHAR)BufferStart + FileNameLength,
1920 Extension,
1921 ExtensionLength + sizeof(WCHAR));
1922 }
1923 else
1924 {
1925 /* Just NULL-terminate */
1926 *(PWCHAR)((PCHAR)BufferStart + FileNameLength) = UNICODE_NULL;
1927 }
1928
1929 /* Now, does this file exist? */
1930 if (RtlDoesFileExists_UEx(NewBuffer, FALSE))
1931 {
1932 /* Call the full-path API to get the length */
1933 Length = RtlGetFullPathName_U(NewBuffer, Size, Buffer, PartName);
1934 break;
1935 }
1936
1937 /* If we got here, path doesn't exist, so fail the call */
1938 Length = 0;
1939 if (!Path) break;
1940 }
1941
1942 /* Free the allocation and return the length */
1943 RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
1944 return Length;
1945 }
1946
1947 /*
1948 * @implemented
1949 */
1950 NTSTATUS
1951 NTAPI
1952 RtlGetFullPathName_UstrEx(IN PUNICODE_STRING FileName,
1953 IN PUNICODE_STRING StaticString,
1954 IN PUNICODE_STRING DynamicString,
1955 IN PUNICODE_STRING *StringUsed,
1956 IN PSIZE_T FilePartSize,
1957 OUT PBOOLEAN NameInvalid,
1958 OUT RTL_PATH_TYPE* PathType,
1959 OUT PSIZE_T LengthNeeded)
1960 {
1961 NTSTATUS Status;
1962 PWCHAR StaticBuffer;
1963 PCWCH ShortName;
1964 ULONG Length;
1965 USHORT StaticLength;
1966 UNICODE_STRING TempDynamicString;
1967
1968 /* Initialize all our locals */
1969 ShortName = NULL;
1970 StaticBuffer = NULL;
1971 TempDynamicString.Buffer = NULL;
1972
1973 /* Initialize the input parameters */
1974 if (StringUsed) *StringUsed = NULL;
1975 if (LengthNeeded) *LengthNeeded = 0;
1976 if (FilePartSize) *FilePartSize = 0;
1977
1978 /* Check for invalid parameters */
1979 if ((DynamicString) && !(StringUsed) && (StaticString))
1980 {
1981 return STATUS_INVALID_PARAMETER;
1982 }
1983
1984 /* Check if we did not get an input string */
1985 if (!StaticString)
1986 {
1987 /* Allocate one */
1988 StaticLength = MAX_PATH * sizeof(WCHAR);
1989 StaticBuffer = RtlpAllocateStringMemory(MAX_PATH * sizeof(WCHAR), TAG_USTR);
1990 if (!StaticBuffer) return STATUS_NO_MEMORY;
1991 }
1992 else
1993 {
1994 /* Use the one we received */
1995 StaticBuffer = StaticString->Buffer;
1996 StaticLength = StaticString->MaximumLength;
1997 }
1998
1999 /* Call the lower-level function */
2000 Length = RtlGetFullPathName_Ustr(FileName,
2001 StaticLength,
2002 StaticBuffer,
2003 &ShortName,
2004 NameInvalid,
2005 PathType);
2006 DPRINT("Length: %u StaticBuffer: %S\n", Length, StaticBuffer);
2007 if (!Length)
2008 {
2009 /* Fail if it failed */
2010 DbgPrint("%s(%d) - RtlGetFullPathName_Ustr() returned 0\n",
2011 __FUNCTION__,
2012 __LINE__);
2013 Status = STATUS_OBJECT_NAME_INVALID;
2014 goto Quickie;
2015 }
2016
2017 /* Check if it fits inside our static string */
2018 if ((StaticString) && (Length < StaticLength))
2019 {
2020 /* Set the final length */
2021 StaticString->Length = (USHORT)Length;
2022
2023 /* Set the file part size */
2024 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
2025
2026 /* Return the static string if requested */
2027 if (StringUsed) *StringUsed = StaticString;
2028
2029 /* We are done with success */
2030 Status = STATUS_SUCCESS;
2031 goto Quickie;
2032 }
2033
2034 /* Did we not have an input dynamic string ?*/
2035 if (!DynamicString)
2036 {
2037 /* Return the length we need */
2038 if (LengthNeeded) *LengthNeeded = Length;
2039
2040 /* And fail such that the caller can try again */
2041 Status = STATUS_BUFFER_TOO_SMALL;
2042 goto Quickie;
2043 }
2044
2045 /* Check if it fits in our static buffer */
2046 if ((StaticBuffer) && (Length < StaticLength))
2047 {
2048 /* NULL-terminate it */
2049 StaticBuffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
2050
2051 /* Set the settings for the dynamic string the caller sent */
2052 DynamicString->MaximumLength = StaticLength;
2053 DynamicString->Length = (USHORT)Length;
2054 DynamicString->Buffer = StaticBuffer;
2055
2056 /* Set the part size */
2057 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticBuffer) : 0;
2058
2059 /* Return the dynamic string if requested */
2060 if (StringUsed) *StringUsed = DynamicString;
2061
2062 /* Do not free the static buffer on exit, and return success */
2063 StaticBuffer = NULL;
2064 Status = STATUS_SUCCESS;
2065 goto Quickie;
2066 }
2067
2068 /* Now try again under the PEB lock */
2069 RtlAcquirePebLock();
2070 Length = RtlGetFullPathName_Ustr(FileName,
2071 StaticLength,
2072 StaticBuffer,
2073 &ShortName,
2074 NameInvalid,
2075 PathType);
2076 if (!Length)
2077 {
2078 /* It failed */
2079 DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
2080 __FUNCTION__, __LINE__);
2081 Status = STATUS_OBJECT_NAME_INVALID;
2082 goto Release;
2083 }
2084
2085 /* Check if it fits inside our static string now */
2086 if ((StaticString) && (Length < StaticLength))
2087 {
2088 /* Set the final length */
2089 StaticString->Length = (USHORT)Length;
2090
2091 /* Set the file part size */
2092 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
2093
2094 /* Return the static string if requested */
2095 if (StringUsed) *StringUsed = StaticString;
2096
2097 /* We are done with success */
2098 Status = STATUS_SUCCESS;
2099 goto Release;
2100 }
2101
2102 /* Check if the path won't even fit in a real string */
2103 if ((Length + sizeof(WCHAR)) > UNICODE_STRING_MAX_BYTES)
2104 {
2105 /* Name is way too long, fail */
2106 Status = STATUS_NAME_TOO_LONG;
2107 goto Release;
2108 }
2109
2110 /* Allocate the string to hold the path name now */
2111 TempDynamicString.Buffer = RtlpAllocateStringMemory(Length + sizeof(WCHAR),
2112 TAG_USTR);
2113 if (!TempDynamicString.Buffer)
2114 {
2115 /* Out of memory, fail */
2116 Status = STATUS_NO_MEMORY;
2117 goto Release;
2118 }
2119
2120 /* Add space for a NULL terminator, and now check the full path */
2121 TempDynamicString.MaximumLength = (USHORT)Length + sizeof(UNICODE_NULL);
2122 Length = RtlGetFullPathName_Ustr(FileName,
2123 Length,
2124 TempDynamicString.Buffer,
2125 &ShortName,
2126 NameInvalid,
2127 PathType);
2128 if (!Length)
2129 {
2130 /* Some path error, so fail out */
2131 DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
2132 __FUNCTION__, __LINE__);
2133 Status = STATUS_OBJECT_NAME_INVALID;
2134 goto Release;
2135 }
2136
2137 /* It should fit in the string we just allocated */
2138 ASSERT(Length < (TempDynamicString.MaximumLength - sizeof(WCHAR)));
2139 if (Length > TempDynamicString.MaximumLength)
2140 {
2141 /* This is really weird and would mean some kind of race */
2142 Status = STATUS_INTERNAL_ERROR;
2143 goto Release;
2144 }
2145
2146 /* Return the file part size */
2147 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - TempDynamicString.Buffer) : 0;
2148
2149 /* Terminate the whole string now */
2150 TempDynamicString.Buffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
2151
2152 /* Finalize the string and return it to the user */
2153 DynamicString->Buffer = TempDynamicString.Buffer;
2154 DynamicString->Length = (USHORT)Length;
2155 DynamicString->MaximumLength = TempDynamicString.MaximumLength;
2156 if (StringUsed) *StringUsed = DynamicString;
2157
2158 /* Return success and make sure we don't free the buffer on exit */
2159 TempDynamicString.Buffer = NULL;
2160 Status = STATUS_SUCCESS;
2161
2162 Release:
2163 /* Release the PEB lock */
2164 RtlReleasePebLock();
2165
2166 Quickie:
2167 /* Free any buffers we should be freeing */
2168 DPRINT("Status: %lx %S %S\n", Status, StaticBuffer, TempDynamicString.Buffer);
2169 if ((StaticString) && (StaticBuffer) && (StaticBuffer != StaticString->Buffer))
2170 {
2171 RtlpFreeMemory(StaticBuffer, TAG_USTR);
2172 }
2173 if (TempDynamicString.Buffer)
2174 {
2175 RtlpFreeMemory(TempDynamicString.Buffer, TAG_USTR);
2176 }
2177
2178 /* Print out any unusual errors */
2179 if ((NT_ERROR(Status)) &&
2180 (Status != STATUS_NO_SUCH_FILE) && (Status != STATUS_BUFFER_TOO_SMALL))
2181 {
2182 DbgPrint("RTL: %s - failing on filename %wZ with status %08lx\n",
2183 __FUNCTION__, FileName, Status);
2184 }
2185
2186 /* Return, we're all done */
2187 return Status;
2188 }
2189
2190 /*
2191 * @implemented
2192 */
2193 NTSTATUS
2194 NTAPI
2195 RtlDosSearchPath_Ustr(IN ULONG Flags,
2196 IN PUNICODE_STRING PathString,
2197 IN PUNICODE_STRING FileNameString,
2198 IN PUNICODE_STRING ExtensionString,
2199 IN PUNICODE_STRING CallerBuffer,
2200 IN OUT PUNICODE_STRING DynamicString OPTIONAL,
2201 OUT PUNICODE_STRING* FullNameOut OPTIONAL,
2202 OUT PSIZE_T FilePartSize OPTIONAL,
2203 OUT PSIZE_T LengthNeeded OPTIONAL)
2204 {
2205 WCHAR StaticCandidateBuffer[MAX_PATH];
2206 UNICODE_STRING StaticCandidateString;
2207 NTSTATUS Status;
2208 RTL_PATH_TYPE PathType;
2209 PWCHAR p, End, CandidateEnd, SegmentEnd;
2210 SIZE_T SegmentSize, ByteCount, PathSize, MaxPathSize = 0;
2211 USHORT NamePlusExtLength, WorstCaseLength, ExtensionLength = 0;
2212 PUNICODE_STRING FullIsolatedPath;
2213 DPRINT("DOS Path Search: %lx %wZ %wZ %wZ %wZ %wZ\n",
2214 Flags, PathString, FileNameString, ExtensionString, CallerBuffer, DynamicString);
2215
2216 /* Initialize the input string */
2217 RtlInitEmptyUnicodeString(&StaticCandidateString,
2218 StaticCandidateBuffer,
2219 sizeof(StaticCandidateBuffer));
2220
2221 /* Initialize optional arguments */
2222 if (FullNameOut ) *FullNameOut = NULL;
2223 if (FilePartSize) *FilePartSize = 0;
2224 if (LengthNeeded) *LengthNeeded = 0;
2225 if (DynamicString)
2226 {
2227 DynamicString->Length = DynamicString->MaximumLength = 0;
2228 DynamicString->Buffer = NULL;
2229 }
2230
2231 /* Check for invalid parameters */
2232 if ((Flags & ~7) ||
2233 !(PathString) ||
2234 !(FileNameString) ||
2235 ((CallerBuffer) && (DynamicString) && !(FullNameOut)))
2236 {
2237 /* Fail */
2238 DbgPrint("%s: Invalid parameters passed\n", __FUNCTION__);
2239 Status = STATUS_INVALID_PARAMETER;
2240 goto Quickie;
2241 }
2242
2243 /* First check what kind of path this is */
2244 PathType = RtlDetermineDosPathNameType_Ustr(FileNameString);
2245
2246 /* Check if the caller wants to prevent relative .\ and ..\ paths */
2247 if ((Flags & 2) &&
2248 (PathType == RtlPathTypeRelative) &&
2249 (FileNameString->Length >= (2 * sizeof(WCHAR))) &&
2250 (FileNameString->Buffer[0] == L'.') &&
2251 ((IS_PATH_SEPARATOR(FileNameString->Buffer[1])) ||
2252 ((FileNameString->Buffer[1] == L'.') &&
2253 ((FileNameString->Length >= (3 * sizeof(WCHAR))) &&
2254 (IS_PATH_SEPARATOR(FileNameString->Buffer[2]))))))
2255 {
2256 /* Yes, and this path is like that, so make it seem unknown */
2257 PathType = RtlPathTypeUnknown;
2258 }
2259
2260 /* Now check relative vs non-relative paths */
2261 if (PathType == RtlPathTypeRelative)
2262 {
2263 /* Does the caller want SxS? */
2264 if (Flags & 1)
2265 {
2266 /* Apply the SxS magic */
2267 FullIsolatedPath = NULL;
2268 Status = RtlDosApplyFileIsolationRedirection_Ustr(TRUE,
2269 FileNameString,
2270 ExtensionString,
2271 CallerBuffer,
2272 DynamicString,
2273 &FullIsolatedPath,
2274 NULL,
2275 FilePartSize,
2276 LengthNeeded);
2277 if (NT_SUCCESS(Status))
2278 {
2279 /* We found the SxS path, return it */
2280 if (FullNameOut) *FullNameOut = FullIsolatedPath;
2281 goto Quickie;
2282 }
2283 else if (Status != STATUS_SXS_KEY_NOT_FOUND)
2284 {
2285 /* Critical SxS error, fail */
2286 DbgPrint("%s: Failing because call to "
2287 "RtlDosApplyIsolationRedirection_Ustr(%wZ) failed with "
2288 "status 0x%08lx\n",
2289 __FUNCTION__,
2290 FileNameString,
2291 Status);
2292 goto Quickie;
2293 }
2294 }
2295
2296 /* No SxS key found, or not requested, check if there's an extension */
2297 if (ExtensionString)
2298 {
2299 /* Save the extension length, and check if there's a file name */
2300 ExtensionLength = ExtensionString->Length;
2301 if (FileNameString->Length)
2302 {
2303 /* Start parsing the file name */
2304 End = &FileNameString->Buffer[FileNameString->Length / sizeof(WCHAR)];
2305 while (End > FileNameString->Buffer)
2306 {
2307 /* If we find a path separator, there's no extension */
2308 if (IS_PATH_SEPARATOR(*--End)) break;
2309
2310 /* Otherwise, did we find an extension dot? */
2311 if (*End == L'.')
2312 {
2313 /* Ignore what the caller sent it, use the filename's */
2314 ExtensionString = NULL;
2315 ExtensionLength = 0;
2316 break;
2317 }
2318 }
2319 }
2320 }
2321
2322 /* Check if we got a path */
2323 if (PathString->Length)
2324 {
2325 /* Start parsing the path name, looking for path separators */
2326 End = &PathString->Buffer[PathString->Length / sizeof(WCHAR)];
2327 p = End;
2328 while ((p > PathString->Buffer) && (*--p == L';'))
2329 {
2330 /* This is the size of the path -- handle a trailing slash */
2331 PathSize = End - p - 1;
2332 if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
2333
2334 /* Check if we found a bigger path than before */
2335 if (PathSize > MaxPathSize) MaxPathSize = PathSize;
2336
2337 /* Keep going with the path after this path separator */
2338 End = p;
2339 }
2340
2341 /* This is the trailing path, run the same code as above */
2342 PathSize = End - p;
2343 if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
2344 if (PathSize > MaxPathSize) MaxPathSize = PathSize;
2345
2346 /* Finally, convert the largest path size into WCHAR */
2347 MaxPathSize *= sizeof(WCHAR);
2348 }
2349
2350 /* Use the extension, the file name, and the largest path as the size */
2351 WorstCaseLength = ExtensionLength +
2352 FileNameString->Length +
2353 (USHORT)MaxPathSize +
2354 sizeof(UNICODE_NULL);
2355 if (WorstCaseLength > UNICODE_STRING_MAX_BYTES)
2356 {
2357 /* It has to fit in a registry string, if not, fail here */
2358 DbgPrint("%s returning STATUS_NAME_TOO_LONG because the computed "
2359 "worst case file name length is %Iu bytes\n",
2360 __FUNCTION__,
2361 WorstCaseLength);
2362 Status = STATUS_NAME_TOO_LONG;
2363 goto Quickie;
2364 }
2365
2366 /* Scan the path now, to see if we can find the file */
2367 p = PathString->Buffer;
2368 End = &p[PathString->Length / sizeof(WCHAR)];
2369 while (p < End)
2370 {
2371 /* Find out where this path ends */
2372 for (SegmentEnd = p;
2373 ((SegmentEnd != End) && (*SegmentEnd != L';'));
2374 SegmentEnd++);
2375
2376 /* Compute the size of this path */
2377 ByteCount = SegmentSize = (SegmentEnd - p) * sizeof(WCHAR);
2378
2379 /* Handle trailing slash if there isn't one */
2380 if ((SegmentSize) && !(IS_PATH_SEPARATOR(*(SegmentEnd - 1))))
2381 {
2382 /* Add space for one */
2383 SegmentSize += sizeof(OBJ_NAME_PATH_SEPARATOR);
2384 }
2385
2386 /* Now check if our initial static buffer is too small */
2387 if (StaticCandidateString.MaximumLength <
2388 (SegmentSize + ExtensionLength + FileNameString->Length))
2389 {
2390 /* At this point we should've been using our static buffer */
2391 ASSERT(StaticCandidateString.Buffer == StaticCandidateBuffer);
2392 if (StaticCandidateString.Buffer != StaticCandidateBuffer)
2393 {
2394 /* Something is really messed up if this was the dynamic string */
2395 DbgPrint("%s: internal error #1; "
2396 "CandidateString.Buffer = %p; "
2397 "StaticCandidateBuffer = %p\n",
2398 __FUNCTION__,
2399 StaticCandidateString.Buffer,
2400 StaticCandidateBuffer);
2401 Status = STATUS_INTERNAL_ERROR;
2402 goto Quickie;
2403 }
2404
2405 /* We checked before that the maximum possible size shoudl fit! */
2406 ASSERT((SegmentSize + FileNameString->Length + ExtensionLength) <
2407 UNICODE_STRING_MAX_BYTES);
2408 if ((SegmentSize + ExtensionLength + FileNameString->Length) >
2409 (UNICODE_STRING_MAX_BYTES - sizeof(WCHAR)))
2410 {
2411 /* For some reason it's not fitting anymore. Something messed up */
2412 DbgPrint("%s: internal error #2; SegmentSize = %u, "
2413 "FileName->Length = %u, DefaultExtensionLength = %u\n",
2414 __FUNCTION__,
2415 SegmentSize,
2416 FileNameString->Length,
2417 ExtensionLength);
2418 Status = STATUS_INTERNAL_ERROR;
2419 goto Quickie;
2420 }
2421
2422 /* Now allocate the dynamic string */
2423 StaticCandidateString.MaximumLength = FileNameString->Length +
2424 WorstCaseLength;
2425 StaticCandidateString.Buffer = RtlpAllocateStringMemory(WorstCaseLength,
2426 TAG_USTR);
2427 if (!StaticCandidateString.Buffer)
2428 {
2429 /* Out of memory, fail */
2430 DbgPrint("%s: Unable to allocate %u byte buffer for path candidate\n",
2431 __FUNCTION__,
2432 StaticCandidateString.MaximumLength);
2433 Status = STATUS_NO_MEMORY;
2434 goto Quickie;
2435 }
2436 }
2437
2438 /* Copy the path in the string */
2439 RtlCopyMemory(StaticCandidateString.Buffer, p, ByteCount);
2440
2441 /* Get to the end of the string, and add the trailing slash if missing */
2442 CandidateEnd = &StaticCandidateString.Buffer[ByteCount / sizeof(WCHAR)];
2443 if ((SegmentSize) && (SegmentSize != ByteCount))
2444 {
2445 *CandidateEnd++ = OBJ_NAME_PATH_SEPARATOR;
2446 }
2447
2448 /* Copy the filename now */
2449 RtlCopyMemory(CandidateEnd,
2450 FileNameString->Buffer,
2451 FileNameString->Length);
2452 CandidateEnd += (FileNameString->Length / sizeof(WCHAR));
2453
2454 /* Check if there was an extension */
2455 if (ExtensionString)
2456 {
2457 /* Copy the extension too */
2458 RtlCopyMemory(CandidateEnd,
2459 ExtensionString->Buffer,
2460 ExtensionString->Length);
2461 CandidateEnd += (ExtensionString->Length / sizeof(WCHAR));
2462 }
2463
2464 /* We are done, terminate it */
2465 *CandidateEnd = UNICODE_NULL;
2466
2467 /* Now set the final length of the string so it becomes valid */
2468 StaticCandidateString.Length = (USHORT)(CandidateEnd -
2469 StaticCandidateString.Buffer) *
2470 sizeof(WCHAR);
2471
2472 /* Check if this file exists */
2473 DPRINT("BUFFER: %S\n", StaticCandidateString.Buffer);
2474 if (RtlDoesFileExists_UEx(StaticCandidateString.Buffer, FALSE))
2475 {
2476 /* Awesome, it does, now get the full path */
2477 Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
2478 CallerBuffer,
2479 DynamicString,
2480 (PUNICODE_STRING*)FullNameOut,
2481 FilePartSize,
2482 NULL,
2483 &PathType,
2484 LengthNeeded);
2485 if (!(NT_SUCCESS(Status)) &&
2486 ((Status != STATUS_NO_SUCH_FILE) &&
2487 (Status != STATUS_BUFFER_TOO_SMALL)))
2488 {
2489 DbgPrint("%s: Failing because we thought we found %wZ on "
2490 "the search path, but RtlGetfullPathNameUStrEx() "
2491 "returned %08lx\n",
2492 __FUNCTION__,
2493 &StaticCandidateString,
2494 Status);
2495 }
2496 DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2497 goto Quickie;
2498 }
2499 else
2500 {
2501 /* Otherwise, move to the next path */
2502 if (SegmentEnd != End)
2503 {
2504 /* Handle the case of the path separator trailing */
2505 p = SegmentEnd + 1;
2506 }
2507 else
2508 {
2509 p = SegmentEnd;
2510 }
2511 }
2512 }
2513
2514 /* Loop finished and we didn't break out -- fail */
2515 Status = STATUS_NO_SUCH_FILE;
2516 }
2517 else
2518 {
2519 /* We have a full path, so check if it does exist */
2520 DPRINT("%wZ\n", FileNameString);
2521 if (!RtlDoesFileExists_UstrEx(FileNameString, TRUE))
2522 {
2523 /* It doesn't exist, did we have an extension? */
2524 if (!(ExtensionString) || !(ExtensionString->Length))
2525 {
2526 /* No extension, so just fail */
2527 Status = STATUS_NO_SUCH_FILE;
2528 goto Quickie;
2529 }
2530
2531 /* There was an extension, check if the filename already had one */
2532 if (!(Flags & 4) && (FileNameString->Length))
2533 {
2534 /* Parse the filename */
2535 p = FileNameString->Buffer;
2536 End = &p[FileNameString->Length / sizeof(WCHAR)];
2537 while (End > p)
2538 {
2539 /* If there's a path separator, there's no extension */
2540 if (IS_PATH_SEPARATOR(*--End)) break;
2541
2542 /* Othwerwise, did we find an extension dot? */
2543 if (*End == L'.')
2544 {
2545 /* File already had an extension, so fail */
2546 Status = STATUS_NO_SUCH_FILE;
2547 goto Quickie;
2548 }
2549 }
2550 }
2551
2552 /* So there is an extension, we'll try again by adding it */
2553 NamePlusExtLength = FileNameString->Length +
2554 ExtensionString->Length +
2555 sizeof(UNICODE_NULL);
2556 if (NamePlusExtLength > UNICODE_STRING_MAX_BYTES)
2557 {
2558 /* It won't fit in any kind of valid string, so fail */
2559 DbgPrint("%s: Failing because filename plus extension (%Iu bytes) is too big\n",
2560 __FUNCTION__,
2561 NamePlusExtLength);
2562 Status = STATUS_NAME_TOO_LONG;
2563 goto Quickie;
2564 }
2565
2566 /* Fill it fit in our temporary string? */
2567 if (NamePlusExtLength > StaticCandidateString.MaximumLength)
2568 {
2569 /* It won't fit anymore, allocate a dynamic string for it */
2570 StaticCandidateString.MaximumLength = NamePlusExtLength;
2571 StaticCandidateString.Buffer = RtlpAllocateStringMemory(NamePlusExtLength,
2572 TAG_USTR);
2573 if (!StaticCandidateString.Buffer)
2574 {
2575 /* Ran out of memory, so fail */
2576 DbgPrint("%s: Failing because allocating the dynamic filename buffer failed\n",
2577 __FUNCTION__);
2578 Status = STATUS_NO_MEMORY;
2579 goto Quickie;
2580 }
2581 }
2582
2583 /* Copy the filename */
2584 RtlCopyUnicodeString(&StaticCandidateString, FileNameString);
2585
2586 /* Copy the extension */
2587 RtlAppendUnicodeStringToString(&StaticCandidateString,
2588 ExtensionString);
2589
2590 DPRINT("SB: %wZ\n", &StaticCandidateString);
2591
2592 /* And check if this file now exists */
2593 if (!RtlDoesFileExists_UstrEx(&StaticCandidateString, TRUE))
2594 {
2595 /* Still no joy, fail out */
2596 Status = STATUS_NO_SUCH_FILE;
2597 goto Quickie;
2598 }
2599
2600 /* File was found, get the final full path */
2601 Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
2602 CallerBuffer,
2603 DynamicString,
2604 (PUNICODE_STRING*)FullNameOut,
2605 FilePartSize,
2606 NULL,
2607 &PathType,
2608 LengthNeeded);
2609 if (!(NT_SUCCESS(Status)) && (Status != STATUS_NO_SUCH_FILE))
2610 {
2611 DbgPrint("%s: Failing on \"%wZ\" because RtlGetfullPathNameUStrEx() "
2612 "failed with status %08lx\n",
2613 __FUNCTION__,
2614 &StaticCandidateString,
2615 Status);
2616 }
2617 DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2618 }
2619 else
2620 {
2621 /* File was found on the first try, get the final full path */
2622 Status = RtlGetFullPathName_UstrEx(FileNameString,
2623 CallerBuffer,
2624 DynamicString,
2625 (PUNICODE_STRING*)FullNameOut,
2626 FilePartSize,
2627 NULL,
2628 &PathType,
2629 LengthNeeded);
2630 if (!(NT_SUCCESS(Status)) &&
2631 ((Status != STATUS_NO_SUCH_FILE) &&
2632 (Status != STATUS_BUFFER_TOO_SMALL)))
2633 {
2634 DbgPrint("%s: Failing because RtlGetfullPathNameUStrEx() on %wZ "
2635 "failed with status %08lx\n",
2636 __FUNCTION__,
2637 FileNameString,
2638 Status);
2639 }
2640 DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2641 }
2642 }
2643
2644 Quickie:
2645 /* Anything that was not an error, turn into STATUS_SUCCESS */
2646 if (NT_SUCCESS(Status)) Status = STATUS_SUCCESS;
2647
2648 /* Check if we had a dynamic string */
2649 if ((StaticCandidateString.Buffer) &&
2650 (StaticCandidateString.Buffer != StaticCandidateBuffer))
2651 {
2652 /* Free it */
2653 RtlFreeUnicodeString(&StaticCandidateString);
2654 }
2655
2656 /* Return the status */
2657 return Status;
2658 }
2659
2660 /*
2661 * @implemented
2662 */
2663 BOOLEAN
2664 NTAPI
2665 RtlDoesFileExists_U(IN PCWSTR FileName)
2666 {
2667 /* Call the new function */
2668 return RtlDoesFileExists_UEx(FileName, TRUE);
2669 }
2670
2671 /* EOF */