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