[RTL]
[reactos.git] / reactos / lib / rtl / path.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: lib/rtl/path.c
5 * PURPOSE: Path and current directory functions
6 * PROGRAMMERS: Wine team
7 * Thomas Weidenmueller
8 * Gunnar Dalsnes
9 * 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 = (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 = 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 = 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 = 0, CurDirHandle, OldCurDirHandle = 0;
1013
1014 DPRINT("RtlSetCurrentDirectory_U %wZ\n", Path);
1015
1016 /* Can't set current directory on DOS device */
1017 if (RtlIsDosDeviceName_Ustr(Path))
1018 {
1019 return STATUS_NOT_A_DIRECTORY;
1020 }
1021
1022 /* Get current directory */
1023 RtlAcquirePebLock();
1024 CurDir = &NtCurrentPeb()->ProcessParameters->CurrentDirectory;
1025
1026 /* Check if we have to drop current handle */
1027 if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_DROP_OLD_HANDLE)
1028 {
1029 OldHandle = CurDir->Handle;
1030 CurDir->Handle = NULL;
1031 }
1032
1033 /* Allocate a buffer for full path (using max possible length */
1034 FullPath.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, CurDir->DosPath.MaximumLength);
1035 if (!FullPath.Buffer)
1036 {
1037 Status = STATUS_NO_MEMORY;
1038 goto Leave;
1039 }
1040
1041 /* Init string */
1042 FullPath.Length = 0;
1043 FullPath.MaximumLength = CurDir->DosPath.MaximumLength;
1044
1045 /* Get new directory full path */
1046 FullPathLength = RtlGetFullPathName_Ustr(Path, FullPath.MaximumLength, FullPath.Buffer, NULL, NULL, &PathType);
1047 if (!FullPathLength)
1048 {
1049 Status = STATUS_OBJECT_NAME_INVALID;
1050 goto Leave;
1051 }
1052
1053 SavedLength = FullPath.MaximumLength;
1054 CharLength = FullPathLength / sizeof(WCHAR);
1055
1056 if (FullPathLength > FullPath.MaximumLength)
1057 {
1058 Status = STATUS_NAME_TOO_LONG;
1059 goto Leave;
1060 }
1061
1062 /* Translate it to NT name */
1063 if (!RtlDosPathNameToNtPathName_U(FullPath.Buffer, &NtName, NULL, NULL))
1064 {
1065 Status = STATUS_OBJECT_NAME_INVALID;
1066 goto Leave;
1067 }
1068
1069 InitializeObjectAttributes(&ObjectAttributes, &NtName,
1070 OBJ_CASE_INSENSITIVE | OBJ_INHERIT,
1071 NULL, NULL);
1072
1073 /* If previous current directory was removable, then check it for dropping */
1074 if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_ALL_FLAGS)
1075 {
1076 /* Get back normal handle */
1077 CurDirHandle = (HANDLE)((ULONG_PTR)(CurDir->Handle) & ~RTL_CURDIR_ALL_FLAGS);
1078 CurDir->Handle = 0;
1079
1080 /* Get device information */
1081 Status = NtQueryVolumeInformationFile(CurDirHandle,
1082 &IoStatusBlock,
1083 &FileFsDeviceInfo,
1084 sizeof(FileFsDeviceInfo),
1085 FileFsDeviceInformation);
1086 /* Retry without taking care of removable device */
1087 if (!NT_SUCCESS(Status))
1088 {
1089 Status = RtlSetCurrentDirectory_U(Path);
1090 goto Leave;
1091 }
1092 }
1093 else
1094 {
1095 /* Open directory */
1096 Status = NtOpenFile(&CurDirHandle,
1097 SYNCHRONIZE | FILE_TRAVERSE,
1098 &ObjectAttributes,
1099 &IoStatusBlock,
1100 FILE_SHARE_READ | FILE_SHARE_WRITE,
1101 FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
1102 if (!NT_SUCCESS(Status)) goto Leave;
1103
1104 /* Get device information */
1105 Status = NtQueryVolumeInformationFile(CurDirHandle,
1106 &IoStatusBlock,
1107 &FileFsDeviceInfo,
1108 sizeof(FileFsDeviceInfo),
1109 FileFsDeviceInformation);
1110 if (!NT_SUCCESS(Status)) goto Leave;
1111 }
1112
1113 /* If device is removable, mark handle */
1114 if (FileFsDeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA)
1115 {
1116 CurDirHandle = (HANDLE)((ULONG_PTR)CurDirHandle | RTL_CURDIR_IS_REMOVABLE);
1117 }
1118
1119 FullPath.Length = FullPathLength;
1120
1121 /* If full path isn't \ terminated, do it */
1122 if (FullPath.Buffer[CharLength - 1] != L'\\')
1123 {
1124 if ((CharLength + 1) * sizeof(WCHAR) > SavedLength)
1125 {
1126 Status = STATUS_NAME_TOO_LONG;
1127 goto Leave;
1128 }
1129
1130 FullPath.Buffer[CharLength] = L'\\';
1131 FullPath.Buffer[CharLength + 1] = UNICODE_NULL;
1132 FullPath.Length += sizeof(WCHAR);
1133 }
1134
1135 /* If we have previous current directory with only us as reference, save it */
1136 if (RtlpCurDirRef != NULL && RtlpCurDirRef->RefCount == 1)
1137 {
1138 OldCurDirHandle = RtlpCurDirRef->Handle;
1139 }
1140 else
1141 {
1142 /* Allocate new current directory struct saving previous one */
1143 OldCurDir = RtlpCurDirRef;
1144 RtlpCurDirRef = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(RTLP_CURDIR_REF));
1145 if (!RtlpCurDirRef)
1146 {
1147 RtlpCurDirRef = OldCurDir;
1148 OldCurDir = NULL;
1149 Status = STATUS_NO_MEMORY;
1150 goto Leave;
1151 }
1152
1153 /* Set reference to 1 (us) */
1154 RtlpCurDirRef->RefCount = 1;
1155 }
1156
1157 /* Save new data */
1158 CurDir->Handle = CurDirHandle;
1159 RtlpCurDirRef->Handle = CurDirHandle;
1160 CurDirHandle = 0;
1161
1162 /* Copy full path */
1163 RtlCopyMemory(CurDir->DosPath.Buffer, FullPath.Buffer, FullPath.Length + sizeof(WCHAR));
1164 CurDir->DosPath.Length = FullPath.Length;
1165
1166 Status = STATUS_SUCCESS;
1167
1168 Leave:
1169 RtlReleasePebLock();
1170
1171 if (FullPath.Buffer)
1172 {
1173 RtlFreeHeap(RtlGetProcessHeap(), 0, FullPath.Buffer);
1174 }
1175
1176 if (NtName.Buffer)
1177 {
1178 RtlFreeHeap(RtlGetProcessHeap(), 0, NtName.Buffer);
1179 }
1180
1181 if (CurDirHandle) NtClose(CurDirHandle);
1182
1183 if (OldHandle) NtClose(OldHandle);
1184
1185 if (OldCurDirHandle) NtClose(OldCurDirHandle);
1186
1187 if (OldCurDir && InterlockedDecrement(&OldCurDir->RefCount) == 0)
1188 {
1189 NtClose(OldCurDir->Handle);
1190 RtlFreeHeap(RtlGetProcessHeap(), 0, OldCurDir);
1191 }
1192
1193 return Status;
1194 }
1195
1196
1197 /******************************************************************
1198 * collapse_path
1199 *
1200 * Helper for RtlGetFullPathName_U.
1201 * Get rid of . and .. components in the path.
1202 */
1203 void FORCEINLINE collapse_path( WCHAR *path, UINT mark )
1204 {
1205 WCHAR *p, *next;
1206
1207 /* convert every / into a \ */
1208 for (p = path; *p; p++) if (*p == '/') *p = '\\';
1209
1210 /* collapse duplicate backslashes */
1211 next = path + max( 1, mark );
1212 for (p = next; *p; p++) if (*p != '\\' || next[-1] != '\\') *next++ = *p;
1213 *next = 0;
1214
1215 p = path + mark;
1216 while (*p)
1217 {
1218 if (*p == '.')
1219 {
1220 switch(p[1])
1221 {
1222 case '\\': /* .\ component */
1223 next = p + 2;
1224 memmove( p, next, (wcslen(next) + 1) * sizeof(WCHAR) );
1225 continue;
1226 case 0: /* final . */
1227 if (p > path + mark) p--;
1228 *p = 0;
1229 continue;
1230 case '.':
1231 if (p[2] == '\\') /* ..\ component */
1232 {
1233 next = p + 3;
1234 if (p > path + mark)
1235 {
1236 p--;
1237 while (p > path + mark && p[-1] != '\\') p--;
1238 }
1239 memmove( p, next, (wcslen(next) + 1) * sizeof(WCHAR) );
1240 continue;
1241 }
1242 else if (!p[2]) /* final .. */
1243 {
1244 if (p > path + mark)
1245 {
1246 p--;
1247 while (p > path + mark && p[-1] != '\\') p--;
1248 if (p > path + mark) p--;
1249 }
1250 *p = 0;
1251 continue;
1252 }
1253 break;
1254 }
1255 }
1256 /* skip to the next component */
1257 while (*p && *p != '\\') p++;
1258 if (*p == '\\')
1259 {
1260 /* remove last dot in previous dir name */
1261 if (p > path + mark && p[-1] == '.') memmove( p-1, p, (wcslen(p) + 1) * sizeof(WCHAR) );
1262 else p++;
1263 }
1264 }
1265
1266 /* remove trailing spaces and dots (yes, Windows really does that, don't ask) */
1267 while (p > path + mark && (p[-1] == ' ' || p[-1] == '.')) p--;
1268 *p = 0;
1269 }
1270
1271
1272
1273 /******************************************************************
1274 * skip_unc_prefix
1275 *
1276 * Skip the \\share\dir\ part of a file name. Helper for RtlGetFullPathName_U.
1277 */
1278 static const WCHAR *skip_unc_prefix( const WCHAR *ptr )
1279 {
1280 ptr += 2;
1281 while (*ptr && !IS_PATH_SEPARATOR(*ptr)) ptr++; /* share name */
1282 while (IS_PATH_SEPARATOR(*ptr)) ptr++;
1283 while (*ptr && !IS_PATH_SEPARATOR(*ptr)) ptr++; /* dir name */
1284 while (IS_PATH_SEPARATOR(*ptr)) ptr++;
1285 return ptr;
1286 }
1287
1288
1289 /******************************************************************
1290 * get_full_path_helper
1291 *
1292 * Helper for RtlGetFullPathName_U
1293 * Note: name and buffer are allowed to point to the same memory spot
1294 */
1295 static ULONG get_full_path_helper(
1296 LPCWSTR name,
1297 LPWSTR buffer,
1298 ULONG size)
1299 {
1300 SIZE_T reqsize = 0, mark = 0, dep = 0, deplen;
1301 LPWSTR ins_str = NULL;
1302 LPCWSTR ptr;
1303 const UNICODE_STRING* cd;
1304 WCHAR tmp[4];
1305
1306 /* return error if name only consists of spaces */
1307 for (ptr = name; *ptr; ptr++) if (*ptr != ' ') break;
1308 if (!*ptr) return 0;
1309
1310 RtlAcquirePebLock();
1311
1312 //cd = &((PCURDIR)&NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->CurrentDirectory.DosPath)->DosPath;
1313 cd = &NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->CurrentDirectory.DosPath;
1314
1315 switch (RtlDetermineDosPathNameType_U(name))
1316 {
1317 case RtlPathTypeUncAbsolute: /* \\foo */
1318 ptr = skip_unc_prefix( name );
1319 mark = (ptr - name);
1320 break;
1321
1322 case RtlPathTypeLocalDevice: /* \\.\foo */
1323 mark = 4;
1324 break;
1325
1326 case RtlPathTypeDriveAbsolute: /* c:\foo */
1327 reqsize = sizeof(WCHAR);
1328 tmp[0] = towupper(name[0]);
1329 ins_str = tmp;
1330 dep = 1;
1331 mark = 3;
1332 break;
1333
1334 case RtlPathTypeDriveRelative: /* c:foo */
1335 dep = 2;
1336 if (towupper(name[0]) != towupper(cd->Buffer[0]) || cd->Buffer[1] != ':')
1337 {
1338 UNICODE_STRING var, val;
1339
1340 tmp[0] = '=';
1341 tmp[1] = name[0];
1342 tmp[2] = ':';
1343 tmp[3] = '\0';
1344 var.Length = 3 * sizeof(WCHAR);
1345 var.MaximumLength = 4 * sizeof(WCHAR);
1346 var.Buffer = tmp;
1347 val.Length = 0;
1348 val.MaximumLength = (USHORT)size;
1349 val.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, size);
1350 if (val.Buffer == NULL)
1351 {
1352 reqsize = 0;
1353 goto done;
1354 }
1355
1356 switch (RtlQueryEnvironmentVariable_U(NULL, &var, &val))
1357 {
1358 case STATUS_SUCCESS:
1359 /* FIXME: Win2k seems to check that the environment variable actually points
1360 * to an existing directory. If not, root of the drive is used
1361 * (this seems also to be the only spot in RtlGetFullPathName that the
1362 * existence of a part of a path is checked)
1363 */
1364 /* fall thru */
1365 case STATUS_BUFFER_TOO_SMALL:
1366 reqsize = val.Length + sizeof(WCHAR); /* append trailing '\\' */
1367 val.Buffer[val.Length / sizeof(WCHAR)] = '\\';
1368 ins_str = val.Buffer;
1369 break;
1370 case STATUS_VARIABLE_NOT_FOUND:
1371 reqsize = 3 * sizeof(WCHAR);
1372 tmp[0] = name[0];
1373 tmp[1] = ':';
1374 tmp[2] = '\\';
1375 ins_str = tmp;
1376 RtlFreeHeap(RtlGetProcessHeap(), 0, val.Buffer);
1377 break;
1378 default:
1379 DPRINT1("Unsupported status code\n");
1380 RtlFreeHeap(RtlGetProcessHeap(), 0, val.Buffer);
1381 break;
1382 }
1383 mark = 3;
1384 break;
1385 }
1386 /* fall through */
1387
1388 case RtlPathTypeRelative: /* foo */
1389 reqsize = cd->Length;
1390 ins_str = cd->Buffer;
1391 if (cd->Buffer[1] != ':')
1392 {
1393 ptr = skip_unc_prefix( cd->Buffer );
1394 mark = ptr - cd->Buffer;
1395 }
1396 else mark = 3;
1397 break;
1398
1399 case RtlPathTypeRooted: /* \xxx */
1400 #ifdef __WINE__
1401 if (name[0] == '/') /* may be a Unix path */
1402 {
1403 const WCHAR *ptr = name;
1404 int drive = find_drive_root( &ptr );
1405 if (drive != -1)
1406 {
1407 reqsize = 3 * sizeof(WCHAR);
1408 tmp[0] = 'A' + drive;
1409 tmp[1] = ':';
1410 tmp[2] = '\\';
1411 ins_str = tmp;
1412 mark = 3;
1413 dep = ptr - name;
1414 break;
1415 }
1416 }
1417 #endif
1418 if (cd->Buffer[1] == ':')
1419 {
1420 reqsize = 2 * sizeof(WCHAR);
1421 tmp[0] = cd->Buffer[0];
1422 tmp[1] = ':';
1423 ins_str = tmp;
1424 mark = 3;
1425 }
1426 else
1427 {
1428 ptr = skip_unc_prefix( cd->Buffer );
1429 reqsize = (ptr - cd->Buffer) * sizeof(WCHAR);
1430 mark = reqsize / sizeof(WCHAR);
1431 ins_str = cd->Buffer;
1432 }
1433 break;
1434
1435 case RtlPathTypeRootLocalDevice: /* \\. */
1436 reqsize = 4 * sizeof(WCHAR);
1437 dep = 3;
1438 tmp[0] = '\\';
1439 tmp[1] = '\\';
1440 tmp[2] = '.';
1441 tmp[3] = '\\';
1442 ins_str = tmp;
1443 mark = 4;
1444 break;
1445
1446 case RtlPathTypeUnknown:
1447 goto done;
1448 }
1449
1450 /* enough space ? */
1451 deplen = wcslen(name + dep) * sizeof(WCHAR);
1452 if (reqsize + deplen + sizeof(WCHAR) > size)
1453 {
1454 /* not enough space, return need size (including terminating '\0') */
1455 reqsize += deplen + sizeof(WCHAR);
1456 goto done;
1457 }
1458
1459 memmove(buffer + reqsize / sizeof(WCHAR), name + dep, deplen + sizeof(WCHAR));
1460 if (reqsize) memcpy(buffer, ins_str, reqsize);
1461 reqsize += deplen;
1462
1463 if (ins_str != tmp && ins_str != cd->Buffer)
1464 RtlFreeHeap(RtlGetProcessHeap(), 0, ins_str);
1465
1466 collapse_path( buffer, (ULONG)mark );
1467 reqsize = wcslen(buffer) * sizeof(WCHAR);
1468
1469 done:
1470 RtlReleasePebLock();
1471 return (ULONG)reqsize;
1472 }
1473
1474
1475 /******************************************************************
1476 * RtlGetFullPathName_U (NTDLL.@)
1477 *
1478 * Returns the number of bytes written to buffer (not including the
1479 * terminating NULL) if the function succeeds, or the required number of bytes
1480 * (including the terminating NULL) if the buffer is too small.
1481 *
1482 * file_part will point to the filename part inside buffer (except if we use
1483 * DOS device name, in which case file_in_buf is NULL)
1484 *
1485 * @implemented
1486 */
1487 ULONG NTAPI RtlGetFullPathName_U(
1488 const WCHAR* name,
1489 ULONG size,
1490 WCHAR* buffer,
1491 WCHAR** file_part)
1492 {
1493 WCHAR* ptr;
1494 ULONG dosdev;
1495 ULONG reqsize;
1496
1497 DPRINT("RtlGetFullPathName_U(%S %lu %p %p)\n", name, size, buffer, file_part);
1498
1499 if (!name || !*name) return 0;
1500
1501 if (file_part) *file_part = NULL;
1502
1503 /* check for DOS device name */
1504 dosdev = RtlIsDosDeviceName_U((WCHAR*)name);
1505 if (dosdev)
1506 {
1507 DWORD offset = HIWORD(dosdev) / sizeof(WCHAR); /* get it in WCHARs, not bytes */
1508 DWORD sz = LOWORD(dosdev); /* in bytes */
1509
1510 if (8 + sz + 2 > size) return sz + 10;
1511 wcscpy(buffer, DeviceRootW);
1512 memmove(buffer + 4, name + offset, sz);
1513 buffer[4 + sz / sizeof(WCHAR)] = '\0';
1514 /* file_part isn't set in this case */
1515 return sz + 8;
1516 }
1517
1518 reqsize = get_full_path_helper(name, buffer, size);
1519 if (!reqsize) return 0;
1520 if (reqsize > size)
1521 {
1522 LPWSTR tmp = RtlAllocateHeap(RtlGetProcessHeap(), 0, reqsize);
1523 if (tmp == NULL) return 0;
1524 reqsize = get_full_path_helper(name, tmp, reqsize);
1525 if (reqsize + sizeof(WCHAR) > size) /* it may have worked the second time */
1526 {
1527 RtlFreeHeap(RtlGetProcessHeap(), 0, tmp);
1528 return reqsize + sizeof(WCHAR);
1529 }
1530 memcpy( buffer, tmp, reqsize + sizeof(WCHAR) );
1531 RtlFreeHeap(RtlGetProcessHeap(), 0, tmp);
1532 }
1533
1534 /* find file part */
1535 if (file_part && (ptr = wcsrchr(buffer, '\\')) != NULL && ptr >= buffer + 2 && *++ptr)
1536 *file_part = ptr;
1537 return reqsize;
1538 }
1539
1540 /*
1541 * @implemented
1542 */
1543 BOOLEAN
1544 NTAPI
1545 RtlDosPathNameToNtPathName_U(IN PCWSTR DosName,
1546 OUT PUNICODE_STRING NtName,
1547 OUT PCWSTR *PartName,
1548 OUT PRTL_RELATIVE_NAME_U RelativeName)
1549 {
1550 /* Call the internal function */
1551 return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(FALSE,
1552 DosName,
1553 NtName,
1554 PartName,
1555 RelativeName));
1556 }
1557
1558 /*
1559 * @implemented
1560 */
1561 NTSTATUS
1562 NTAPI
1563 RtlDosPathNameToNtPathName_U_WithStatus(IN PCWSTR DosName,
1564 OUT PUNICODE_STRING NtName,
1565 OUT PCWSTR *PartName,
1566 OUT PRTL_RELATIVE_NAME_U RelativeName)
1567 {
1568 /* Call the internal function */
1569 return RtlpDosPathNameToRelativeNtPathName_U(FALSE,
1570 DosName,
1571 NtName,
1572 PartName,
1573 RelativeName);
1574 }
1575
1576 /*
1577 * @implemented
1578 */
1579 BOOLEAN
1580 NTAPI
1581 RtlDosPathNameToRelativeNtPathName_U(IN PCWSTR DosName,
1582 OUT PUNICODE_STRING NtName,
1583 OUT PCWSTR *PartName,
1584 OUT PRTL_RELATIVE_NAME_U RelativeName)
1585 {
1586 /* Call the internal function */
1587 ASSERT(RelativeName);
1588 return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(TRUE,
1589 DosName,
1590 NtName,
1591 PartName,
1592 RelativeName));
1593 }
1594
1595 /*
1596 * @implemented
1597 */
1598 NTSTATUS
1599 NTAPI
1600 RtlDosPathNameToRelativeNtPathName_U_WithStatus(IN PCWSTR DosName,
1601 OUT PUNICODE_STRING NtName,
1602 OUT PCWSTR *PartName,
1603 OUT PRTL_RELATIVE_NAME_U RelativeName)
1604 {
1605 /* Call the internal function */
1606 ASSERT(RelativeName);
1607 return RtlpDosPathNameToRelativeNtPathName_U(TRUE,
1608 DosName,
1609 NtName,
1610 PartName,
1611 RelativeName);
1612 }
1613
1614 /*
1615 * @unimplemented
1616 */
1617 NTSTATUS NTAPI
1618 RtlNtPathNameToDosPathName(ULONG Unknown1, ULONG Unknown2, ULONG Unknown3, ULONG Unknown4)
1619 {
1620 DPRINT1("RtlNtPathNameToDosPathName: stub\n");
1621 return STATUS_NOT_IMPLEMENTED;
1622 }
1623
1624 /*
1625 * @implemented
1626 */
1627 ULONG
1628 NTAPI
1629 RtlDosSearchPath_U(IN PCWSTR Path,
1630 IN PCWSTR FileName,
1631 IN PCWSTR Extension,
1632 IN ULONG Size,
1633 IN PWSTR Buffer,
1634 OUT PWSTR *PartName)
1635 {
1636 NTSTATUS Status;
1637 ULONG ExtensionLength, Length, FileNameLength, PathLength;
1638 UNICODE_STRING TempString;
1639 PWCHAR NewBuffer, BufferStart;
1640 PCWSTR p;
1641
1642 /* Validate the input */
1643 if (!(Path) || !(FileName)) return 0;
1644
1645 /* Check if this is an absolute path */
1646 if (RtlDetermineDosPathNameType_U(FileName) != RtlPathTypeRelative)
1647 {
1648 /* Check if the file exists */
1649 if (RtlDoesFileExists_UEx(FileName, TRUE))
1650 {
1651 /* Get the full name, which does the DOS lookup */
1652 return RtlGetFullPathName_U(FileName, Size, Buffer, PartName);
1653 }
1654
1655 /* Doesn't exist, so fail */
1656 return 0;
1657 }
1658
1659 /* Scan the filename */
1660 p = FileName;
1661 while (*p)
1662 {
1663 /* Looking for an extension */
1664 if (*p == '.')
1665 {
1666 /* No extension string needed -- it's part of the filename */
1667 Extension = NULL;
1668 break;
1669 }
1670
1671 /* Next character */
1672 p++;
1673 }
1674
1675 /* Do we have an extension? */
1676 if (!Extension)
1677 {
1678 /* Nope, don't worry about one */
1679 ExtensionLength = 0;
1680 }
1681 else
1682 {
1683 /* Build a temporary string to get the extension length */
1684 Status = RtlInitUnicodeStringEx(&TempString, Extension);
1685 if (!NT_SUCCESS(Status)) return 0;
1686 ExtensionLength = TempString.Length;
1687 }
1688
1689 /* Build a temporary string to get the path length */
1690 Status = RtlInitUnicodeStringEx(&TempString, Path);
1691 if (!NT_SUCCESS(Status)) return 0;
1692 PathLength = TempString.Length;
1693
1694 /* Build a temporary string to get the filename length */
1695 Status = RtlInitUnicodeStringEx(&TempString, FileName);
1696 if (!NT_SUCCESS(Status)) return 0;
1697 FileNameLength = TempString.Length;
1698
1699 /* Allocate the buffer for the new string name */
1700 NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
1701 0,
1702 FileNameLength +
1703 ExtensionLength +
1704 PathLength +
1705 3 * sizeof(WCHAR));
1706 if (!NewBuffer)
1707 {
1708 /* Fail the call */
1709 DbgPrint("%s: Failing due to out of memory (RtlAllocateHeap failure)\n",
1710 __FUNCTION__);
1711 return 0;
1712 }
1713
1714 /* Final loop to build the path */
1715 while (TRUE)
1716 {
1717 /* Check if we have a valid character */
1718 BufferStart = NewBuffer;
1719 if (*Path)
1720 {
1721 /* Loop as long as there's no semicolon */
1722 while (*Path != ';')
1723 {
1724 /* Copy the next character */
1725 *BufferStart++ = *Path++;
1726 if (!*Path) break;
1727 }
1728
1729 /* We found a semi-colon, to stop path processing on this loop */
1730 if (*Path == ';') ++Path;
1731 }
1732
1733 /* Add a terminating slash if needed */
1734 if ((BufferStart != NewBuffer) && (BufferStart[-1] != '\\'))
1735 {
1736 *BufferStart++ = '\\';
1737 }
1738
1739 /* Bail out if we reached the end */
1740 if (!*Path) Path = NULL;
1741
1742 /* Copy the file name and check if an extension is needed */
1743 RtlCopyMemory(BufferStart, FileName, FileNameLength);
1744 if (ExtensionLength)
1745 {
1746 /* Copy the extension too */
1747 RtlCopyMemory((PCHAR)BufferStart + FileNameLength,
1748 Extension,
1749 ExtensionLength + sizeof(WCHAR));
1750 }
1751 else
1752 {
1753 /* Just NULL-terminate */
1754 *(PWCHAR)((PCHAR)BufferStart + FileNameLength) = UNICODE_NULL;
1755 }
1756
1757 /* Now, does this file exist? */
1758 if (RtlDoesFileExists_UEx(NewBuffer, FALSE))
1759 {
1760 /* Call the full-path API to get the length */
1761 Length = RtlGetFullPathName_U(NewBuffer, Size, Buffer, PartName);
1762 break;
1763 }
1764
1765 /* If we got here, path doesn't exist, so fail the call */
1766 Length = 0;
1767 if (!Path) break;
1768 }
1769
1770 /* Free the allocation and return the length */
1771 RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
1772 return Length;
1773 }
1774
1775 /*
1776 * @implemented
1777 */
1778 ULONG
1779 NTAPI
1780 RtlGetFullPathName_UstrEx(IN PUNICODE_STRING FileName,
1781 IN PUNICODE_STRING StaticString,
1782 IN PUNICODE_STRING DynamicString,
1783 IN PUNICODE_STRING *StringUsed,
1784 IN PSIZE_T FilePartSize,
1785 OUT PBOOLEAN NameInvalid,
1786 OUT RTL_PATH_TYPE* PathType,
1787 OUT PULONG LengthNeeded)
1788 {
1789 NTSTATUS Status;
1790 PWCHAR StaticBuffer;
1791 PCWCH ShortName;
1792 ULONG Length;
1793 USHORT StaticLength;
1794 UNICODE_STRING TempDynamicString;
1795
1796 /* Initialize all our locals */
1797 ShortName = NULL;
1798 StaticBuffer = NULL;
1799 TempDynamicString.Buffer = NULL;
1800
1801 /* Initialize the input parameters */
1802 if (StringUsed) *StringUsed = NULL;
1803 if (LengthNeeded) *LengthNeeded = 0;
1804 if (FilePartSize) *FilePartSize = 0;
1805
1806 /* Check for invalid parameters */
1807 if ((DynamicString) && !(StringUsed) && (StaticString))
1808 {
1809 return STATUS_INVALID_PARAMETER;
1810 }
1811
1812 /* Check if we did not get an input string */
1813 if (!StaticString)
1814 {
1815 /* Allocate one */
1816 StaticLength = MAX_PATH * sizeof(WCHAR);
1817 StaticBuffer = RtlpAllocateStringMemory(MAX_PATH * sizeof(WCHAR), TAG_USTR);
1818 if (!StaticBuffer) return STATUS_NO_MEMORY;
1819 }
1820 else
1821 {
1822 /* Use the one we received */
1823 StaticBuffer = StaticString->Buffer;
1824 StaticLength = StaticString->MaximumLength;
1825 }
1826
1827 /* Call the lower-level function */
1828 Length = RtlGetFullPathName_Ustr(FileName,
1829 StaticLength,
1830 StaticBuffer,
1831 &ShortName,
1832 NameInvalid,
1833 PathType);
1834 DPRINT("Length: %d StaticBuffer: %S\n", Length, StaticBuffer);
1835 if (!Length)
1836 {
1837 /* Fail if it failed */
1838 DbgPrint("%s(%d) - RtlGetFullPathName_Ustr() returned 0\n",
1839 __FUNCTION__,
1840 __LINE__);
1841 Status = STATUS_OBJECT_NAME_INVALID;
1842 goto Quickie;
1843 }
1844
1845 /* Check if it fits inside our static string */
1846 if ((StaticString) && (Length < StaticLength))
1847 {
1848 /* Set the final length */
1849 StaticString->Length = Length;
1850
1851 /* Set the file part size */
1852 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
1853
1854 /* Return the static string if requested */
1855 if (StringUsed) *StringUsed = StaticString;
1856
1857 /* We are done with success */
1858 Status = STATUS_SUCCESS;
1859 goto Quickie;
1860 }
1861
1862 /* Did we not have an input dynamic string ?*/
1863 if (!DynamicString)
1864 {
1865 /* Return the length we need */
1866 if (LengthNeeded) *LengthNeeded = Length;
1867
1868 /* And fail such that the caller can try again */
1869 Status = STATUS_BUFFER_TOO_SMALL;
1870 goto Quickie;
1871 }
1872
1873 /* Check if it fits in our static buffer */
1874 if ((StaticBuffer) && (Length < StaticLength))
1875 {
1876 /* NULL-terminate it */
1877 StaticBuffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
1878
1879 /* Set the settings for the dynamic string the caller sent */
1880 DynamicString->MaximumLength = StaticLength;
1881 DynamicString->Length = Length;
1882 DynamicString->Buffer = StaticBuffer;
1883
1884 /* Set the part size */
1885 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticBuffer) : 0;
1886
1887 /* Return the dynamic string if requested */
1888 if (StringUsed) *StringUsed = DynamicString;
1889
1890 /* Do not free the static buffer on exit, and return success */
1891 StaticBuffer = NULL;
1892 Status = STATUS_SUCCESS;
1893 goto Quickie;
1894 }
1895
1896 /* Now try again under the PEB lock */
1897 RtlAcquirePebLock();
1898 Length = RtlGetFullPathName_Ustr(FileName,
1899 StaticLength,
1900 StaticBuffer,
1901 &ShortName,
1902 NameInvalid,
1903 PathType);
1904 if (!Length)
1905 {
1906 /* It failed */
1907 DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
1908 __FUNCTION__, __LINE__);
1909 Status = STATUS_OBJECT_NAME_INVALID;
1910 goto Release;
1911 }
1912
1913 /* Check if it fits inside our static string now */
1914 if ((StaticString) && (Length < StaticLength))
1915 {
1916 /* Set the final length */
1917 StaticString->Length = Length;
1918
1919 /* Set the file part size */
1920 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
1921
1922 /* Return the static string if requested */
1923 if (StringUsed) *StringUsed = StaticString;
1924
1925 /* We are done with success */
1926 Status = STATUS_SUCCESS;
1927 goto Release;
1928 }
1929
1930 /* Check if the path won't even fit in a real string */
1931 if ((Length + sizeof(WCHAR)) > UNICODE_STRING_MAX_BYTES)
1932 {
1933 /* Name is way too long, fail */
1934 Status = STATUS_NAME_TOO_LONG;
1935 goto Release;
1936 }
1937
1938 /* Allocate the string to hold the path name now */
1939 TempDynamicString.Buffer = RtlpAllocateStringMemory(Length + sizeof(WCHAR),
1940 TAG_USTR);
1941 if (!TempDynamicString.Buffer)
1942 {
1943 /* Out of memory, fail */
1944 Status = STATUS_NO_MEMORY;
1945 goto Release;
1946 }
1947
1948 /* Add space for a NULL terminator, and now check the full path */
1949 TempDynamicString.MaximumLength = Length + sizeof(UNICODE_NULL);
1950 Length = RtlGetFullPathName_Ustr(FileName,
1951 Length,
1952 TempDynamicString.Buffer,
1953 &ShortName,
1954 NameInvalid,
1955 PathType);
1956 if (!Length)
1957 {
1958 /* Some path error, so fail out */
1959 DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
1960 __FUNCTION__, __LINE__);
1961 Status = STATUS_OBJECT_NAME_INVALID;
1962 goto Release;
1963 }
1964
1965 /* It should fit in the string we just allocated */
1966 ASSERT(Length < (TempDynamicString.MaximumLength - sizeof(WCHAR)));
1967 if (Length > TempDynamicString.MaximumLength)
1968 {
1969 /* This is really weird and would mean some kind of race */
1970 Status = STATUS_INTERNAL_ERROR;
1971 goto Release;
1972 }
1973
1974 /* Return the file part size */
1975 if (FilePartSize) *FilePartSize = ShortName ? (ShortName - TempDynamicString.Buffer) : 0;
1976
1977 /* Terminate the whole string now */
1978 TempDynamicString.Buffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
1979
1980 /* Finalize the string and return it to the user */
1981 DynamicString->Buffer = TempDynamicString.Buffer;
1982 DynamicString->Length = Length;
1983 DynamicString->MaximumLength = TempDynamicString.MaximumLength;
1984 if (StringUsed) *StringUsed = DynamicString;
1985
1986 /* Return success and make sure we don't free the buffer on exit */
1987 TempDynamicString.Buffer = NULL;
1988 Status = STATUS_SUCCESS;
1989
1990 Release:
1991 /* Release the PEB lock */
1992 RtlReleasePebLock();
1993
1994 Quickie:
1995 /* Free any buffers we should be freeing */
1996 DPRINT("Status: %lx %S %S\n", Status, StaticBuffer, TempDynamicString.Buffer);
1997 if ((StaticBuffer) && (StaticBuffer != StaticString->Buffer))
1998 {
1999 RtlpFreeMemory(StaticBuffer, TAG_USTR);
2000 }
2001 if (TempDynamicString.Buffer)
2002 {
2003 RtlpFreeMemory(TempDynamicString.Buffer, TAG_USTR);
2004 }
2005
2006 /* Print out any unusual errors */
2007 if ((NT_ERROR(Status)) &&
2008 (Status != STATUS_NO_SUCH_FILE) && (Status != STATUS_BUFFER_TOO_SMALL))
2009 {
2010 DbgPrint("RTL: %s - failing on filename %wZ with status %08lx\n",
2011 __FUNCTION__, FileName, Status);
2012 }
2013
2014 /* Return, we're all done */
2015 return Status;
2016 }
2017
2018 /*
2019 * @implemented
2020 */
2021 ULONG
2022 NTAPI
2023 RtlDosSearchPath_Ustr(IN ULONG Flags,
2024 IN PUNICODE_STRING PathString,
2025 IN PUNICODE_STRING FileNameString,
2026 IN PUNICODE_STRING ExtensionString,
2027 IN PUNICODE_STRING CallerBuffer,
2028 IN OUT PUNICODE_STRING DynamicString OPTIONAL,
2029 OUT PUNICODE_STRING* FullNameOut OPTIONAL,
2030 OUT PULONG FilePartSize OPTIONAL,
2031 OUT PULONG LengthNeeded OPTIONAL)
2032 {
2033 WCHAR StaticCandidateBuffer[MAX_PATH];
2034 UNICODE_STRING StaticCandidateString;
2035 NTSTATUS Status;
2036 RTL_PATH_TYPE PathType;
2037 PWCHAR p, End, CandidateEnd, SegmentEnd;
2038 ULONG SegmentSize, NamePlusExtLength, PathSize, MaxPathSize = 0, WorstCaseLength, ByteCount;
2039 USHORT ExtensionLength = 0;
2040 PUNICODE_STRING FullIsolatedPath;
2041 DPRINT("DOS Path Search: %lx %wZ %wZ %wZ %wZ %wZ\n",
2042 Flags, PathString, FileNameString, ExtensionString, CallerBuffer, DynamicString);
2043
2044 /* Initialize the input string */
2045 RtlInitEmptyUnicodeString(&StaticCandidateString,
2046 StaticCandidateBuffer,
2047 sizeof(StaticCandidateBuffer));
2048
2049 /* Initialize optional arguments */
2050 if (FullNameOut) *FullNameOut = NULL;
2051 if (FilePartSize) *FilePartSize = 0;
2052 if (DynamicString)
2053 {
2054 DynamicString->Length = DynamicString->MaximumLength = 0;
2055 DynamicString->Buffer = NULL;
2056 }
2057
2058 /* Check for invalid parameters */
2059 if ((Flags & ~7) ||
2060 !(PathString) ||
2061 !(FileNameString) ||
2062 ((CallerBuffer) && (DynamicString) && !(FullNameOut)))
2063 {
2064 /* Fail */
2065 DbgPrint("%s: Invalid parameters passed\n", __FUNCTION__);
2066 Status = STATUS_INVALID_PARAMETER;
2067 goto Quickie;
2068 }
2069
2070 /* First check what kind of path this is */
2071 PathType = RtlDetermineDosPathNameType_Ustr(FileNameString);
2072
2073 /* Check if the caller wants to prevent relative .\ and ..\ paths */
2074 if ((Flags & 2) &&
2075 (PathType == RtlPathTypeRelative) &&
2076 (FileNameString->Length >= (2 * sizeof(WCHAR))) &&
2077 (FileNameString->Buffer[0] == L'.') &&
2078 ((IS_PATH_SEPARATOR(FileNameString->Buffer[1])) ||
2079 ((FileNameString->Buffer[1] == L'.') &&
2080 ((FileNameString->Length >= (3 * sizeof(WCHAR))) &&
2081 (IS_PATH_SEPARATOR(FileNameString->Buffer[2]))))))
2082 {
2083 /* Yes, and this path is like that, so make it seem unknown */
2084 PathType = RtlPathTypeUnknown;
2085 }
2086
2087 /* Now check relative vs non-relative paths */
2088 if (PathType == RtlPathTypeRelative)
2089 {
2090 /* Does the caller want SxS? */
2091 if (Flags & 1)
2092 {
2093 /* Apply the SxS magic */
2094 FullIsolatedPath = NULL;
2095 Status = RtlDosApplyFileIsolationRedirection_Ustr(TRUE,
2096 FileNameString,
2097 ExtensionString,
2098 CallerBuffer,
2099 DynamicString,
2100 &FullIsolatedPath,
2101 NULL,
2102 FilePartSize,
2103 LengthNeeded);
2104 if (NT_SUCCESS(Status))
2105 {
2106 /* We found the SxS path, return it */
2107 if (FullNameOut) *FullNameOut = FullIsolatedPath;
2108 goto Quickie;
2109 }
2110 else if (Status != STATUS_SXS_KEY_NOT_FOUND)
2111 {
2112 /* Critical SxS error, fail */
2113 DbgPrint("%s: Failing because call to "
2114 "RtlDosApplyIsolationRedirection_Ustr(%wZ) failed with "
2115 "status 0x%08lx\n",
2116 __FUNCTION__,
2117 FileNameString,
2118 Status);
2119 goto Quickie;
2120 }
2121 }
2122
2123 /* No SxS key found, or not requested, check if there's an extension */
2124 if (ExtensionString)
2125 {
2126 /* Save the extension length, and check if there's a file name */
2127 ExtensionLength = ExtensionString->Length;
2128 if (FileNameString->Length)
2129 {
2130 /* Start parsing the file name */
2131 End = &FileNameString->Buffer[FileNameString->Length / sizeof(WCHAR)];
2132 while (End > FileNameString->Buffer)
2133 {
2134 /* If we find a path separator, there's no extension */
2135 if (IS_PATH_SEPARATOR(*--End)) break;
2136
2137 /* Otherwise, did we find an extension dot? */
2138 if (*End == L'.')
2139 {
2140 /* Ignore what the caller sent it, use the filename's */
2141 ExtensionString = NULL;
2142 ExtensionLength = 0;
2143 break;
2144 }
2145 }
2146 }
2147 }
2148
2149 /* Check if we got a path */
2150 if (PathString->Length)
2151 {
2152 /* Start parsing the path name, looking for path separators */
2153 End = &PathString->Buffer[PathString->Length / sizeof(WCHAR)];
2154 p = End;
2155 while ((p > PathString->Buffer) && (*--p == L';'))
2156 {
2157 /* This is the size of the path -- handle a trailing slash */
2158 PathSize = End - p - 1;
2159 if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
2160
2161 /* Check if we found a bigger path than before */
2162 if (PathSize > MaxPathSize) MaxPathSize = PathSize;
2163
2164 /* Keep going with the path after this path separator */
2165 End = p;
2166 }
2167
2168 /* This is the trailing path, run the same code as above */
2169 PathSize = End - p;
2170 if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
2171 if (PathSize > MaxPathSize) MaxPathSize = PathSize;
2172
2173 /* Finally, convert the largest path size into WCHAR */
2174 MaxPathSize *= sizeof(WCHAR);
2175 }
2176
2177 /* Use the extension, the file name, and the largest path as the size */
2178 WorstCaseLength = ExtensionLength +
2179 FileNameString->Length +
2180 MaxPathSize +
2181 sizeof(UNICODE_NULL);
2182 if (WorstCaseLength > UNICODE_STRING_MAX_BYTES)
2183 {
2184 /* It has to fit in a registry string, if not, fail here */
2185 DbgPrint("%s returning STATUS_NAME_TOO_LONG because the computed "
2186 "worst case file name length is %Iu bytes\n",
2187 __FUNCTION__,
2188 WorstCaseLength);
2189 Status = STATUS_NAME_TOO_LONG;
2190 goto Quickie;
2191 }
2192
2193 /* Scan the path now, to see if we can find the file */
2194 p = PathString->Buffer;
2195 End = &p[PathString->Length / sizeof(WCHAR)];
2196 while (p < End)
2197 {
2198 /* Find out where this path ends */
2199 for (SegmentEnd = p;
2200 ((SegmentEnd != End) && (*SegmentEnd != L';'));
2201 SegmentEnd++);
2202
2203 /* Compute the size of this path */
2204 ByteCount = SegmentSize = (SegmentEnd - p) * sizeof(WCHAR);
2205
2206 /* Handle trailing slash if there isn't one */
2207 if ((SegmentSize) && !(IS_PATH_SEPARATOR(*(SegmentEnd - 1))))
2208 {
2209 /* Add space for one */
2210 SegmentSize += sizeof(OBJ_NAME_PATH_SEPARATOR);
2211 }
2212
2213 /* Now check if our initial static buffer is too small */
2214 if (StaticCandidateString.MaximumLength <
2215 (SegmentSize + ExtensionLength + FileNameString->Length))
2216 {
2217 /* At this point we should've been using our static buffer */
2218 ASSERT(StaticCandidateString.Buffer == StaticCandidateBuffer);
2219 if (StaticCandidateString.Buffer != StaticCandidateBuffer)
2220 {
2221 /* Something is really messed up if this was the dynamic string */
2222 DbgPrint("%s: internal error #1; "
2223 "CandidateString.Buffer = %p; "
2224 "StaticCandidateBuffer = %p\n",
2225 __FUNCTION__,
2226 StaticCandidateString.Buffer,
2227 StaticCandidateBuffer);
2228 Status = STATUS_INTERNAL_ERROR;
2229 goto Quickie;
2230 }
2231
2232 /* We checked before that the maximum possible size shoudl fit! */
2233 ASSERT((SegmentSize + FileNameString->Length + ExtensionLength) <
2234 UNICODE_STRING_MAX_BYTES);
2235 if ((SegmentSize + ExtensionLength + FileNameString->Length) >
2236 (UNICODE_STRING_MAX_BYTES - sizeof(WCHAR)))
2237 {
2238 /* For some reason it's not fitting anymore. Something messed up */
2239 DbgPrint("%s: internal error #2; SegmentSize = %u, "
2240 "FileName->Length = %u, DefaultExtensionLength = %u\n",
2241 __FUNCTION__,
2242 SegmentSize,
2243 FileNameString->Length,
2244 ExtensionLength);
2245 Status = STATUS_INTERNAL_ERROR;
2246 goto Quickie;
2247 }
2248
2249 /* Now allocate the dynamic string */
2250 StaticCandidateString.MaximumLength = FileNameString->Length +
2251 WorstCaseLength;
2252 StaticCandidateString.Buffer = RtlpAllocateStringMemory(WorstCaseLength,
2253 TAG_USTR);
2254 if (!StaticCandidateString.Buffer)
2255 {
2256 /* Out of memory, fail */
2257 DbgPrint("%s: Unable to allocate %u byte buffer for path candidate\n",
2258 __FUNCTION__,
2259 StaticCandidateString.MaximumLength);
2260 Status = STATUS_NO_MEMORY;
2261 goto Quickie;
2262 }
2263 }
2264
2265 /* Copy the path in the string */
2266 RtlCopyMemory(StaticCandidateString.Buffer, p, ByteCount);
2267
2268 /* Get to the end of the string, and add the trailing slash if missing */
2269 CandidateEnd = &StaticCandidateString.Buffer[ByteCount / sizeof(WCHAR)];
2270 if ((SegmentSize) && (SegmentSize != ByteCount))
2271 {
2272 *CandidateEnd++ = OBJ_NAME_PATH_SEPARATOR;
2273 }
2274
2275 /* Copy the filename now */
2276 RtlCopyMemory(CandidateEnd,
2277 FileNameString->Buffer,
2278 FileNameString->Length);
2279 CandidateEnd += (FileNameString->Length / sizeof(WCHAR));
2280
2281 /* Check if there was an extension */
2282 if (ExtensionString)
2283 {
2284 /* Copy the extension too */
2285 RtlCopyMemory(CandidateEnd,
2286 ExtensionString->Buffer,
2287 ExtensionString->Length);
2288 CandidateEnd += (ExtensionString->Length / sizeof(WCHAR));
2289 }
2290
2291 /* We are done, terminate it */
2292 *CandidateEnd = UNICODE_NULL;
2293
2294 /* Now set the final length of the string so it becomes valid */
2295 StaticCandidateString.Length = (CandidateEnd -
2296 StaticCandidateString.Buffer) *
2297 sizeof(WCHAR);
2298
2299 /* Check if this file exists */
2300 DPRINT("BUFFER: %S\n", StaticCandidateString.Buffer);
2301 if (RtlDoesFileExists_UEx(StaticCandidateString.Buffer, FALSE))
2302 {
2303 /* Awesome, it does, now get the full path */
2304 Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
2305 CallerBuffer,
2306 DynamicString,
2307 (PUNICODE_STRING*)FullNameOut,
2308 FilePartSize,
2309 NULL,
2310 &PathType,
2311 LengthNeeded);
2312 if (!(NT_SUCCESS(Status)) &&
2313 ((Status != STATUS_NO_SUCH_FILE) &&
2314 (Status != STATUS_BUFFER_TOO_SMALL)))
2315 {
2316 DbgPrint("%s: Failing because we thought we found %wZ on "
2317 "the search path, but RtlGetfullPathNameUStrEx() "
2318 "returned %08lx\n",
2319 __FUNCTION__,
2320 Status);
2321 }
2322 DPRINT("STatus: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2323 goto Quickie;
2324 }
2325 else
2326 {
2327 /* Otherwise, move to the next path */
2328 if (SegmentEnd != End)
2329 {
2330 /* Handle the case of the path separator trailing */
2331 p = SegmentEnd + 1;
2332 }
2333 else
2334 {
2335 p = SegmentEnd;
2336 }
2337 }
2338 }
2339
2340 /* Loop finished and we didn't break out -- fail */
2341 Status = STATUS_NO_SUCH_FILE;
2342 }
2343 else
2344 {
2345 /* We have a full path, so check if it does exist */
2346 DPRINT("%wZ\n", FileNameString);
2347 if (!RtlDoesFileExists_UstrEx(FileNameString, TRUE))
2348 {
2349 /* It doesn't exist, did we have an extension? */
2350 if (!(ExtensionString) || !(ExtensionString->Length))
2351 {
2352 /* No extension, so just fail */
2353 Status = STATUS_NO_SUCH_FILE;
2354 goto Quickie;
2355 }
2356
2357 /* There was an extension, check if the filename already had one */
2358 if (!(Flags & 4) && (FileNameString->Length))
2359 {
2360 /* Parse the filename */
2361 p = FileNameString->Buffer;
2362 End = &p[FileNameString->Length / sizeof(WCHAR)];
2363 while (End > p)
2364 {
2365 /* If there's a path separator, there's no extension */
2366 if (IS_PATH_SEPARATOR(*--End)) break;
2367
2368 /* Othwerwise, did we find an extension dot? */
2369 if (*End == L'.')
2370 {
2371 /* File already had an extension, so fail */
2372 Status = STATUS_NO_SUCH_FILE;
2373 goto Quickie;
2374 }
2375 }
2376 }
2377
2378 /* So there is an extension, we'll try again by adding it */
2379 NamePlusExtLength = FileNameString->Length +
2380 ExtensionString->Length +
2381 sizeof(UNICODE_NULL);
2382 if (NamePlusExtLength > UNICODE_STRING_MAX_BYTES)
2383 {
2384 /* It won't fit in any kind of valid string, so fail */
2385 DbgPrint("%s: Failing because filename plus extension (%Iu bytes) is too big\n",
2386 __FUNCTION__,
2387 NamePlusExtLength);
2388 Status = STATUS_NAME_TOO_LONG;
2389 goto Quickie;
2390 }
2391
2392 /* Fill it fit in our temporary string? */
2393 if (NamePlusExtLength > StaticCandidateString.MaximumLength)
2394 {
2395 /* It won't fit anymore, allocate a dynamic string for it */
2396 StaticCandidateString.MaximumLength = NamePlusExtLength;
2397 StaticCandidateString.Buffer = RtlpAllocateStringMemory(NamePlusExtLength,
2398 TAG_USTR);
2399 if (!StaticCandidateString.Buffer)
2400 {
2401 /* Ran out of memory, so fail */
2402 DbgPrint("%s: Failing because allocating the dynamic filename buffer failed\n",
2403 __FUNCTION__);
2404 Status = STATUS_NO_MEMORY;
2405 goto Quickie;
2406 }
2407 }
2408
2409 /* Copy the filename */
2410 RtlCopyMemory(StaticCandidateString.Buffer,
2411 FileNameString->Buffer,
2412 FileNameString->Length);
2413
2414 /* Copy the extension */
2415 RtlCopyMemory(&StaticCandidateString.Buffer[FileNameString->Length / sizeof(WCHAR)],
2416 ExtensionString->Buffer,
2417 ExtensionString->Length);
2418
2419 /* Now NULL-terminate */
2420 StaticCandidateString.Buffer[StaticCandidateString.Length / sizeof(WCHAR)] = UNICODE_NULL;
2421
2422 /* Finalize the length of the string to make it valid */
2423 StaticCandidateString.Length = FileNameString->Length + ExtensionString->Length;
2424 DPRINT("SB: %wZ\n", &StaticCandidateString);
2425
2426 /* And check if this file now exists */
2427 if (!RtlDoesFileExists_UstrEx(&StaticCandidateString, TRUE))
2428 {
2429 /* Still no joy, fail out */
2430 Status = STATUS_NO_SUCH_FILE;
2431 goto Quickie;
2432 }
2433
2434 /* File was found, get the final full path */
2435 Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
2436 CallerBuffer,
2437 DynamicString,
2438 (PUNICODE_STRING*)FullNameOut,
2439 FilePartSize,
2440 NULL,
2441 &PathType,
2442 LengthNeeded);
2443 if (!(NT_SUCCESS(Status)) && (Status != STATUS_NO_SUCH_FILE))
2444 {
2445 DbgPrint("%s: Failing on \"%wZ\" because RtlGetfullPathNameUStrEx() "
2446 "failed with status %08lx\n",
2447 __FUNCTION__,
2448 &StaticCandidateString,
2449 Status);
2450 }
2451 DPRINT("STatus: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2452 }
2453 else
2454 {
2455 /* File was found on the first try, get the final full path */
2456 Status = RtlGetFullPathName_UstrEx(FileNameString,
2457 CallerBuffer,
2458 DynamicString,
2459 (PUNICODE_STRING*)FullNameOut,
2460 FilePartSize,
2461 NULL,
2462 &PathType,
2463 LengthNeeded);
2464 if (!(NT_SUCCESS(Status)) &&
2465 ((Status != STATUS_NO_SUCH_FILE) &&
2466 (Status != STATUS_BUFFER_TOO_SMALL)))
2467 {
2468 DbgPrint("%s: Failing because RtlGetfullPathNameUStrEx() on %wZ "
2469 "failed with status %08lx\n",
2470 __FUNCTION__,
2471 FileNameString,
2472 Status);
2473 }
2474 DPRINT("STatus: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
2475 }
2476 }
2477
2478 Quickie:
2479 /* Anything that was not an error, turn into STATUS_SUCCESS */
2480 if (NT_SUCCESS(Status)) Status = STATUS_SUCCESS;
2481
2482 /* Check if we had a dynamic string */
2483 if ((StaticCandidateString.Buffer) &&
2484 (StaticCandidateString.Buffer != StaticCandidateBuffer))
2485 {
2486 /* Free it */
2487 RtlFreeUnicodeString(&StaticCandidateString);
2488 }
2489
2490 /* Return the status */
2491 return Status;
2492 }
2493
2494 /*
2495 * @implemented
2496 */
2497 BOOLEAN
2498 NTAPI
2499 RtlDoesFileExists_U(IN PCWSTR FileName)
2500 {
2501 /* Call the new function */
2502 return RtlDoesFileExists_UEx(FileName, TRUE);
2503 }
2504
2505 /* EOF */