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