3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS system libraries
5 * FILE: lib/ntdll/rtl/path.c
6 * PURPOSE: Path and current directory functions
11 /* INCLUDES ******************************************************************/
17 /* DEFINITONS and MACROS ******************************************************/
19 #define MAX_PFX_SIZE 16
21 #define IS_PATH_SEPARATOR(x) (((x)==L'\\')||((x)==L'/'))
24 /* GLOBALS ********************************************************************/
26 static const WCHAR DeviceRootW
[] = L
"\\\\.\\";
28 static const UNICODE_STRING _condev
= RTL_CONSTANT_STRING(L
"\\\\.\\CON");
30 static const UNICODE_STRING _lpt
= RTL_CONSTANT_STRING(L
"LPT");
32 static const UNICODE_STRING _com
= RTL_CONSTANT_STRING(L
"COM");
34 static const UNICODE_STRING _prn
= RTL_CONSTANT_STRING(L
"PRN");
36 static const UNICODE_STRING _aux
= RTL_CONSTANT_STRING(L
"AUX");
38 static const UNICODE_STRING _con
= RTL_CONSTANT_STRING(L
"CON");
40 static const UNICODE_STRING _nul
= RTL_CONSTANT_STRING(L
"NUL");
42 /* FUNCTIONS *****************************************************************/
48 ULONG STDCALL
RtlGetLongestNtPathLength (VOID
)
50 return (MAX_PATH
+ 9);
59 RtlDetermineDosPathNameType_U(PCWSTR Path
)
61 DPRINT("RtlDetermineDosPathNameType_U %S\n", Path
);
68 if (IS_PATH_SEPARATOR(Path
[0]))
70 if (!IS_PATH_SEPARATOR(Path
[1])) return ABSOLUTE_PATH
; /* \xxx */
71 if (Path
[2] != L
'.') return UNC_PATH
; /* \\xxx */
72 if (IS_PATH_SEPARATOR(Path
[3])) return DEVICE_PATH
; /* \\.\xxx */
73 if (Path
[3]) return UNC_PATH
; /* \\.xxxx */
75 return UNC_DOT_PATH
; /* \\. */
79 /* FIXME: the Wine version of this line reads:
80 * if (!Path[1] || Path[1] != L':') return RELATIVE_PATH
81 * Should we do this too?
84 if (Path
[1] != L
':') return RELATIVE_PATH
; /* xxx */
85 if (IS_PATH_SEPARATOR(Path
[2])) return ABSOLUTE_DRIVE_PATH
; /* x:\xxx */
87 return RELATIVE_DRIVE_PATH
; /* x:xxx */
92 /* returns 0 if name is not valid DOS device name, or DWORD with
93 * offset in bytes to DOS device name from beginning of buffer in high word
94 * and size in bytes of DOS device name in low word */
100 RtlIsDosDeviceName_U(PWSTR DeviceName
)
106 UNICODE_STRING DeviceNameU
;
108 if (DeviceName
== NULL
)
113 while (DeviceName
[Length
])
118 Type
= RtlDetermineDosPathNameType_U(DeviceName
);
126 DeviceNameU
.Length
= DeviceNameU
.MaximumLength
= Length
* sizeof(WCHAR
);
127 DeviceNameU
.Buffer
= DeviceName
;
129 RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_condev
, TRUE
))
134 /* name can end with ':' */
135 if (Length
&& DeviceName
[Length
- 1 ] == L
':')
140 /* there can be spaces or points at the end of name */
141 wc
= DeviceName
+ Length
- 1;
142 while (Length
&& (*wc
== L
'.' || *wc
== L
' '))
148 /* let's find a beginning of name */
149 wc
= DeviceName
+ Length
- 1;
150 while (wc
> DeviceName
&& !IS_PATH_SEPARATOR(*(wc
- 1)))
154 Offset
= wc
- DeviceName
;
156 DeviceNameU
.Length
= DeviceNameU
.MaximumLength
= 3 * sizeof(WCHAR
);
157 DeviceNameU
.Buffer
= wc
;
159 /* check for LPTx or COMx */
160 if (Length
== 4 && wc
[3] >= L
'0' && wc
[3] <= L
'9')
167 if (RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_lpt
, TRUE
) ||
168 RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_com
, TRUE
))
170 return ((Offset
* 2) << 16 ) | 8;
175 /* check for PRN,AUX,NUL or CON */
177 (RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_prn
, TRUE
) ||
178 RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_aux
, TRUE
) ||
179 RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_nul
, TRUE
) ||
180 RtlEqualUnicodeString(&DeviceNameU
, (PUNICODE_STRING
)&_con
, TRUE
)))
182 return ((Offset
* 2) << 16) | 6;
193 RtlGetCurrentDirectory_U(ULONG MaximumLength
,
199 DPRINT ("RtlGetCurrentDirectory %lu %p\n", MaximumLength
, Buffer
);
203 cd
= (PCURDIR
)&(NtCurrentPeb ()->ProcessParameters
->CurrentDirectory
.DosPath
);
204 Length
= cd
->DosPath
.Length
/ sizeof(WCHAR
);
205 if (cd
->DosPath
.Buffer
[Length
- 1] == L
'\\' &&
206 cd
->DosPath
.Buffer
[Length
- 2] != L
':')
209 DPRINT ("cd->DosPath.Buffer %S Length %d\n",
210 cd
->DosPath
.Buffer
, Length
);
212 if (MaximumLength
/ sizeof(WCHAR
) > Length
)
216 Length
* sizeof(WCHAR
));
224 RtlReleasePebLock ();
226 DPRINT ("CurrentDirectory %S\n", Buffer
);
228 return (Length
* sizeof(WCHAR
));
236 RtlSetCurrentDirectory_U(PUNICODE_STRING dir
)
239 UNICODE_STRING envvar
;
240 FILE_FS_DEVICE_INFORMATION device_info
;
241 OBJECT_ATTRIBUTES Attr
;
242 IO_STATUS_BLOCK iosb
;
246 HANDLE handle
= NULL
;
250 DPRINT("RtlSetCurrentDirectory %wZ\n", dir
);
252 RtlAcquirePebLock ();
254 cd
= (PCURDIR
)&NtCurrentPeb ()->ProcessParameters
->CurrentDirectory
.DosPath
;
256 if (!RtlDosPathNameToNtPathName_U (dir
->Buffer
, &full
, 0, 0))
258 RtlReleasePebLock ();
259 return STATUS_OBJECT_NAME_INVALID
;
262 DPRINT("RtlSetCurrentDirectory: full %wZ\n",&full
);
264 InitializeObjectAttributes (&Attr
,
266 OBJ_CASE_INSENSITIVE
| OBJ_INHERIT
,
270 Status
= NtOpenFile (&handle
,
271 SYNCHRONIZE
| FILE_TRAVERSE
,
274 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
275 FILE_DIRECTORY_FILE
| FILE_SYNCHRONOUS_IO_NONALERT
);
277 if (!NT_SUCCESS(Status
))
279 RtlFreeUnicodeString( &full
);
280 RtlReleasePebLock ();
284 /* don't keep the directory handle open on removable media */
285 if (NT_SUCCESS(NtQueryVolumeInformationFile( handle
, &iosb
, &device_info
,
286 sizeof(device_info
), FileFsDeviceInformation
)) &&
287 (device_info
.Characteristics
& FILE_REMOVABLE_MEDIA
))
289 DPRINT1("don't keep the directory handle open on removable media\n");
295 /* What the heck is this all about??? It looks like its getting the long path,
296 * and if does, ITS WRONG! If current directory is set with a short path,
297 * GetCurrentDir should return a short path.
298 * If anyone agrees with me, remove this stuff.
302 filenameinfo
= RtlAllocateHeap(RtlGetProcessHeap(),
304 MAX_PATH
*sizeof(WCHAR
)+sizeof(ULONG
));
306 Status
= NtQueryInformationFile(handle
,
309 MAX_PATH
*sizeof(WCHAR
)+sizeof(ULONG
),
310 FileNameInformation
);
311 if (!NT_SUCCESS(Status
))
313 RtlFreeHeap(RtlGetProcessHeap(),
316 RtlFreeHeap(RtlGetProcessHeap(),
319 RtlFreeHeap(RtlGetProcessHeap(),
326 /* If it's just "\", we need special handling */
327 if (filenameinfo
->FileNameLength
> sizeof(WCHAR
))
329 wcs
= buf
+ size
/ sizeof(WCHAR
) - 1;
334 size
-= sizeof(WCHAR
);
338 Index
< filenameinfo
->FileNameLength
/ sizeof(WCHAR
);
341 if (filenameinfo
->FileName
[Index
] == '\\') backslashcount
++;
344 DPRINT("%d \n",backslashcount
);
345 for (;backslashcount
;wcs
--)
347 if (*wcs
=='\\') backslashcount
--;
351 RtlCopyMemory(wcs
, filenameinfo
->FileName
, filenameinfo
->FileNameLength
);
352 wcs
[filenameinfo
->FileNameLength
/ sizeof(WCHAR
)] = 0;
354 size
= (wcs
- buf
) * sizeof(WCHAR
) + filenameinfo
->FileNameLength
;
364 /* append trailing \ if missing */
365 size
= full
.Length
/ sizeof(WCHAR
);
367 ptr
+= 4; /* skip \??\ prefix */
370 /* This is ok because RtlDosPathNameToNtPathName_U returns a nullterminated string.
371 * So the nullterm is replaced with \
374 if (size
&& ptr
[size
- 1] != '\\') ptr
[size
++] = '\\';
376 memcpy( cd
->DosPath
.Buffer
, ptr
, size
* sizeof(WCHAR
));
377 cd
->DosPath
.Buffer
[size
] = 0;
378 cd
->DosPath
.Length
= size
* sizeof(WCHAR
);
381 /* FIXME: whats this all about??? Wine doesnt have this. -Gunnar */
382 if (cd
->DosPath
.Buffer
[1]==':')
384 envvar
.Length
= 2 * swprintf (var
, L
"=%c:", cd
->DosPath
.Buffer
[0]);
385 envvar
.MaximumLength
= 8;
388 RtlSetEnvironmentVariable(NULL
,
393 RtlFreeUnicodeString( &full
);
396 return STATUS_SUCCESS
;
401 /******************************************************************
404 * Helper for RtlGetFullPathName_U.
405 * 1) Convert slashes into backslashes
406 * 2) Get rid of duplicate backslashes
407 * 3) Get rid of . and .. components in the path.
409 static inline void collapse_path( WCHAR
*path
, UINT mark
)
413 /* convert every / into a \ */
414 for (p
= path
; *p
; p
++) if (*p
== '/') *p
= '\\';
416 /* collapse duplicate backslashes */
417 next
= path
+ max( 1, mark
);
418 for (p
= next
; *p
; p
++) if (*p
!= '\\' || next
[-1] != '\\') *next
++ = *p
;
428 case '\\': /* .\ component */
430 memmove( p
, next
, (wcslen(next
) + 1) * sizeof(WCHAR
) );
432 case 0: /* final . */
433 if (p
> path
+ mark
) p
--;
437 if (p
[2] == '\\') /* ..\ component */
443 while (p
> path
+ mark
&& p
[-1] != '\\') p
--;
445 memmove( p
, next
, (wcslen(next
) + 1) * sizeof(WCHAR
) );
448 else if (!p
[2]) /* final .. */
453 while (p
> path
+ mark
&& p
[-1] != '\\') p
--;
454 if (p
> path
+ mark
) p
--;
462 /* skip to the next component */
463 while (*p
&& *p
!= '\\') p
++;
467 /* remove trailing spaces and dots (yes, Windows really does that, don't ask) */
468 while (p
> path
+ mark
&& (p
[-1] == ' ' || p
[-1] == '.')) p
--;
474 /******************************************************************
477 * Skip the \\share\dir\ part of a file name. Helper for RtlGetFullPathName_U.
479 static const WCHAR
*skip_unc_prefix( const WCHAR
*ptr
)
482 while (*ptr
&& !IS_PATH_SEPARATOR(*ptr
)) ptr
++; /* share name */
483 while (IS_PATH_SEPARATOR(*ptr
)) ptr
++;
484 while (*ptr
&& !IS_PATH_SEPARATOR(*ptr
)) ptr
++; /* dir name */
485 while (IS_PATH_SEPARATOR(*ptr
)) ptr
++;
490 /******************************************************************
491 * get_full_path_helper
493 * Helper for RtlGetFullPathName_U
494 * Note: name and buffer are allowed to point to the same memory spot
496 static ULONG
get_full_path_helper(
501 ULONG reqsize
= 0, mark
= 0, dep
= 0, deplen
;
502 DOS_PATHNAME_TYPE type
;
503 LPWSTR ins_str
= NULL
;
505 const UNICODE_STRING
* cd
;
508 /* return error if name only consists of spaces */
509 for (ptr
= name
; *ptr
; ptr
++) if (*ptr
!= ' ') break;
514 cd
= &((PCURDIR
)&NtCurrentTeb()->ProcessEnvironmentBlock
->ProcessParameters
->CurrentDirectory
.DosPath
)->DosPath
;
516 switch (type
= RtlDetermineDosPathNameType_U(name
))
518 case UNC_PATH
: /* \\foo */
519 ptr
= skip_unc_prefix( name
);
523 case DEVICE_PATH
: /* \\.\foo */
527 case ABSOLUTE_DRIVE_PATH
: /* c:\foo */
528 reqsize
= sizeof(WCHAR
);
529 tmp
[0] = towupper(name
[0]);
535 case RELATIVE_DRIVE_PATH
: /* c:foo */
537 if (towupper(name
[0]) != towupper(cd
->Buffer
[0]) || cd
->Buffer
[1] != ':')
539 UNICODE_STRING var
, val
;
545 var
.Length
= 3 * sizeof(WCHAR
);
546 var
.MaximumLength
= 4 * sizeof(WCHAR
);
549 val
.MaximumLength
= size
;
550 val
.Buffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, size
);
552 switch (RtlQueryEnvironmentVariable_U(NULL
, &var
, &val
))
555 /* FIXME: Win2k seems to check that the environment variable actually points
556 * to an existing directory. If not, root of the drive is used
557 * (this seems also to be the only spot in RtlGetFullPathName that the
558 * existence of a part of a path is checked)
561 case STATUS_BUFFER_TOO_SMALL
:
562 reqsize
= val
.Length
+ sizeof(WCHAR
); /* append trailing '\\' */
563 val
.Buffer
[val
.Length
/ sizeof(WCHAR
)] = '\\';
564 ins_str
= val
.Buffer
;
566 case STATUS_VARIABLE_NOT_FOUND
:
567 reqsize
= 3 * sizeof(WCHAR
);
574 DPRINT1("Unsupported status code\n");
582 case RELATIVE_PATH
: /* foo */
583 reqsize
= cd
->Length
;
584 ins_str
= cd
->Buffer
;
585 if (cd
->Buffer
[1] != ':')
587 ptr
= skip_unc_prefix( cd
->Buffer
);
588 mark
= ptr
- cd
->Buffer
;
593 case ABSOLUTE_PATH
: /* \xxx */
595 if (name
[0] == '/') /* may be a Unix path */
597 const WCHAR
*ptr
= name
;
598 int drive
= find_drive_root( &ptr
);
601 reqsize
= 3 * sizeof(WCHAR
);
602 tmp
[0] = 'A' + drive
;
612 if (cd
->Buffer
[1] == ':')
614 reqsize
= 2 * sizeof(WCHAR
);
615 tmp
[0] = cd
->Buffer
[0];
622 ptr
= skip_unc_prefix( cd
->Buffer
);
623 reqsize
= (ptr
- cd
->Buffer
) * sizeof(WCHAR
);
624 mark
= reqsize
/ sizeof(WCHAR
);
625 ins_str
= cd
->Buffer
;
629 case UNC_DOT_PATH
: /* \\. */
630 reqsize
= 4 * sizeof(WCHAR
);
645 deplen
= wcslen(name
+ dep
) * sizeof(WCHAR
);
646 if (reqsize
+ deplen
+ sizeof(WCHAR
) > size
)
648 /* not enough space, return need size (including terminating '\0') */
649 reqsize
+= deplen
+ sizeof(WCHAR
);
653 memmove(buffer
+ reqsize
/ sizeof(WCHAR
), name
+ dep
, deplen
+ sizeof(WCHAR
));
654 if (reqsize
) memcpy(buffer
, ins_str
, reqsize
);
657 if (ins_str
&& ins_str
!= tmp
&& ins_str
!= cd
->Buffer
)
658 RtlFreeHeap(RtlGetProcessHeap(), 0, ins_str
);
660 collapse_path( buffer
, mark
);
661 reqsize
= wcslen(buffer
) * sizeof(WCHAR
);
669 /******************************************************************
670 * RtlGetFullPathName_U (NTDLL.@)
672 * Returns the number of bytes written to buffer (not including the
673 * terminating NULL) if the function succeeds, or the required number of bytes
674 * (including the terminating NULL) if the buffer is too small.
676 * file_part will point to the filename part inside buffer (except if we use
677 * DOS device name, in which case file_in_buf is NULL)
681 DWORD STDCALL
RtlGetFullPathName_U(
691 DPRINT("RtlGetFullPathName_U(%S %lu %p %p)\n", name
, size
, buffer
, file_part
);
693 if (!name
|| !*name
) return 0;
695 if (file_part
) *file_part
= NULL
;
697 /* check for DOS device name */
698 dosdev
= RtlIsDosDeviceName_U((WCHAR
*)name
);
701 DWORD offset
= HIWORD(dosdev
) / sizeof(WCHAR
); /* get it in WCHARs, not bytes */
702 DWORD sz
= LOWORD(dosdev
); /* in bytes */
704 if (8 + sz
+ 2 > size
) return sz
+ 10;
705 wcscpy(buffer
, DeviceRootW
);
706 memmove(buffer
+ 4, name
+ offset
, sz
);
707 buffer
[4 + sz
/ sizeof(WCHAR
)] = '\0';
708 /* file_part isn't set in this case */
712 reqsize
= get_full_path_helper(name
, buffer
, size
);
713 if (!reqsize
) return 0;
716 LPWSTR tmp
= RtlAllocateHeap(RtlGetProcessHeap(), 0, reqsize
);
717 reqsize
= get_full_path_helper(name
, tmp
, reqsize
);
718 if (reqsize
> size
) /* it may have worked the second time */
720 RtlFreeHeap(RtlGetProcessHeap(), 0, tmp
);
721 return reqsize
+ sizeof(WCHAR
);
723 memcpy( buffer
, tmp
, reqsize
+ sizeof(WCHAR
) );
724 RtlFreeHeap(RtlGetProcessHeap(), 0, tmp
);
728 if (file_part
&& (ptr
= wcsrchr(buffer
, '\\')) != NULL
&& ptr
>= buffer
+ 2 && *++ptr
)
738 RtlDosPathNameToNtPathName_U(PWSTR dosname
,
739 PUNICODE_STRING ntname
,
750 WCHAR fullname
[MAX_PATH
+ 1];
754 RtlAcquirePebLock ();
756 RtlInitUnicodeString (&us
, dosname
);
760 /* check for "\\?\" - allows to use very long filenames ( up to 32k ) */
761 if (Buffer
[0] == L
'\\' && Buffer
[1] == L
'\\' &&
762 Buffer
[2] == L
'?' && Buffer
[3] == L
'\\')
764 // if( f_77F68606( &us, ntname, shortname, nah ) )
766 // RtlReleasePebLock ();
770 RtlReleasePebLock ();
775 Buffer
= RtlAllocateHeap (RtlGetProcessHeap (),
777 sizeof( fullname
) + MAX_PFX_SIZE
);
780 RtlReleasePebLock ();
784 Size
= RtlGetFullPathName_U (dosname
,
788 if (Size
== 0 || Size
> MAX_PATH
* sizeof(WCHAR
))
790 RtlFreeHeap (RtlGetProcessHeap (),
793 RtlReleasePebLock ();
799 memcpy (Buffer
, L
"\\??\\", 4 * sizeof(WCHAR
));
802 Type
= RtlDetermineDosPathNameType_U (fullname
);
806 memcpy (Buffer
+ tmpLength
, L
"UNC\\", 4 * sizeof(WCHAR
));
815 Length
= wcslen(fullname
+ Offset
);
816 memcpy (Buffer
+ tmpLength
, fullname
+ Offset
, (Length
+ 1) * sizeof(WCHAR
));
818 if (Type
== ABSOLUTE_DRIVE_PATH
||
819 Type
== RELATIVE_DRIVE_PATH
)
821 /* make the drive letter to uppercase */
822 Buffer
[tmpLength
] = towupper(Buffer
[tmpLength
]);
825 /* set NT filename */
826 ntname
->Length
= Length
* sizeof(WCHAR
);
827 ntname
->MaximumLength
= sizeof(fullname
) + MAX_PFX_SIZE
;
828 ntname
->Buffer
= Buffer
;
830 /* set pointer to file part if possible */
831 if (FilePart
&& *FilePart
)
832 *FilePart
= Buffer
+ Length
- wcslen (*FilePart
);
834 /* Set name and handle structure if possible */
837 memset (nah
, 0, sizeof(CURDIR
));
838 cd
= (PCURDIR
)&(NtCurrentPeb ()->ProcessParameters
->CurrentDirectory
.DosPath
);
839 if (Type
== 5 && cd
->Handle
)
841 RtlInitUnicodeString(&us
, fullname
);
842 if (RtlEqualUnicodeString(&us
, &cd
->DosPath
, TRUE
))
844 Length
= ((cd
->DosPath
.Length
/ sizeof(WCHAR
)) - Offset
) + ((Type
== 1) ? 8 : 4);
845 nah
->DosPath
.Buffer
= Buffer
+ Length
;
846 nah
->DosPath
.Length
= ntname
->Length
- (Length
* sizeof(WCHAR
));
847 nah
->DosPath
.MaximumLength
= nah
->DosPath
.Length
;
848 nah
->Handle
= cd
->Handle
;
878 Type
= RtlDetermineDosPathNameType_U (name
);
882 Length
= wcslen (sp
);
883 Length
+= wcslen (name
);
884 if (wcschr (name
, L
'.'))
887 Length
+= wcslen (ext
);
889 full_name
= (WCHAR
*)RtlAllocateHeap (RtlGetProcessHeap (),
891 (Length
+ 1) * sizeof(WCHAR
));
893 if (full_name
!= NULL
)
899 while (*path
&& *path
!= L
';')
903 if (wcs
!= full_name
&& *(wcs
- 1) != L
'\\')
908 if (RtlDoesFileExists_U (full_name
))
910 Length
= RtlGetFullPathName_U (full_name
,
918 RtlFreeHeap (RtlGetProcessHeap (),
923 else if (RtlDoesFileExists_U (name
))
925 Length
= RtlGetFullPathName_U (name
,
939 RtlDoesFileExists_U(IN PWSTR FileName
)
941 UNICODE_STRING NtFileName
;
942 OBJECT_ATTRIBUTES Attr
;
943 FILE_BASIC_INFORMATION Info
;
948 if (!RtlDosPathNameToNtPathName_U (FileName
,
954 if (CurDir
.DosPath
.Length
)
955 NtFileName
= CurDir
.DosPath
;
959 InitializeObjectAttributes (&Attr
,
961 OBJ_CASE_INSENSITIVE
,
965 Status
= NtQueryAttributesFile (&Attr
, &Info
);
967 RtlFreeUnicodeString(&NtFileName
);
970 if (NT_SUCCESS(Status
) ||
971 Status
== STATUS_SHARING_VIOLATION
||
972 Status
== STATUS_ACCESS_DENIED
)
979 RtlpEnsureBufferSize(ULONG Unknown1
, ULONG Unknown2
, ULONG Unknown3
)
981 DPRINT1("RtlpEnsureBufferSize: stub\n");
982 return STATUS_NOT_IMPLEMENTED
;
986 RtlNtPathNameToDosPathName(ULONG Unknown1
, ULONG Unknown2
, ULONG Unknown3
, ULONG Unknown4
)
988 DPRINT1("RtlNtPathNameToDosPathName: stub\n");
989 return STATUS_NOT_IMPLEMENTED
;