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