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