WindowsXP-SP1/base/ntdll/curdir.c

5318 lines
174 KiB
C
Raw Permalink Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
curdir.c
Abstract:
Current directory support
Author:
Mark Lucovsky (markl) 10-Oct-1990
Revision History:
--*/
#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "string.h"
#include "ctype.h"
#include "sxstypes.h"
#include "ntdllp.h"
#define IS_PATH_SEPARATOR_U(ch) (((ch) == L'\\') || ((ch) == L'/'))
#define IS_END_OF_COMPONENT_U(ch) (IS_PATH_SEPARATOR_U(ch) || (ch) == UNICODE_NULL)
#define IS_DOT_U(s) ( (s)[0] == L'.' && IS_END_OF_COMPONENT_U( (s)[1] ))
#define IS_DOT_DOT_U(s) ( (s)[0] == L'.' && IS_DOT_U( (s) + 1))
#define IS_DRIVE_LETTER(ch) (((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z'))
extern const UNICODE_STRING RtlpDosLPTDevice = RTL_CONSTANT_STRING( L"LPT" );
extern const UNICODE_STRING RtlpDosCOMDevice = RTL_CONSTANT_STRING( L"COM" );
extern const UNICODE_STRING RtlpDosPRNDevice = RTL_CONSTANT_STRING( L"PRN" );
extern const UNICODE_STRING RtlpDosAUXDevice = RTL_CONSTANT_STRING( L"AUX" );
extern const UNICODE_STRING RtlpDosNULDevice = RTL_CONSTANT_STRING( L"NUL" );
extern const UNICODE_STRING RtlpDosCONDevice = RTL_CONSTANT_STRING( L"CON" );
extern const UNICODE_STRING RtlpDosSlashCONDevice = RTL_CONSTANT_STRING( L"\\\\.\\CON" );
extern const UNICODE_STRING RtlpSlashSlashDot = RTL_CONSTANT_STRING( L"\\\\.\\" );
extern const UNICODE_STRING RtlpDosDevicesPrefix = RTL_CONSTANT_STRING( L"\\??\\" );
extern const UNICODE_STRING RtlpDosDevicesUncPrefix = RTL_CONSTANT_STRING( L"\\??\\UNC\\" );
#define RtlpLongestPrefix RtlpDosDevicesUncPrefix.Length
const UNICODE_STRING RtlpEmptyString = RTL_CONSTANT_STRING(L"");
//
// \\? is referred to as the "Win32Nt" prefix or root.
// Paths that start with \\? are referred to as "Win32Nt" paths.
// Fudging the \\? to \?? converts the path to an Nt path.
//
extern const UNICODE_STRING RtlpWin32NtRoot = RTL_CONSTANT_STRING( L"\\\\?" );
extern const UNICODE_STRING RtlpWin32NtRootSlash = RTL_CONSTANT_STRING( L"\\\\?\\" );
extern const UNICODE_STRING RtlpWin32NtUncRoot = RTL_CONSTANT_STRING( L"\\\\?\\UNC" );
extern const UNICODE_STRING RtlpWin32NtUncRootSlash = RTL_CONSTANT_STRING( L"\\\\?\\UNC\\" );
#define DPFLTR_LEVEL_STATUS(x) ((NT_SUCCESS(x) \
|| (x) == STATUS_OBJECT_NAME_NOT_FOUND \
) \
? DPFLTR_TRACE_LEVEL : DPFLTR_ERROR_LEVEL)
ULONG
RtlpComputeBackupIndex(
IN PCURDIR CurDir
)
{
ULONG BackupIndex;
PWSTR UncPathPointer;
ULONG NumberOfPathSeparators;
RTL_PATH_TYPE CurDirPathType;
//
// Get pathType of curdir
//
CurDirPathType = RtlDetermineDosPathNameType_U(CurDir->DosPath.Buffer);
BackupIndex = 3;
if ( CurDirPathType == RtlPathTypeUncAbsolute ) {
//
// We want to scan the supplied path to determine where
// the "share" ends, and set BackupIndex to that point.
//
UncPathPointer = CurDir->DosPath.Buffer + 2;
NumberOfPathSeparators = 0;
while (*UncPathPointer) {
if (IS_PATH_SEPARATOR_U(*UncPathPointer)) {
NumberOfPathSeparators++;
if (NumberOfPathSeparators == 2) {
break;
}
}
UncPathPointer++;
}
BackupIndex = (ULONG)(UncPathPointer - CurDir->DosPath.Buffer);
}
return BackupIndex;
}
ULONG
RtlGetLongestNtPathLength( VOID )
{
return RtlpLongestPrefix + DOS_MAX_PATH_LENGTH + 1;
}
VOID
RtlpResetDriveEnvironment(
IN WCHAR DriveLetter
)
{
WCHAR EnvVarNameBuffer[4];
WCHAR EnvVarNameValue[4];
UNICODE_STRING s1,s2;
EnvVarNameBuffer[0] = L'=';
EnvVarNameBuffer[1] = DriveLetter;
EnvVarNameBuffer[2] = L':';
EnvVarNameBuffer[3] = L'\0';
RtlInitUnicodeString(&s1,EnvVarNameBuffer);
EnvVarNameValue[0] = DriveLetter;
EnvVarNameValue[1] = L':';
EnvVarNameValue[2] = L'\\';
EnvVarNameValue[3] = L'\0';
RtlInitUnicodeString(&s2,EnvVarNameValue);
RtlSetEnvironmentVariable(NULL,&s1,&s2);
}
ULONG
RtlGetCurrentDirectory_U(
ULONG nBufferLength,
PWSTR lpBuffer
)
/*++
Routine Description:
The current directory for a process can be retreived using
GetCurrentDirectory.
Arguments:
nBufferLength - Supplies the length in bytes of the buffer that is to
receive the current directory string.
lpBuffer - Returns the current directory string for the current
process. The string is a null terminated string and specifies
the absolute path to the current directory.
Return Value:
The return value is the length of the string copied to lpBuffer, not
including the terminating null character. If the return value is
greater than nBufferLength, the return value is the size of the buffer
required to hold the pathname. The return value is zero if the
function failed.
--*/
{
PCURDIR CurDir;
ULONG Length;
PWSTR CurDirName;
CurDir = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
RtlAcquirePebLock();
CurDirName = CurDir->DosPath.Buffer;
//
// Make sure user's buffer is big enough to hold the null
// terminated current directory
//
Length = CurDir->DosPath.Length>>1;
if (CurDirName[Length-2] != L':') {
if ( nBufferLength < (Length)<<1 ) {
RtlReleasePebLock();
return (Length)<<1;
}
}
else {
if ( nBufferLength <= (Length<<1) ) {
RtlReleasePebLock();
return ((Length+1)<<1);
}
}
try {
RtlMoveMemory(lpBuffer,CurDirName,Length<<1);
ASSERT(lpBuffer[Length-1] == L'\\');
if (lpBuffer[Length-2] == L':') {
lpBuffer[Length] = UNICODE_NULL;
}
else {
lpBuffer[Length-1] = UNICODE_NULL;
Length--;
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
RtlReleasePebLock();
return 0L;
}
RtlReleasePebLock();
return Length<<1;
}
NTSTATUS
RtlSetCurrentDirectory_U(
PUNICODE_STRING PathName
)
/*++
Routine Description:
The current directory for a process is changed using
SetCurrentDirectory.
Each process has a single current directory. A current directory is
made up of type parts.
- A disk designator either which is either a drive letter followed
by a colon, or a UNC servername/sharename "\\servername\sharename".
- A directory on the disk designator.
For APIs that manipulate files, the file names may be relative to
the current directory. A filename is relative to the entire current
directory if it does not begin with a disk designator or a path name
SEPARATOR. If the file name begins with a path name SEPARATOR, then
it is relative to the disk designator of the current directory. If
a file name begins with a disk designator, than it is a fully
qualified absolute path name.
The value of lpPathName supplies the current directory. The value
of lpPathName, may be a relative path name as described above, or a
fully qualified absolute path name. In either case, the fully
qualified absolute path name of the specified directory is
calculated and is stored as the current directory.
Arguments:
lpPathName - Supplies the pathname of the directory that is to be
made the current directory.
Return Value:
NT_SUCCESS - The operation was successful
!NT_SUCCESS - The operation failed
--*/
{
PCURDIR CurDir;
NTSTATUS Status;
BOOLEAN TranslationStatus;
PVOID FreeBuffer;
ULONG DosDirLength;
ULONG IsDevice;
ULONG DosDirCharCount;
UNICODE_STRING DosDir;
UNICODE_STRING NtFileName;
HANDLE NewDirectoryHandle;
OBJECT_ATTRIBUTES Obja;
IO_STATUS_BLOCK IoStatusBlock;
FILE_FS_DEVICE_INFORMATION DeviceInfo;
RTL_PATH_TYPE InputPathType;
CurDir = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
DosDir.Buffer = NULL;
FreeBuffer = NULL;
NewDirectoryHandle = NULL;
RtlAcquirePebLock();
NtCurrentPeb()->EnvironmentUpdateCount += 1;
//
// Set current directory is called first by the loader.
// If current directory is not being inherited, then close
// it !
//
if ( ((ULONG_PTR)CurDir->Handle & OBJ_HANDLE_TAGBITS) == RTL_USER_PROC_CURDIR_CLOSE ) {
NtClose(CurDir->Handle);
CurDir->Handle = NULL;
}
try {
try {
//
// Compute the length of the Dos style fully qualified current
// directory
//
DosDirLength = CurDir->DosPath.MaximumLength;
DosDir.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0,DosDirLength);
if ( !DosDir.Buffer ) {
return STATUS_NO_MEMORY;
}
DosDir.Length = 0;
DosDir.MaximumLength = (USHORT)DosDirLength;
//
// Now get the full pathname for the Dos style current
// directory
//
IsDevice = RtlIsDosDeviceName_Ustr(PathName);
if ( IsDevice ) {
return STATUS_NOT_A_DIRECTORY;
}
DosDirLength = RtlGetFullPathName_Ustr(
PathName,
DosDirLength,
DosDir.Buffer,
NULL,
NULL,
&InputPathType
);
if ( !DosDirLength ) {
return STATUS_OBJECT_NAME_INVALID;
}
DosDirCharCount = DosDirLength >> 1;
//
// Get the Nt filename of the new current directory
//
TranslationStatus = RtlDosPathNameToNtPathName_U(
DosDir.Buffer,
&NtFileName,
NULL,
NULL
);
if ( !TranslationStatus ) {
return STATUS_OBJECT_NAME_INVALID;
}
FreeBuffer = NtFileName.Buffer;
InitializeObjectAttributes(
&Obja,
&NtFileName,
OBJ_CASE_INSENSITIVE | OBJ_INHERIT,
NULL,
NULL
);
//
// If we are inheriting current directory, then
// avoid the open
//
if ( ((ULONG_PTR)CurDir->Handle & OBJ_HANDLE_TAGBITS) == RTL_USER_PROC_CURDIR_INHERIT ) {
NewDirectoryHandle = (HANDLE)((ULONG_PTR)CurDir->Handle & ~OBJ_HANDLE_TAGBITS);
CurDir->Handle = NULL;
//
// Test to see if this is removable media. If so
// tag the handle this may fail if the process was
// created with inherit handles set to false
//
Status = NtQueryVolumeInformationFile(
NewDirectoryHandle,
&IoStatusBlock,
&DeviceInfo,
sizeof(DeviceInfo),
FileFsDeviceInformation
);
if ( !NT_SUCCESS(Status) ) {
return RtlSetCurrentDirectory_U(PathName);
}
else {
if ( DeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA ) {
NewDirectoryHandle =(HANDLE)( (ULONG_PTR)NewDirectoryHandle | 1);
}
}
}
else {
//
// Open a handle to the current directory. Don't allow
// deletes of the directory.
//
Status = NtOpenFile(
&NewDirectoryHandle,
FILE_TRAVERSE | SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
);
if ( !NT_SUCCESS(Status) ) {
return Status;
}
//
// Test to see if this is removable media. If so
// tag the handle
//
Status = NtQueryVolumeInformationFile(
NewDirectoryHandle,
&IoStatusBlock,
&DeviceInfo,
sizeof(DeviceInfo),
FileFsDeviceInformation
);
if ( !NT_SUCCESS(Status) ) {
return Status;
}
else {
if ( DeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA ) {
NewDirectoryHandle =(HANDLE)( (ULONG_PTR)NewDirectoryHandle | 1);
}
}
}
//
// If there is no trailing '\', than place one
//
DosDir.Length = (USHORT)DosDirLength;
if ( DosDir.Buffer[DosDirCharCount-1] != L'\\') {
DosDir.Buffer[DosDirCharCount] = L'\\';
DosDir.Buffer[DosDirCharCount+1] = UNICODE_NULL;
DosDir.Length += sizeof( UNICODE_NULL );
}
//
// Now we are set to change to the new directory.
//
RtlMoveMemory( CurDir->DosPath.Buffer, DosDir.Buffer, DosDir.Length+sizeof(UNICODE_NULL) );
CurDir->DosPath.Length = DosDir.Length;
if ( CurDir->Handle ) {
NtClose(CurDir->Handle);
}
CurDir->Handle = NewDirectoryHandle;
NewDirectoryHandle = NULL;
}
finally {
if ( DosDir.Buffer ) {
RtlFreeHeap(RtlProcessHeap(), 0, DosDir.Buffer);
}
if ( FreeBuffer ) {
RtlFreeHeap(RtlProcessHeap(), 0, FreeBuffer);
}
if ( NewDirectoryHandle ) {
NtClose(NewDirectoryHandle);
}
RtlReleasePebLock();
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
return STATUS_ACCESS_VIOLATION;
}
return STATUS_SUCCESS;
}
RTL_PATH_TYPE
RtlDetermineDosPathNameType_U(
IN PCWSTR DosFileName
)
/*++
Routine Description:
This function examines the Dos format file name and determines the
type of file name (i.e. UNC, DriveAbsolute, Current Directory
rooted, or Relative.)
Arguments:
DosFileName - Supplies the Dos format file name whose type is to be
determined.
Return Value:
RtlPathTypeUnknown - The path type can not be determined
RtlPathTypeUncAbsolute - The path specifies a Unc absolute path
in the format \\server-name\sharename\rest-of-path
RtlPathTypeLocalDevice - The path specifies a local device in the format
\\.\rest-of-path or \\?\rest-of-path. This can be used for any device
where the nt and Win32 names are the same. For example mailslots.
RtlPathTypeRootLocalDevice - The path specifies the root of the local
devices in the format \\. or \\?
RtlPathTypeDriveAbsolute - The path specifies a drive letter absolute
path in the form drive:\rest-of-path
RtlPathTypeDriveRelative - The path specifies a drive letter relative
path in the form drive:rest-of-path
RtlPathTypeRooted - The path is rooted relative to the current disk
designator (either Unc disk, or drive). The form is \rest-of-path.
RtlPathTypeRelative - The path is relative (i.e. not absolute or rooted).
--*/
{
RTL_PATH_TYPE ReturnValue;
ASSERT(DosFileName != NULL);
if (IS_PATH_SEPARATOR_U(*DosFileName)) {
if ( IS_PATH_SEPARATOR_U(*(DosFileName+1)) ) {
if ( DosFileName[2] == '.' || DosFileName[2] == '?') {
if ( IS_PATH_SEPARATOR_U(*(DosFileName+3)) ){
ReturnValue = RtlPathTypeLocalDevice;
}
else if ( (*(DosFileName+3)) == UNICODE_NULL ){
ReturnValue = RtlPathTypeRootLocalDevice;
}
else {
ReturnValue = RtlPathTypeUncAbsolute;
}
}
else {
ReturnValue = RtlPathTypeUncAbsolute;
}
}
else {
ReturnValue = RtlPathTypeRooted;
}
}
else if ((*DosFileName) && (*(DosFileName+1)==L':')) {
if (IS_PATH_SEPARATOR_U(*(DosFileName+2))) {
ReturnValue = RtlPathTypeDriveAbsolute;
}
else {
ReturnValue = RtlPathTypeDriveRelative;
}
}
else {
ReturnValue = RtlPathTypeRelative;
}
return ReturnValue;
}
RTL_PATH_TYPE
NTAPI
RtlDetermineDosPathNameType_Ustr(
IN PCUNICODE_STRING String
)
/*++
Routine Description:
This function examines the Dos format file name and determines the
type of file name (i.e. UNC, DriveAbsolute, Current Directory
rooted, or Relative.)
Arguments:
DosFileName - Supplies the Dos format file name whose type is to be
determined.
Return Value:
RtlPathTypeUnknown - The path type can not be determined
RtlPathTypeUncAbsolute - The path specifies a Unc absolute path
in the format \\server-name\sharename\rest-of-path
RtlPathTypeLocalDevice - The path specifies a local device in the format
\\.\rest-of-path or \\?\rest-of-path. This can be used for any device
where the nt and Win32 names are the same. For example mailslots.
RtlPathTypeRootLocalDevice - The path specifies the root of the local
devices in the format \\. or \\?
RtlPathTypeDriveAbsolute - The path specifies a drive letter absolute
path in the form drive:\rest-of-path
RtlPathTypeDriveRelative - The path specifies a drive letter relative
path in the form drive:rest-of-path
RtlPathTypeRooted - The path is rooted relative to the current disk
designator (either Unc disk, or drive). The form is \rest-of-path.
RtlPathTypeRelative - The path is relative (i.e. not absolute or rooted).
--*/
{
RTL_PATH_TYPE ReturnValue;
const PCWSTR DosFileName = String->Buffer;
#define ENOUGH_CHARS(_cch) (String->Length >= ((_cch) * sizeof(WCHAR)))
if ( ENOUGH_CHARS(1) && IS_PATH_SEPARATOR_U(*DosFileName) ) {
if ( ENOUGH_CHARS(2) && IS_PATH_SEPARATOR_U(*(DosFileName+1)) ) {
if ( ENOUGH_CHARS(3) && (DosFileName[2] == '.' ||
DosFileName[2] == '?') ) {
if ( ENOUGH_CHARS(4) && IS_PATH_SEPARATOR_U(*(DosFileName+3)) ){
// "\\.\" or "\\?\"
ReturnValue = RtlPathTypeLocalDevice;
}
//
// Bogosity ahead, the code is confusing length and nuls,
// because it was copy/pasted from the PCWSTR version.
//
else if ( ENOUGH_CHARS(4) && (*(DosFileName+3)) == UNICODE_NULL ){
// "\\.\0" or \\?\0"
ReturnValue = RtlPathTypeRootLocalDevice;
}
else {
// "\\.x" or "\\." or "\\?x" or "\\?"
ReturnValue = RtlPathTypeUncAbsolute;
}
}
else {
// "\\x"
ReturnValue = RtlPathTypeUncAbsolute;
}
}
else {
// "\x"
ReturnValue = RtlPathTypeRooted;
}
}
//
// the "*DosFileName" is left over from the PCWSTR version
// Win32 and DOS don't allow embedded nuls and much code limits
// drive letters to strictly 7bit a-zA-Z so it's ok.
//
else if (ENOUGH_CHARS(2) && *DosFileName && *(DosFileName+1)==L':') {
if (ENOUGH_CHARS(3) && IS_PATH_SEPARATOR_U(*(DosFileName+2))) {
// "x:\"
ReturnValue = RtlPathTypeDriveAbsolute;
}
else {
// "c:x"
ReturnValue = RtlPathTypeDriveRelative;
}
}
else {
// "x", first char is not a slash / second char is not colon
ReturnValue = RtlPathTypeRelative;
}
return ReturnValue;
#undef ENOUGH_CHARS
}
NTSTATUS
NTAPI
RtlpDetermineDosPathNameType4(
IN ULONG InFlags,
IN PCUNICODE_STRING DosPath,
OUT RTL_PATH_TYPE* OutType,
OUT ULONG* OutFlags
)
{
NTSTATUS Status = STATUS_SUCCESS;
RTL_PATH_TYPE PathType = 0;
BOOLEAN Win32Nt = FALSE;
BOOLEAN Win32NtUncAbsolute = FALSE;
BOOLEAN Win32NtDriveAbsolute = FALSE;
BOOLEAN IncompleteRoot = FALSE;
RTL_PATH_TYPE PathTypeAfterWin32Nt = 0;
if (OutType != NULL
) {
*OutType = RtlPathTypeUnknown;
}
if (OutFlags != NULL
) {
*OutFlags = 0;
}
if (
!RTL_SOFT_VERIFY(DosPath != NULL)
|| !RTL_SOFT_VERIFY(OutType != NULL)
|| !RTL_SOFT_VERIFY(OutFlags != NULL)
|| !RTL_SOFT_VERIFY(
(InFlags & ~(RTL_DETERMINE_DOS_PATH_NAME_TYPE_IN_FLAG_OLD | RTL_DETERMINE_DOS_PATH_NAME_TYPE_IN_FLAG_STRICT_WIN32NT))
== 0)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
PathType = RtlDetermineDosPathNameType_Ustr(DosPath);
*OutType = PathType;
if (InFlags & RTL_DETERMINE_DOS_PATH_NAME_TYPE_IN_FLAG_OLD)
goto Exit;
if (DosPath->Length == sizeof(L"\\\\") - sizeof(DosPath->Buffer[0])
) {
IncompleteRoot = TRUE;
}
else if (RtlEqualUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtRoot), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
IncompleteRoot = TRUE;
Win32Nt = TRUE;
}
else if (RtlEqualUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtRootSlash), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
IncompleteRoot = TRUE;
Win32Nt = TRUE;
}
else if (RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtRootSlash), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
Win32Nt = TRUE;
}
if (Win32Nt) {
if (RtlEqualUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtUncRoot), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
IncompleteRoot = TRUE;
Win32NtUncAbsolute = TRUE;
}
else if (RtlEqualUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtUncRootSlash), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
IncompleteRoot = TRUE;
Win32NtUncAbsolute = TRUE;
}
else if (RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtUncRootSlash), RTL_CONST_CAST(PUNICODE_STRING)(DosPath), TRUE)
) {
Win32NtUncAbsolute = TRUE;
}
if (Win32NtUncAbsolute
) {
Win32NtDriveAbsolute = FALSE;
} else if (!IncompleteRoot) {
const RTL_STRING_LENGTH_TYPE i = RtlpWin32NtRootSlash.Length;
UNICODE_STRING PathAfterWin32Nt = *DosPath;
PathAfterWin32Nt.Buffer += i / sizeof(PathAfterWin32Nt.Buffer[0]);
PathAfterWin32Nt.Length -= i;
PathAfterWin32Nt.MaximumLength -= i;
PathTypeAfterWin32Nt = RtlDetermineDosPathNameType_Ustr(&PathAfterWin32Nt);
Win32NtDriveAbsolute = (PathTypeAfterWin32Nt == RtlPathTypeDriveAbsolute);
if (InFlags & RTL_DETERMINE_DOS_PATH_NAME_TYPE_IN_FLAG_STRICT_WIN32NT
) {
if (!RTL_SOFT_VERIFY(Win32NtDriveAbsolute
)) {
*OutFlags |= RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_INVALID;
// we still succeed the function call
}
}
}
}
ASSERT(RTLP_IMPLIES(Win32NtDriveAbsolute, Win32Nt));
ASSERT(RTLP_IMPLIES(Win32NtUncAbsolute, Win32Nt));
ASSERT(!(Win32NtUncAbsolute && Win32NtDriveAbsolute));
if (IncompleteRoot)
*OutFlags |= RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_INCOMPLETE_ROOT;
if (Win32Nt)
*OutFlags |= RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_WIN32NT;
if (Win32NtUncAbsolute)
*OutFlags |= RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_WIN32NT_UNC_ABSOLUTE;
if (Win32NtDriveAbsolute)
*OutFlags |= RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_WIN32NT_DRIVE_ABSOLUTE;
Status = STATUS_SUCCESS;
Exit:
return Status;
}
ULONG
RtlIsDosDeviceName_Ustr(
IN PUNICODE_STRING DosFileName
)
/*++
Routine Description:
This function examines the Dos format file name and determines if it
is a Dos device name (e.g. LPT1, etc.). Valid Dos device names are:
LPTn
COMn
PRN
AUX
NUL
CON
when n is a digit. Trailing colon is ignored if present.
Arguments:
DosFileName - Supplies the Dos format file name that is to be examined.
Return Value:
0 - Specified Dos file name is not the name of a Dos device.
> 0 - Specified Dos file name is the name of a Dos device and the
return value is a ULONG where the high order 16 bits is the
offset in the input buffer where the dos device name beings
and the low order 16 bits is the length of the device name
the length of the name (excluding any optional
trailing colon).
--*/
{
UNICODE_STRING UnicodeString;
USHORT NumberOfCharacters;
ULONG ReturnLength;
ULONG ReturnOffset;
LPWSTR p;
USHORT ColonBias;
RTL_PATH_TYPE PathType;
WCHAR wch;
ColonBias = 0;
PathType = RtlDetermineDosPathNameType_U(DosFileName->Buffer);
switch ( PathType ) {
case RtlPathTypeLocalDevice:
//
// For Unc Absolute, Check for \\.\CON
// since this really is not a device
//
if ( RtlEqualUnicodeString(DosFileName,&RtlpDosSlashCONDevice,TRUE) ) {
return 0x00080006;
}
//
// FALLTHRU
//
case RtlPathTypeUncAbsolute:
case RtlPathTypeUnknown:
return 0;
}
UnicodeString = *DosFileName;
NumberOfCharacters = DosFileName->Length >> 1;
if (NumberOfCharacters && DosFileName->Buffer[NumberOfCharacters-1] == L':') {
UnicodeString.Length -= sizeof(WCHAR);
NumberOfCharacters--;
ColonBias = 1;
}
//
// The above strip the trailing colon logic could have left us with 0
// for NumberOfCharacters, so that needs to be tested
//
if ( NumberOfCharacters == 0 ) {
return 0;
}
wch = UnicodeString.Buffer[NumberOfCharacters-1];
while ( NumberOfCharacters && (wch == L'.' || wch == L' ') ) {
UnicodeString.Length -= sizeof(WCHAR);
NumberOfCharacters--;
ColonBias++;
wch = UnicodeString.Buffer[NumberOfCharacters-1];
}
ReturnLength = NumberOfCharacters << 1;
//
// Walk backwards through the string finding the
// first slash or the beginning of the string. We also stop
// at the drive: if it is present.
//
ReturnOffset = 0;
if ( NumberOfCharacters ) {
p = UnicodeString.Buffer + NumberOfCharacters-1;
while ( p >= UnicodeString.Buffer ) {
if ( *p == L'\\' || *p == L'/'
|| (*p == L':' && p == UnicodeString.Buffer + 1)) {
p++;
//
// Get the first char of the file name and convert it to
// lower case. This will be safe since we will be comparing
// it to only lower-case ASCII.
//
wch = (*p) | 0x20;
//
// check to see if we possibly have a hit on
// lpt, prn, con, com, aux, or nul
//
if ( !(wch == L'l' || wch == L'c' || wch == L'p' || wch == L'a'
|| wch == L'n')
) {
return 0;
}
ReturnOffset = (ULONG)((PSZ)p - (PSZ)UnicodeString.Buffer);
RtlInitUnicodeString(&UnicodeString,p);
NumberOfCharacters = UnicodeString.Length >> 1;
NumberOfCharacters -= ColonBias;
ReturnLength = NumberOfCharacters << 1;
UnicodeString.Length -= ColonBias*sizeof(WCHAR);
break;
}
p--;
}
wch = UnicodeString.Buffer[0] | 0x20;
//
// check to see if we possibly have a hit on
// lpt, prn, con, com, aux, or nul
//
if ( !( wch == L'l' || wch == L'c' || wch == L'p' || wch == L'a'
|| wch == L'n' ) ) {
return 0;
}
}
//
// Now we need to see if we are dealing with a device name that has
// an extension or a stream name. If so, we need to limit the search to the
// file portion only
//
p = UnicodeString.Buffer;
while (p < UnicodeString.Buffer + NumberOfCharacters && *p != L'.' && *p != L':') {
p++;
}
//
// p either points past end of string or to a dot or :. We back up over
// trailing spaces
//
while (p > UnicodeString.Buffer && p[-1] == L' ') {
p--;
}
//
// p either points to the beginning of the string or p[-1] is
// the first non-space char found above.
//
NumberOfCharacters = (USHORT)(p - UnicodeString.Buffer);
UnicodeString.Length = NumberOfCharacters * sizeof( WCHAR );
if ( NumberOfCharacters == 4 && iswdigit(UnicodeString.Buffer[3] ) ) {
if ( (WCHAR)UnicodeString.Buffer[3] == L'0') {
return 0;
} else {
UnicodeString.Length -= sizeof(WCHAR);
if ( RtlEqualUnicodeString(&UnicodeString,&RtlpDosLPTDevice,TRUE) ||
RtlEqualUnicodeString(&UnicodeString,&RtlpDosCOMDevice,TRUE) ) {
ReturnLength = NumberOfCharacters << 1;
} else {
return 0;
}
}
} else if ( NumberOfCharacters != 3 ) {
return 0;
} else if ( RtlEqualUnicodeString(&UnicodeString,&RtlpDosPRNDevice,TRUE) ) {
ReturnLength = NumberOfCharacters << 1;
} else if ( RtlEqualUnicodeString(&UnicodeString,&RtlpDosAUXDevice,TRUE) ) {
ReturnLength = NumberOfCharacters << 1;
} else if ( RtlEqualUnicodeString(&UnicodeString,&RtlpDosNULDevice,TRUE) ) {
ReturnLength = NumberOfCharacters << 1;
} else if ( RtlEqualUnicodeString(&UnicodeString,&RtlpDosCONDevice,TRUE) ) {
ReturnLength = NumberOfCharacters << 1;
} else {
return 0;
}
return ReturnLength | (ReturnOffset << 16);
}
ULONG
RtlIsDosDeviceName_U(
IN PWSTR DosFileName
)
{
UNICODE_STRING UnicodeString;
RtlInitUnicodeString(&UnicodeString,DosFileName);
return RtlIsDosDeviceName_Ustr(&UnicodeString);
}
NTSTATUS
RtlpCheckDeviceName(
PUNICODE_STRING DevName,
ULONG DeviceNameOffset,
BOOLEAN* NameInvalid
)
{
PWSTR DevPath;
NTSTATUS Status = STATUS_SUCCESS;
DevPath = RtlAllocateHeap(RtlProcessHeap(), 0,DevName->Length);
if (!DevPath) {
*NameInvalid = FALSE;
Status = STATUS_NO_MEMORY;
goto Exit;
}
*NameInvalid = TRUE;
try {
RtlCopyMemory(DevPath,DevName->Buffer,DevName->Length);
DevPath[DeviceNameOffset>>1]=L'.';
DevPath[(DeviceNameOffset>>1)+1]=UNICODE_NULL;
if (RtlDoesFileExists_U(DevPath) ) {
*NameInvalid = FALSE;
}
else {
*NameInvalid = TRUE;
}
}
finally {
RtlFreeHeap(RtlProcessHeap(), 0, DevPath);
}
Status = STATUS_SUCCESS;
Exit:
return Status;
}
//
// We keep an open handle to the current directory on the current drive in order
// to make relative opens faster.
//
// However, this current directory can become invalid under two circumstances:
//
// 1. The current drive is removable media. The user may arbitrarily switch
// media without our knowledge. At this point, whatever information the
// filesystem has about the media is now out of date.
// 2. The volume is dismounted by explicit system/user action.
//
// We can ping (via FSCTL_IS_VOLUME_MOUNTED) the volume each time we want to
// test to see if the current directory is still valid. While a "cheap" call
// we will end up doing it a lot. We can reduce this frequency by only checking
// when the media is known to be removable or when a dismount is known to have
// occurred. The Io system exports in USER_SHARED_DATA a count of dismounts
// since boot. We capture this and use it to decide if a dismount was performed.
//
ULONG RtlpSavedDismountCount = -1;
VOID
RtlpValidateCurrentDirectory(
PCURDIR CurDir
)
/*++
Routine Description:
This function is used to validate the current directory for the process.
The current directory can change in several ways, first, by replacing
the media with one that has a different directory structure. Second
by performing a force-dismount.
Arguments:
CurDir - Current directory structure for process
Return Value:
None.
--*/
{
NTSTATUS FsCtlStatus;
IO_STATUS_BLOCK IoStatusBlock;
WCHAR TrimmedPath[4];
UNICODE_STRING str;
if (((ULONG_PTR)CurDir->Handle & 1) == 0
&& USER_SHARED_DATA->DismountCount == RtlpSavedDismountCount) {
return;
}
//
// Never been set yet.
//
if (CurDir->Handle == NULL) {
return;
}
//
// Call Nt to see if the volume that
// contains the directory is still mounted.
// If it is, then continue. Otherwise, trim
// current directory to the root.
//
//
// We're updated as far as possible with the current dismount count
//
RtlpSavedDismountCount = USER_SHARED_DATA->DismountCount;
FsCtlStatus = NtFsControlFile(
CurDir->Handle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_IS_VOLUME_MOUNTED,
NULL,
0,
NULL,
0
);
if ( FsCtlStatus == STATUS_WRONG_VOLUME || FsCtlStatus == STATUS_VOLUME_DISMOUNTED) {
//
// Try to get back to where we were, failing that reset current directory
// to the root of the current drive
//
NtClose( CurDir->Handle );
CurDir->Handle = NULL;
FsCtlStatus = RtlSetCurrentDirectory_U(&CurDir->DosPath);
if ( !NT_SUCCESS(FsCtlStatus) ) {
TrimmedPath[0] = CurDir->DosPath.Buffer[0];
TrimmedPath[1] = CurDir->DosPath.Buffer[1];
TrimmedPath[2] = CurDir->DosPath.Buffer[2];
TrimmedPath[3] = UNICODE_NULL;
RtlpResetDriveEnvironment( TrimmedPath[0] );
RtlInitUnicodeString( &str, TrimmedPath );
//
// This can still fail if the volume was hard dismounted. We tried.
// Ah well.
//
(VOID) RtlSetCurrentDirectory_U( &str );
}
}
}
ULONG
RtlGetFullPathName_Ustr(
PUNICODE_STRING FileName,
ULONG nBufferLength,
PWSTR lpBuffer,
PWSTR *lpFilePart OPTIONAL,
PBOOLEAN NameInvalid,
RTL_PATH_TYPE *InputPathType
)
/*++
Routine Description:
This function is used to return a fully qualified pathname
corresponding to the specified unicode filename. It does this by
merging the current drive and directory together with the specified
file name. In addition to this, it calculates the address of the
file name portion of the fully qualified pathname.
Arguments:
lpFileName - Supplies the unicode file name of the file whose fully
qualified pathname is to be returned.
nBufferLength - Supplies the length in bytes of the buffer that is
to receive the fully qualified path.
lpBuffer - Returns the fully qualified pathname corresponding to the
specified file.
lpFilePart - Optional parameter that if specified, returns the
address of the last component of the fully qualified pathname.
Return Value:
The return value is the length of the string copied to lpBuffer, not
including the terminating unicode null character. If the return
value is greater than nBufferLength, the return value is the size of
the buffer required to hold the pathname. The return value is zero
if the function failed.
--*/
{
ULONG DeviceNameLength;
ULONG DeviceNameOffset;
ULONG PrefixSourceLength;
LONG PathNameLength;
UCHAR CurDrive, NewDrive;
WCHAR EnvVarNameBuffer[4];
UNICODE_STRING EnvVarName;
PWSTR Source,Dest;
UNICODE_STRING Prefix;
PCURDIR CurDir;
ULONG MaximumLength;
UNICODE_STRING FullPath;
ULONG BackupIndex;
RTL_PATH_TYPE PathType;
NTSTATUS Status;
BOOLEAN StripTrailingSlash;
UNICODE_STRING UnicodeString;
ULONG NumberOfCharacters;
PWSTR lpFileName;
WCHAR wch;
PWCHAR pwch;
ULONG i,j;
if ( ARGUMENT_PRESENT(NameInvalid) ) {
*NameInvalid = FALSE;
}
if ( nBufferLength > MAXUSHORT ) {
nBufferLength = MAXUSHORT-2;
}
*InputPathType = RtlPathTypeUnknown;
UnicodeString = *FileName;
lpFileName = UnicodeString.Buffer;
NumberOfCharacters = UnicodeString.Length >> 1;
PathNameLength = UnicodeString.Length;
if ( PathNameLength == 0 || UnicodeString.Buffer[0] == UNICODE_NULL ) {
return 0;
}
else {
//
// trim trailing spaces to check for a null name
//
DeviceNameLength = PathNameLength;
wch = UnicodeString.Buffer[(DeviceNameLength>>1) - 1];
while ( DeviceNameLength && wch == L' ' ) {
DeviceNameLength -= sizeof(WCHAR);
if ( DeviceNameLength ) {
wch = UnicodeString.Buffer[(DeviceNameLength>>1) - 1];
}
}
if ( !DeviceNameLength ) {
return 0;
}
}
if ( lpFileName[NumberOfCharacters-1] == L'\\' || lpFileName[NumberOfCharacters-1] == L'/' ) {
StripTrailingSlash = FALSE;
}
else {
StripTrailingSlash = TRUE;
}
//
// If pass Dos file name is a Dos device name, then turn it into
// \\.\devicename and return its length.
//
DeviceNameLength = RtlIsDosDeviceName_Ustr(&UnicodeString);
if ( DeviceNameLength ) {
if ( ARGUMENT_PRESENT( lpFilePart ) ) {
*lpFilePart = NULL;
}
DeviceNameOffset = DeviceNameLength >> 16;
DeviceNameLength &= 0x0000ffff;
if ( ARGUMENT_PRESENT(NameInvalid) && DeviceNameOffset ) {
NTSTATUS Status;
Status = RtlpCheckDeviceName(&UnicodeString, DeviceNameOffset, NameInvalid);
if (*NameInvalid) {
return 0;
}
}
PathNameLength = DeviceNameLength + RtlpSlashSlashDot.Length;
if ( PathNameLength < (LONG)nBufferLength ) {
RtlMoveMemory(
lpBuffer,
RtlpSlashSlashDot.Buffer,
RtlpSlashSlashDot.Length
);
RtlMoveMemory(
(PVOID)((PUCHAR)lpBuffer+RtlpSlashSlashDot.Length),
(PSZ)lpFileName+DeviceNameOffset,
DeviceNameLength
);
RtlZeroMemory(
(PVOID)((PUCHAR)lpBuffer+RtlpSlashSlashDot.Length+DeviceNameLength),
sizeof(UNICODE_NULL)
);
return PathNameLength;
}
else {
return PathNameLength+sizeof(UNICODE_NULL);
}
}
//
// Setup output string that points to callers buffer.
//
FullPath.MaximumLength = (USHORT)nBufferLength;
FullPath.Length = 0;
FullPath.Buffer = lpBuffer;
RtlZeroMemory(lpBuffer,nBufferLength);
//
// Get a pointer to the current directory structure.
//
CurDir = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
//
// Determine the type of Dos Path Name specified.
//
*InputPathType = PathType = RtlDetermineDosPathNameType_U(lpFileName);
//
// Determine the prefix and backup index.
//
// Input Prefix Backup Index
//
// \\ -> \\, end of \\server\share
// \\.\ -> \\.\, 4
// \\. -> \\. 3 (\\.)
// \ -> Drive: from CurDir.DosPath 3 (Drive:\)
// d: -> Drive:\curdir from environment 3 (Drive:\)
// d:\ -> no prefix 3 (Drive:\)
// any -> CurDir.DosPath 3 (Drive:\)
//
RtlAcquirePebLock();
try {
//
// No prefixes yet.
//
Source = lpFileName;
PrefixSourceLength = 0;
Prefix.Length = 0;
Prefix.MaximumLength = 0;
Prefix.Buffer = NULL;
switch (PathType) {
case RtlPathTypeUncAbsolute :
{
PWSTR UncPathPointer;
ULONG NumberOfPathSeparators;
//
// We want to scan the supplied path to determine where
// the "share" ends, and set BackupIndex to that point.
//
UncPathPointer = lpFileName + 2;
NumberOfPathSeparators = 0;
while (*UncPathPointer) {
if (IS_PATH_SEPARATOR_U(*UncPathPointer)) {
NumberOfPathSeparators++;
if (NumberOfPathSeparators == 2) {
break;
}
}
UncPathPointer++;
}
BackupIndex = (ULONG)(UncPathPointer - lpFileName);
//
// Unc name. prefix = \\server\share
//
PrefixSourceLength = BackupIndex << 1;
Source += BackupIndex;
//
// There is no prefix to place into the buffer.
// The entire path is in Source
//
}
break;
case RtlPathTypeLocalDevice :
//
// Local device name. prefix = "\\.\"
//
PrefixSourceLength = RtlpSlashSlashDot.Length;
BackupIndex = 4;
Source += BackupIndex;
//
// There is no prefix to place into the buffer.
// The entire path is in Source
//
break;
case RtlPathTypeRootLocalDevice :
//
// Local Device root. prefix = "\\.\"
//
Prefix = RtlpSlashSlashDot;
Prefix.Length = (USHORT)(Prefix.Length - (USHORT)(2*sizeof(UNICODE_NULL)));
PrefixSourceLength = Prefix.Length + sizeof(UNICODE_NULL);
BackupIndex = 3;
Source += BackupIndex;
PathNameLength -= BackupIndex * sizeof( WCHAR );
break;
case RtlPathTypeDriveAbsolute :
CurDrive = (UCHAR)RtlUpcaseUnicodeChar( CurDir->DosPath.Buffer[0] );
NewDrive = (UCHAR)RtlUpcaseUnicodeChar( lpFileName[0] );
if ( CurDrive == NewDrive ) {
RtlpValidateCurrentDirectory( CurDir );
}
//
// Dos drive absolute name
//
BackupIndex = 3;
break;
case RtlPathTypeDriveRelative :
//
// Dos drive relative name
//
CurDrive = (UCHAR)RtlUpcaseUnicodeChar( CurDir->DosPath.Buffer[0] );
NewDrive = (UCHAR)RtlUpcaseUnicodeChar( lpFileName[0] );
if ( CurDrive == NewDrive ) {
RtlpValidateCurrentDirectory( CurDir );
Prefix = *(PUNICODE_STRING)&CurDir->DosPath;
}
else {
RtlpCheckRelativeDrive((WCHAR)NewDrive);
EnvVarNameBuffer[0] = L'=';
EnvVarNameBuffer[1] = (WCHAR)NewDrive;
EnvVarNameBuffer[2] = L':';
EnvVarNameBuffer[3] = UNICODE_NULL;
RtlInitUnicodeString(&EnvVarName,EnvVarNameBuffer);
Prefix = FullPath;
Status = RtlQueryEnvironmentVariable_U( NULL,
&EnvVarName,
&Prefix
);
if ( !NT_SUCCESS( Status ) ) {
if (Status == STATUS_BUFFER_TOO_SMALL) {
return (ULONG)(Prefix.Length) + PathNameLength + 2;
}
else {
//
// Otherwise default to root directory of drive
//
Status = STATUS_SUCCESS;
EnvVarNameBuffer[0] = (WCHAR)NewDrive;
EnvVarNameBuffer[1] = L':';
EnvVarNameBuffer[2] = L'\\';
EnvVarNameBuffer[3] = UNICODE_NULL;
RtlInitUnicodeString(&Prefix,EnvVarNameBuffer);
}
}
else {
{
ULONG LastChar;
//
// Determine
// if a backslash needs to be added
//
LastChar = Prefix.Length >> 1;
if (LastChar > 3) {
Prefix.Buffer[ LastChar ] = L'\\';
Prefix.Length += sizeof(UNICODE_NULL);
}
}
}
}
BackupIndex = 3;
Source += 2;
PathNameLength -= 2 * sizeof( WCHAR );
break;
case RtlPathTypeRooted :
BackupIndex = RtlpComputeBackupIndex(CurDir);
if ( BackupIndex != 3 ) {
Prefix = CurDir->DosPath;
Prefix.Length = (USHORT)(BackupIndex << 1);
}
else {
//
// Rooted name. Prefix is drive portion of current directory
//
Prefix = CurDir->DosPath;
Prefix.Length = 2*sizeof(UNICODE_NULL);
}
break;
case RtlPathTypeRelative :
RtlpValidateCurrentDirectory( CurDir );
//
// Current drive:directory relative name
//
Prefix = CurDir->DosPath;
BackupIndex = RtlpComputeBackupIndex(CurDir);
break;
default:
return 0;
}
//
// Maximum length required is the length of the prefix plus
// the length of the specified pathname. If the callers buffer
// is not at least this large, then return an error.
//
MaximumLength = PathNameLength + Prefix.Length;
if ( MaximumLength >= nBufferLength ) {
if ( (NumberOfCharacters > 1) ||
(*lpFileName != L'.') ) {
return MaximumLength+sizeof(UNICODE_NULL);
}
else {
//
// If we are expanding curdir, then remember the trailing '\'
//
if ( NumberOfCharacters == 1 && *lpFileName == L'.' ) {
//
// We are expanding .
//
if ( Prefix.Length == 6 ) {
if ( nBufferLength <= Prefix.Length ) {
return (ULONG)(Prefix.Length+(USHORT)sizeof(UNICODE_NULL));
}
}
else {
if ( nBufferLength < Prefix.Length ) {
return (ULONG)Prefix.Length;
}
else {
for(i=0,j=0;i<Prefix.Length;i+=sizeof(WCHAR),j++){
if ( Prefix.Buffer[j] == L'\\' ||
Prefix.Buffer[j] == L'/' ) {
FullPath.Buffer[j] = L'\\';
}
else {
FullPath.Buffer[j] = Prefix.Buffer[j];
}
}
FullPath.Length = Prefix.Length-(USHORT)sizeof(L'\\');
goto skipit;
}
}
}
else {
return MaximumLength;
}
}
}
if (PrefixSourceLength || Prefix.Buffer != FullPath.Buffer) {
//
// Copy the prefix from the source string.
//
//RtlMoveMemory(FullPath.Buffer,lpFileName,PrefixSourceLength);
for(i=0,j=0;i<PrefixSourceLength;i+=sizeof(WCHAR),j++){
if ( lpFileName[j] == L'\\' ||
lpFileName[j] == L'/' ) {
FullPath.Buffer[j] = L'\\';
}
else {
FullPath.Buffer[j] = lpFileName[j];
}
}
FullPath.Length = (USHORT)PrefixSourceLength;
//
// Append any additional prefix
//
for(i=0,j=0;i<Prefix.Length;i+=sizeof(WCHAR),j++){
if ( Prefix.Buffer[j] == L'\\' ||
Prefix.Buffer[j] == L'/' ) {
FullPath.Buffer[j+(FullPath.Length>>1)] = L'\\';
}
else {
FullPath.Buffer[j+(FullPath.Length>>1)] = Prefix.Buffer[j];
}
}
FullPath.Length += Prefix.Length;
}
else {
FullPath.Length = Prefix.Length;
}
skipit:
Dest = (PWSTR)((PUCHAR)FullPath.Buffer + FullPath.Length);
*Dest = UNICODE_NULL;
while ( *Source ) {
switch ( *Source ) {
case L'\\' :
case L'/' :
//
// collapse multiple "\" characters. If the previous character was
// a path character, skip it.
//
if ( *(Dest-1) != L'\\' ) {
*Dest++ = L'\\';
}
Source++;
break;
case '.' :
//
// Ignoring dot in a leading //./ has already been taken
// care of by advancing Source above.
//
// Eat single dots as in /./ by simply skipping them
//
// Double dots back up one level as in /../
//
// Any other . is just a filename character
//
if ( IS_DOT_U(Source) ) {
Source++;
if (IS_PATH_SEPARATOR_U(*Source)) {
Source++;
}
break;
}
else if ( IS_DOT_DOT_U(Source) ) {
//
// backup destination string looking for a '\'
//
while (*Dest != L'\\') {
*Dest = UNICODE_NULL;
Dest--;
}
//
// backup to previous component..
// \a\b\c\.. to \a\b
//
do {
//
// If we bump into root prefix, then
// stay at root
//
if ( Dest == FullPath.Buffer + (BackupIndex-1) ) {
break;
}
*Dest = UNICODE_NULL;
Dest--;
} while (*Dest != L'\\');
if ( Dest == FullPath.Buffer + (BackupIndex-1) ) {
Dest++;
}
//
// Advance source past ..
//
Source += 2;
break;
}
//
// Not a single dot or double-dot. The dot we found begins
// a file name so we treat this as a normal file name
//
// FALLTHRU
//
default:
//
// Copy the filename. Note that
// null and /,\ will stop the copy. If any
// charcter other than null or /,\ is encountered,
// then the pathname is invalid.
//
//
// Copy up until NULL or path separator
//
while ( !IS_END_OF_COMPONENT_U(*Source) ) {
*Dest++ = *Source++;
}
//
// Once copied, we should do some processing for compatibility with Win9x.
// Win9x strips all trailing spaces and dots from the name.
// Nt4/Win2K strips only the last dot if its followed by a path separator.
//
// Ideally, we'd do something reasonable like stripping all trailing spaces
// and dots (like Win9X). However, IIS has a security model that's based
// on NAMES and not on objects. This means that IIS needs to process names
// in the same way that we do. They should use GetFullPathName and be done
// with it. In any event, DON'T CHANGE THE CANONICALIZATION OF THE NAME
// HERE WITHOUTH FIRST GOING THROUGH IIS.
//
// We do EXACTLY what Nt4 did: if there's a trailing dot AND we're at a path
// seperator, remove the trailing dot.
//
if (IS_PATH_SEPARATOR_U( *Source ) && Dest[-1] == L'.') {
Dest--;
}
}
}
*Dest = UNICODE_NULL;
if ( StripTrailingSlash ) {
if ( Dest > (FullPath.Buffer + BackupIndex ) && *(Dest-1) == L'\\' ) {
Dest--;
*Dest = UNICODE_NULL;
}
}
FullPath.Length = (USHORT)(PtrToUlong(Dest) - PtrToUlong(FullPath.Buffer));
//
// Strip trailing spaces and dots.
//
while (Dest > FullPath.Buffer && (Dest[-1] == L' ' || Dest[-1] == L'.')) {
*--Dest = UNICODE_NULL;
FullPath.Length -= sizeof( WCHAR );
}
if ( ARGUMENT_PRESENT( lpFilePart ) ) {
//
// Locate the file part...
//
Source = Dest-1;
Dest = NULL;
while(Source > FullPath.Buffer ) {
if ( *Source == L'\\' ) {
Dest = Source + 1;
break;
}
Source--;
}
if ( Dest && *Dest ) {
//
// If this is a UNC name, make sure filepart is past the backup index
//
if ( PathType == RtlPathTypeUncAbsolute ) {
if ( Dest < (FullPath.Buffer + BackupIndex ) ) {
*lpFilePart = NULL;
leave;
}
}
*lpFilePart = Dest;
}
else {
*lpFilePart = NULL;
}
}
}
finally {
RtlReleasePebLock();
}
return (ULONG)FullPath.Length;
}
NTSTATUS
RtlGetFullPathName_UstrEx(
PUNICODE_STRING FileName,
PUNICODE_STRING StaticString,
PUNICODE_STRING DynamicString,
PUNICODE_STRING *StringUsed,
SIZE_T *FilePartPrefixCch OPTIONAL,
PBOOLEAN NameInvalid,
RTL_PATH_TYPE *InputPathType,
SIZE_T *BytesRequired OPTIONAL
)
/*++
Routine Description:
See a description of RtlGetFullPathName_Ustr() for a functional
description.
This function provides the same basic behavior as RtlGetFullPathName_Ustr(),
but with easier support for dynamically allocated arbitrary path name
buffers.
One would think that this is the core implementation and the non-Ex()
version would call the ...Ex() version, but that seems risky, and
would only really help the performance of the case where a dynamic allocation
is done.
--*/
{
NTSTATUS Status;
ULONG Length;
PWSTR Buffer;
ULONG BufferLength;
PWSTR FilePart = NULL;
UNICODE_STRING TempDynamicString;
USHORT StaticBufferSize;
if (StringUsed != NULL)
*StringUsed = NULL;
if (BytesRequired != NULL)
*BytesRequired = 0;
if (FilePartPrefixCch != NULL)
*FilePartPrefixCch = 0;
TempDynamicString.Buffer = NULL;
if ((StaticString != NULL) && (DynamicString != NULL) && (StringUsed == NULL)) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
StaticBufferSize = 0;
if (StaticString != NULL) {
StaticBufferSize = StaticString->MaximumLength;
}
// First try getting into the static string.
Length = RtlGetFullPathName_Ustr(
FileName,
StaticBufferSize,
StaticString->Buffer,
&FilePart,
NameInvalid,
InputPathType);
if (Length == 0) {
#if DBG
DbgPrint("%s(%d) - RtlGetFullPathName_Ustr() returned 0\n", __FUNCTION__, __LINE__);
#endif // DBG
Status = STATUS_OBJECT_NAME_INVALID;
goto Exit;
}
if ((StaticString != NULL) && (Length < StaticBufferSize)) {
// woohoo it worked.
StaticString->Length = (USHORT) Length;
if (FilePartPrefixCch != NULL) {
*FilePartPrefixCch = (FilePart != NULL) ? (FilePart - StaticString->Buffer) : 0;
}
if (StringUsed != NULL)
*StringUsed = StaticString;
Status = STATUS_SUCCESS;
} else if (DynamicString == NULL) {
// The static buffer wasn't big enough and the caller doesn't want us to do
// a dynamic allocation; the best we can hope for is to give them back a
// reasonable size.
if (BytesRequired != NULL) {
*BytesRequired = Length;
}
Status = STATUS_BUFFER_TOO_SMALL;
goto Exit;
} else {
// Not big enough... allocate some memory into the dynamic buffer.
// But wait; we need to lock the Peb lock so that someone doesn't
// change the process's current directory out from under us!
RtlAcquirePebLock();
__try {
// Do it again with the peb lock taken so that we can get an accurate stable
// length.
Length = RtlGetFullPathName_Ustr(
FileName,
StaticBufferSize,
StaticString->Buffer,
&FilePart,
NameInvalid,
InputPathType);
if (Length == 0) {
#if DBG
DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n", __FUNCTION__, __LINE__);
#endif // DBG
Status = STATUS_OBJECT_NAME_INVALID;
goto Exit;
}
if ((StaticString != NULL) && (Length < StaticString->MaximumLength)) {
// woohoo it worked; some voodoo is going on where the current directory
// or something just changed prior to us acquiring the Peb lock.
StaticString->Length = (USHORT) Length;
if (FilePartPrefixCch != NULL) {
*FilePartPrefixCch = (FilePart != NULL) ? (FilePart - StaticString->Buffer) : 0;
}
if (StringUsed != NULL)
*StringUsed = StaticString;
} else {
// If it doesn't fit into a UNICODE string, we're in big trouble.
if ((Length + sizeof(WCHAR)) > UNICODE_STRING_MAX_BYTES) {
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
TempDynamicString.MaximumLength = (USHORT) (Length + sizeof(WCHAR));
TempDynamicString.Buffer = (RtlAllocateStringRoutine)(TempDynamicString.MaximumLength);
if (TempDynamicString.Buffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
Length = RtlGetFullPathName_Ustr(
FileName,
TempDynamicString.MaximumLength - sizeof(WCHAR),
TempDynamicString.Buffer,
&FilePart,
NameInvalid,
InputPathType);
if (Length == 0) {
#if DBG
DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n", __FUNCTION__, __LINE__);
#endif // DBG
Status = STATUS_OBJECT_NAME_INVALID;
goto Exit;
}
// If this assert fires, it means that someone changed something that
// RtlGetFullPathName_Ustr() uses to resolve the filename, even while
// we're holding the Peb lock. This is really bad and whoever is
// trashing the PEB is resposible, not this code.
ASSERT(Length < (TempDynamicString.MaximumLength - sizeof(WCHAR)));
if (Length > (TempDynamicString.MaximumLength - sizeof(WCHAR))) {
Status = STATUS_INTERNAL_ERROR;
goto Exit;
}
if (FilePartPrefixCch != NULL) {
*FilePartPrefixCch = (FilePart != NULL) ? (FilePart - TempDynamicString.Buffer) : 0;
}
TempDynamicString.Buffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
DynamicString->Buffer = TempDynamicString.Buffer;
DynamicString->Length = (USHORT) Length;
DynamicString->MaximumLength = TempDynamicString.MaximumLength;
if (StringUsed != NULL)
*StringUsed = DynamicString;
TempDynamicString.Buffer = NULL;
}
Status = STATUS_SUCCESS;
} __finally {
RtlReleasePebLock();
}
}
Exit:
if (TempDynamicString.Buffer != NULL) {
(RtlFreeStringRoutine)(TempDynamicString.Buffer);
}
#if DBG
// This happens a lot for STATUS_NO_SUCH_FILE and STATUS_BUFFER_TOO_SMALL; we'll report any others
if (NT_ERROR(Status) && (Status != STATUS_NO_SUCH_FILE) && (Status != STATUS_BUFFER_TOO_SMALL)) {
DbgPrint("RTL: %s - failing on filename %wZ with status %08lx\n", __FUNCTION__, FileName, Status);
}
#endif // DBG
return Status;
}
ULONG
RtlGetFullPathName_U(
PCWSTR lpFileName,
ULONG nBufferLength,
PWSTR lpBuffer,
PWSTR *lpFilePart OPTIONAL
)
{
UNICODE_STRING UnicodeString;
RTL_PATH_TYPE PathType;
RtlInitUnicodeString(&UnicodeString,lpFileName);
return RtlGetFullPathName_Ustr(&UnicodeString,nBufferLength,lpBuffer,lpFilePart,NULL,&PathType);
}
NTSTATUS
RtlpWin32NTNameToNtPathName_U(
IN PUNICODE_STRING DosFileName,
OUT PUNICODE_STRING NtFileName,
OUT PWSTR *FilePart OPTIONAL,
OUT PRTL_RELATIVE_NAME RelativeName OPTIONAL
)
{
PWSTR FullNtPathName = NULL;
PWSTR Source,Dest;
NTSTATUS Status = STATUS_SUCCESS;
FullNtPathName = RtlAllocateHeap(
RtlProcessHeap(),
0,
DosFileName->Length-8+sizeof(UNICODE_NULL)+RtlpDosDevicesPrefix.Length
);
if ( !FullNtPathName ) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
//
// Copy the full Win32/NT path next to the name prefix, skipping over
// the \\?\ at the front of the path.
//
RtlMoveMemory(FullNtPathName,RtlpDosDevicesPrefix.Buffer,RtlpDosDevicesPrefix.Length);
RtlMoveMemory((PUCHAR)FullNtPathName+RtlpDosDevicesPrefix.Length,
DosFileName->Buffer + 4,
DosFileName->Length - 8);
//
// Null terminate the path name to make strlen below happy.
//
NtFileName->Buffer = FullNtPathName;
NtFileName->Length = (USHORT)(DosFileName->Length-8+RtlpDosDevicesPrefix.Length);
NtFileName->MaximumLength = NtFileName->Length + sizeof(UNICODE_NULL);
FullNtPathName[ NtFileName->Length >> 1 ] = UNICODE_NULL;
//
// Now we have the passed in path with \DosDevices\ prepended. Blow out the
// relative name structure (if provided), and possibly compute filepart
//
if ( ARGUMENT_PRESENT(RelativeName) ) {
//
// If the current directory is a sub-string of the
// Nt file name, and if a handle exists for the current
// directory, then return the directory handle and name
// relative to the directory.
//
RelativeName->RelativeName.Length = 0;
RelativeName->RelativeName.MaximumLength = 0;
RelativeName->RelativeName.Buffer = 0;
RelativeName->ContainingDirectory = NULL;
}
if ( ARGUMENT_PRESENT( FilePart ) ) {
//
// Locate the file part...
//
Source = &FullNtPathName[ (NtFileName->Length-1) >> 1 ];
Dest = NULL;
while(Source > FullNtPathName ) {
if ( *Source == L'\\' ) {
Dest = Source + 1;
break;
}
Source--;
}
if ( Dest && *Dest ) {
*FilePart = Dest;
}
else {
*FilePart = NULL;
}
}
Status = STATUS_SUCCESS;
Exit:
return Status;
}
BOOLEAN
RtlDosPathNameToNtPathName_Ustr(
IN PCUNICODE_STRING DosFileNameString,
OUT PUNICODE_STRING NtFileName,
OUT PWSTR *FilePart OPTIONAL,
OUT PRTL_RELATIVE_NAME RelativeName OPTIONAL
)
/*++
Routine Description:
A Dos pathname can be translated into an Nt style pathname
using this function.
This function is used only within the Base dll to translate Dos
pathnames to Nt pathnames. Upon successful translation, the
pointer (NtFileName->Buffer) points to memory from RtlProcessHeap()
that contains the Nt version of the input dos file name.
Arguments:
DosFileName - Supplies the unicode Dos style file name that is to be
translated into an equivalent unicode Nt file name.
NtFileName - Returns the address of memory in the RtlProcessHeap() that
contains an NT filename that refers to the specified Dos file
name.
FilePart - Optional parameter that if specified, returns the
trailing file portion of the file name. A path of \foo\bar\x.x
returns the address of x.x as the file part.
RelativeName - An optional parameter, that if specified, returns
a pathname relative to the current directory of the file. The
length field of RelativeName->RelativeName is 0 if the relative
name can not be used.
Return Value:
TRUE - The path name translation was successful. Once the caller is
done with the translated name, the memory pointed to by
NtFileName.Buffer should be returned to the RtlProcessHeap().
FALSE - The operation failed.
Note:
The buffers pointed to by RelativeName, FilePart, and NtFileName must ALL
point within the same memory address. If they don't, code that calls
this routine will fail.
--*/
{
ULONG BufferLength;
ULONG DosPathLength;
PWSTR FullNtPathName = NULL;
PWSTR FullDosPathName = NULL;
UNICODE_STRING Prefix;
UNICODE_STRING UnicodeFilePart;
UNICODE_STRING FullDosPathString;
PCURDIR CurDir;
RTL_PATH_TYPE DosPathType;
RTL_PATH_TYPE InputDosPathType;
ULONG DosPathNameOffset;
ULONG FullDosPathNameLength;
ULONG LastCharacter;
UNICODE_STRING UnicodeString;
BOOLEAN NameInvalid;
WCHAR StaticDosBuffer[DOS_MAX_PATH_LENGTH + 1];
BOOLEAN UseWin32Name, fRC;
//
// Calculate the size needed for the full pathname. Add in
// space for the longest Nt prefix
//
BufferLength = sizeof(StaticDosBuffer);
DosPathLength = (DOS_MAX_PATH_LENGTH << 1 );
UnicodeString = *DosFileNameString;
//
// see if this is \\?\ form of name
//
if ( UnicodeString.Length > 8 && UnicodeString.Buffer[0] == '\\' &&
UnicodeString.Buffer[1] == '\\' && UnicodeString.Buffer[2] == '?' &&
UnicodeString.Buffer[3] == '\\' ) {
UseWin32Name = TRUE;
}
else {
UseWin32Name = FALSE;
//
// The dos name starts just after the longest Nt prefix
//
FullDosPathName = &StaticDosBuffer[0];
BufferLength += RtlpLongestPrefix;
//
// Allocate space for the full Nt Name (including DOS name portion)
//
FullNtPathName = RtlAllocateHeap(RtlProcessHeap(), 0, BufferLength);
if ( !FullNtPathName ) {
return FALSE;
}
}
RtlAcquirePebLock();
fRC = TRUE;
__try {
__try {
if ( UseWin32Name ) {
NTSTATUS Status;
Status = RtlpWin32NTNameToNtPathName_U(&UnicodeString,NtFileName,FilePart,RelativeName);
fRC = NT_SUCCESS(Status);
__leave;
}
FullDosPathNameLength = RtlGetFullPathName_Ustr(
&UnicodeString,
DosPathLength,
FullDosPathName,
FilePart,
&NameInvalid,
&InputDosPathType
);
if ( NameInvalid || !FullDosPathNameLength ||
FullDosPathNameLength > DosPathLength ) {
fRC = FALSE;
__leave;
}
//
// Determine how to format prefix of FullNtPathName base on the
// the type of Dos path name. All Nt names begin in the \DosDevices
// directory.
//
Prefix = RtlpDosDevicesPrefix;
DosPathType = RtlDetermineDosPathNameType_U(FullDosPathName);
switch (DosPathType) {
case RtlPathTypeUncAbsolute :
//
// Unc name, use \DosDevices\UNC symbolic link to find
// redirector. Skip of \\ in source Dos path.
//
Prefix = RtlpDosDevicesUncPrefix;
DosPathNameOffset = 2;
break;
case RtlPathTypeLocalDevice :
//
// Local device name, so just use \DosDevices prefix and
// skip \\.\ in source Dos path.
//
DosPathNameOffset = 4;
break;
case RtlPathTypeRootLocalDevice :
ASSERT( FALSE );
break;
case RtlPathTypeDriveAbsolute :
case RtlPathTypeDriveRelative :
case RtlPathTypeRooted :
case RtlPathTypeRelative :
//
// All drive references just use \DosDevices prefix and
// do not skip any of the characters in the source Dos path.
//
DosPathNameOffset = 0;
break;
default:
ASSERT( FALSE );
}
//
// Copy the full DOS path next to the name prefix, skipping over
// the "\\" at the front of the UNC path or the "\\.\" at the front
// of a device name.
//
RtlMoveMemory(FullNtPathName,Prefix.Buffer,Prefix.Length);
RtlMoveMemory((PUCHAR)FullNtPathName+Prefix.Length,
FullDosPathName + DosPathNameOffset,
FullDosPathNameLength - (DosPathNameOffset<<1));
//
// Null terminate the path name to make strlen below happy.
//
NtFileName->Buffer = FullNtPathName;
NtFileName->Length = (USHORT)(FullDosPathNameLength-(DosPathNameOffset<<1))+Prefix.Length;
NtFileName->MaximumLength = (USHORT)BufferLength;
LastCharacter = NtFileName->Length >> 1;
FullNtPathName[ LastCharacter ] = UNICODE_NULL;
//
// Readjust the file part to point to the appropriate position within
// the FullNtPathName buffer instead of inside the FullDosPathName
// buffer
//
if ( ARGUMENT_PRESENT(FilePart) ) {
if (*FilePart) {
RtlInitUnicodeString(&UnicodeFilePart,*FilePart);
*FilePart = &FullNtPathName[ LastCharacter ] - (UnicodeFilePart.Length >> 1);
}
}
if ( ARGUMENT_PRESENT(RelativeName) ) {
//
// If the current directory is a sub-string of the
// Nt file name, and if a handle exists for the current
// directory, then return the directory handle and name
// relative to the directory.
//
RelativeName->RelativeName.Length = 0;
RelativeName->RelativeName.MaximumLength = 0;
RelativeName->RelativeName.Buffer = 0;
RelativeName->ContainingDirectory = NULL;
if ( InputDosPathType == RtlPathTypeRelative ) {
CurDir = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
if ( CurDir->Handle ) {
//
// Now compare curdir to full dos path. If curdir length is
// greater than full path. It is not a match. Otherwise,
// trim full path length to cur dir length and compare.
//
RtlInitUnicodeString(&FullDosPathString,FullDosPathName);
if ( CurDir->DosPath.Length <= FullDosPathString.Length ) {
FullDosPathString.Length = CurDir->DosPath.Length;
if ( RtlEqualUnicodeString(
(PUNICODE_STRING)&CurDir->DosPath,
&FullDosPathString,
TRUE
) ) {
//
// The full dos pathname is a substring of the
// current directory. Compute the start of the
// relativename.
//
RelativeName->RelativeName.Buffer = ((PUCHAR)FullNtPathName + Prefix.Length - (DosPathNameOffset<<1) + (CurDir->DosPath.Length));
RelativeName->RelativeName.Length = (USHORT)FullDosPathNameLength - (CurDir->DosPath.Length);
if ( *(PWSTR)(RelativeName->RelativeName.Buffer) == L'\\' ) {
(PWSTR)(RelativeName->RelativeName.Buffer)++;
RelativeName->RelativeName.Length -= 2;
}
RelativeName->RelativeName.MaximumLength = RelativeName->RelativeName.Length;
RelativeName->ContainingDirectory = CurDir->Handle;
}
}
}
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
fRC = FALSE;
}
}
finally {
if (fRC == FALSE && FullNtPathName != NULL) {
RtlFreeHeap(RtlProcessHeap(), 0, FullNtPathName);
}
RtlReleasePebLock();
}
return fRC;
}
BOOLEAN
RtlDosPathNameToNtPathName_U(
IN PCWSTR DosFileName,
OUT PUNICODE_STRING NtFileName,
OUT PWSTR *FilePart OPTIONAL,
OUT PRTL_RELATIVE_NAME RelativeName OPTIONAL
)
{
UNICODE_STRING DosFileNameString;
ULONG Length = 0;
if (DosFileName != NULL) {
Length = wcslen( DosFileName ) * sizeof( WCHAR );
if (Length + sizeof( UNICODE_NULL ) >= UNICODE_STRING_MAX_BYTES) {
return FALSE;
}
DosFileNameString.MaximumLength = (USHORT)(Length + sizeof( UNICODE_NULL ));
} else {
DosFileNameString.MaximumLength = 0;
}
DosFileNameString.Buffer = (PWSTR) DosFileName;
DosFileNameString.Length = (USHORT)Length;
return RtlDosPathNameToNtPathName_Ustr( &DosFileNameString, NtFileName, FilePart, RelativeName );
}
NTSTATUS
RtlDoesFileExist3(
OUT PHANDLE FileHandle,
IN PCUNICODE_STRING FileNameString,
IN ACCESS_MASK DesiredAccess,
IN ULONG ShareAccess,
IN ULONG OpenOptions
)
/*++
RtlDoesFileExist is "RtlDoesFileExist1"
RtlDoesFileExistEx is "RtlDoesFileExist2"
Supposedly NtOpenFile on unc paths is not reliable (per comments in RtlDoesFileExists_UstrEx).
Supposedly NtQueryAttributesFile on unc paths is not reliable (per that RtlDoesFileExists_UstrEx has to
decide how to treat STATUS_ACCESS_DENIED and STATUS_SHARING_VIOLATION and per comments
from Mike Grier. STATUS_SHARING_VIOLATION does imply exists, but it's up to the caller
to decide what they really want to know..)
NtOpenFile to NT4 unc and random downlevel 3rd party filesystems / redirectors is also not reliable,
it needs the workarounds that kernel32.dll CreateFile supplies. This is a general
bug throughout ntdll.dll.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING NtFileName;
PVOID FreeBuffer = NULL;
FILE_ALL_INFORMATION FileInfo; // it feels safest to get all
// information, like GetFileInformationByHandle does
BOOLEAN DosPathToNtPathReturnValue;
RTL_RELATIVE_NAME RelativeName;
IO_STATUS_BLOCK IoStatusBlock;
if (FileHandle != NULL) {
*FileHandle = NULL;
}
if (FileHandle == NULL) {
Status = STATUS_INVALID_PARAMETER_1;
goto Exit;
}
if (FileNameString == NULL) {
Status = STATUS_INVALID_PARAMETER_2;
goto Exit;
}
DosPathToNtPathReturnValue = RtlDosPathNameToNtPathName_Ustr(
FileNameString,
&NtFileName,
NULL,
&RelativeName
);
if ( !DosPathToNtPathReturnValue ) {
Status = STATUS_UNSUCCESSFUL; // Blackcomb..
goto Exit;
}
FreeBuffer = NtFileName.Buffer;
if ( RelativeName.RelativeName.Length ) {
NtFileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
}
else {
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(
&Obja,
&NtFileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL
);
//
// do we worry about synchronize / pending?
//
Status = NtOpenFile(FileHandle, DesiredAccess | FILE_READ_ATTRIBUTES, &Obja, &IoStatusBlock, ShareAccess, OpenOptions);
if (!NT_SUCCESS(Status))
goto Exit;
Status = NtQueryInformationFile(*FileHandle, &IoStatusBlock, &FileInfo, sizeof(FileInfo), FileAllInformation);
if (!NT_SUCCESS(Status)) {
NtClose(*FileHandle);
*FileHandle = NULL;
goto Exit;
}
Status = STATUS_SUCCESS;
Exit:
RtlFreeHeap(RtlProcessHeap(),0,FreeBuffer);
return Status;
}
BOOLEAN
RtlDoesFileExists_UstrEx(
IN PCUNICODE_STRING FileNameString,
IN BOOLEAN TreatDeniedOrSharingAsHit
)
/*++
Routine Description:
This function checks to see if the specified unicode filename exists.
Arguments:
FileName - Supplies the file name of the file to find.
Return Value:
TRUE - The file was found.
FALSE - The file was not found.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
UNICODE_STRING NtFileName;
BOOLEAN ReturnValue;
RTL_RELATIVE_NAME RelativeName;
PVOID FreeBuffer;
FILE_BASIC_INFORMATION BasicInfo;
ReturnValue = RtlDosPathNameToNtPathName_Ustr(
FileNameString,
&NtFileName,
NULL,
&RelativeName
);
if ( !ReturnValue ) {
return FALSE;
}
FreeBuffer = NtFileName.Buffer;
if ( RelativeName.RelativeName.Length ) {
NtFileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
}
else {
RelativeName.ContainingDirectory = NULL;
}
InitializeObjectAttributes(
&Obja,
&NtFileName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL
);
//
// Query the file's attributes. Note that the file cannot simply be opened
// to determine whether or not it exists, as the NT LanMan redirector lies
// on NtOpenFile to a Lan Manager server because it does not actually open
// the file until an operation is performed on it.
//
Status = NtQueryAttributesFile(
&Obja,
&BasicInfo
);
RtlFreeHeap(RtlProcessHeap(),0,FreeBuffer);
if ( !NT_SUCCESS(Status) ) {
if ( Status == STATUS_SHARING_VIOLATION ||
Status == STATUS_ACCESS_DENIED ) {
if ( TreatDeniedOrSharingAsHit ) {
ReturnValue = TRUE;
}
else {
ReturnValue = FALSE;
}
}
else {
ReturnValue = FALSE;
}
}
else {
ReturnValue = TRUE;
}
return ReturnValue;
}
BOOLEAN
RtlDoesFileExists_UEx(
IN PCWSTR FileName,
IN BOOLEAN TreatDeniedOrSharingAsHit
)
{
UNICODE_STRING FileNameString;
RtlInitUnicodeString(&FileNameString, FileName);
return RtlDoesFileExists_UstrEx(&FileNameString, TreatDeniedOrSharingAsHit);
}
BOOLEAN
RtlDoesFileExists_U(
IN PCWSTR FileName
)
/*++
Routine Description:
This function checks to see if the specified unicode filename exists.
Arguments:
FileName - Supplies the file name of the file to find.
Return Value:
TRUE - The file was found.
FALSE - The file was not found.
--*/
{
return RtlDoesFileExists_UEx(FileName,TRUE);
}
BOOLEAN
RtlDoesFileExists_UStr(
IN PCUNICODE_STRING FileName
)
/*
RtlDoesFileExists_UStr same as RtlDoesFileExists_U but takes a PCUNICODE_STRING instead of a PCWSTR,
saving a call to wcslen
*/
{
return RtlDoesFileExists_UstrEx(FileName,TRUE);
}
ULONG
RtlDosSearchPath_U(
IN PCWSTR lpPath,
IN PCWSTR lpFileName,
IN PCWSTR lpExtension OPTIONAL,
IN ULONG nBufferLength,
OUT PWSTR lpBuffer,
OUT PWSTR *lpFilePart
)
/*++
Routine Description:
This function is used to search for a file specifying a search path
and a filename. It returns with a fully qualified pathname of the
found file.
This function is used to locate a file using the specified path. If
the file is found, its fully qualified pathname is returned. In
addition to this, it calculates the address of the file name portion
of the fully qualified pathname.
Arguments:
lpPath - Supplies the search path to be used when locating the file.
lpFileName - Supplies the file name of the file to search for.
lpExtension - An optional parameter, that if specified, supplies an
extension to be added to the filename when doing the search.
The extension is only added if the specified filename does not
end with an extension.
nBufferLength - Supplies the length in bytes of the buffer that is
to receive the fully qualified path.
lpBuffer - Returns the fully qualified pathname corresponding to the
file that was found.
lpFilePart - Optional parameter that if specified, returns the
address of the last component of the fully qualified pathname.
Return Value:
The return value is the length of the string copied to lpBuffer, not
including the terminating null character. If the return value is
greater than nBufferLength, the return value is the size of the buffer
required to hold the pathname. The return value is zero if the
function failed.
--*/
{
PWSTR ComputedFileName;
ULONG ExtensionLength;
ULONG PathLength;
ULONG FileLength;
UNICODE_STRING Scratch;
PCWSTR p;
//
// if the file name is not a relative name, then
// return an if the file does not exist.
//
// If a fully qualified pathname is used in the search, then
// allow access_denied or sharing violations to terminate the
// search. This was the nt 3.1-4.0 behavior, and was changed for the
// loader to handle cases where when walking through a search, we didn't
// terminate the search early because of an inaccessible UNC path component
// be restoring the old behavior in this case, we give the correct (access_denied)
// error codes on fully qualified module lookups, but keep going when bumping
// through search path components
//
if ( RtlDetermineDosPathNameType_U(lpFileName) != RtlPathTypeRelative ) {
if (RtlDoesFileExists_UEx(lpFileName,TRUE) ) {
PathLength = RtlGetFullPathName_U(
lpFileName,
nBufferLength,
lpBuffer,
lpFilePart
);
return PathLength;
}
else {
return 0;
}
}
//
// Determine if the file name contains an extension
//
ExtensionLength = 1;
p = lpFileName;
while (*p) {
if ( *p == L'.' ) {
ExtensionLength = 0;
break;
}
p++;
}
//
// If no extension was found, then determine the extension length
// that should be used to search for the file
//
if ( ExtensionLength ) {
if ( ARGUMENT_PRESENT(lpExtension) ) {
RtlInitUnicodeString(&Scratch,lpExtension);
ExtensionLength = Scratch.Length;
}
else {
ExtensionLength = 0;
}
}
//
// Compute the file name length and the path length;
//
RtlInitUnicodeString(&Scratch,lpPath);
PathLength = Scratch.Length;
RtlInitUnicodeString(&Scratch,lpFileName);
FileLength = Scratch.Length;
ComputedFileName = RtlAllocateHeap(
RtlProcessHeap(), 0,
PathLength + FileLength + ExtensionLength + 3*sizeof(UNICODE_NULL)
);
if ( !ComputedFileName ) {
KdPrint(("%s: Failing due to out of memory (RtlAllocateHeap failure)\n", __FUNCTION__));
return 0;
}
//
// find ; 's in path and copy path component to computed file name
//
do {
PWSTR Cursor;
Cursor = ComputedFileName;
while (*lpPath) {
if (*lpPath == L';') {
lpPath++;
break;
}
*Cursor++ = *lpPath++;
}
if (Cursor != ComputedFileName &&
Cursor [ -1 ] != L'\\' ) {
*Cursor++ = L'\\';
}
if (*lpPath == UNICODE_NULL) {
lpPath = NULL;
}
RtlMoveMemory(Cursor,lpFileName,FileLength);
if ( ExtensionLength ) {
RtlMoveMemory((PUCHAR)Cursor+FileLength,lpExtension,ExtensionLength+sizeof(UNICODE_NULL));
}
else {
*(PWSTR)((PUCHAR)Cursor+FileLength) = UNICODE_NULL;
}
if (RtlDoesFileExists_UEx(ComputedFileName,FALSE) ) {
PathLength = RtlGetFullPathName_U(
ComputedFileName,
nBufferLength,
lpBuffer,
lpFilePart
);
RtlFreeHeap(RtlProcessHeap(), 0, ComputedFileName);
return PathLength;
}
}
while ( lpPath );
RtlFreeHeap(RtlProcessHeap(), 0, ComputedFileName);
return 0;
}
NTSTATUS
RtlDosSearchPath_Ustr(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
IN PCUNICODE_STRING FileName,
IN PCUNICODE_STRING DefaultExtension OPTIONAL,
OUT PUNICODE_STRING StaticString OPTIONAL,
OUT PUNICODE_STRING DynamicString OPTIONAL,
OUT PCUNICODE_STRING *FullFileNameOut OPTIONAL,
OUT SIZE_T *FilePartPrefixCch OPTIONAL,
OUT SIZE_T *BytesRequired OPTIONAL // includes space for trailing NULL
)
/*++
Routine Description:
This function is used to search for a file specifying a search path
and a filename. It returns with a fully qualified pathname of the
found file.
This function is used to locate a file using the specified path. If
the file is found, its fully qualified pathname is returned. In
addition to this, it calculates the address of the file name portion
of the fully qualified pathname.
Arguments:
Flags - Optional flags to affect the behavior of the path search.
Use the logical or operator (|) to combine flags.
Defined flags include:
RTL_DOS_SEARCH_PATH_FLAG_APPLY_ISOLATION_REDIRECTION
If the FileName passed in is a relative path, isolation
redirection of the file path is applied prior to searching
for a matching path.
Path - search path to use when locating the file
FileName - file name to search for
DefaultExtension - Optional extension to apply to the file name
if the file name does not include an extension.
StaticString - Optional UNICODE_STRING which references an already
allocated buffer which is used to construct the actual path
of the file.
DynamicString - Optional UNICODE_STRING which will be filled in with
a dynamically allocated UNICODE_STRING if either StaticBuffer is
not provided, or is not long enough to hold the resolved name.
The dynamic buffer's size is reflected in the MaximumLength field
of the UNICODE_STRING. It will always exceed the Length of the
string by at least two bytes, but may be even larger.
FullFileNameOut - Optional pointer to UNICODE_STRING which points to
the complete resolved file name. This UNICODE_STRING is not
allocated; it is either set equal to FileName, StaticBuffer or
DynamicBuffer as appropriate.
Return Value:
NTSTATUS indicating the disposition of the function. If the file
is redirected via activation context data, STATUS_SUCCESS is returned
regardless of whether the file exists or not. If the file does not
exist in any of the directories referenced by the Path parameter,
STATUS_NO_SUCH_FILE is returned.
--*/
{
NTSTATUS Status;
PWSTR Cursor;
PWSTR EndMarker;
SIZE_T MaximumPathSegmentLength = 0;
SIZE_T BiggestPossibleFileName;
USHORT DefaultExtensionLength = 0;
RTL_PATH_TYPE PathType; // not used; required argument to RtlGetFullPathName_Ustr().
UNICODE_STRING CandidateString;
WCHAR StaticCandidateBuffer[DOS_MAX_PATH_LENGTH];
CandidateString.Length = 0;
CandidateString.MaximumLength = sizeof(StaticCandidateBuffer);
CandidateString.Buffer = StaticCandidateBuffer;
if (FullFileNameOut != NULL) {
*FullFileNameOut = NULL;
}
if (BytesRequired != NULL) {
*BytesRequired = 0;
}
if (FilePartPrefixCch != NULL) {
*FilePartPrefixCch = 0;
}
if (DynamicString != NULL) {
DynamicString->Length = 0;
DynamicString->MaximumLength = 0;
DynamicString->Buffer = NULL;
}
if (((Flags & ~(
RTL_DOS_SEARCH_PATH_FLAG_APPLY_ISOLATION_REDIRECTION |
RTL_DOS_SEARCH_PATH_FLAG_DISALLOW_DOT_RELATIVE_PATH_SEARCH |
RTL_DOS_SEARCH_PATH_FLAG_APPLY_DEFAULT_EXTENSION_WHEN_NOT_RELATIVE_PATH_EVEN_IF_FILE_HAS_EXTENSION)) != 0) ||
(Path == NULL) ||
(FileName == NULL) ||
((StaticString != NULL) && (DynamicString != NULL) && (FullFileNameOut == NULL))) {
#if DBG
DbgPrint("%s: Invalid parameters passed\n", __FUNCTION__);
#endif // DBG
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
PathType = RtlDetermineDosPathNameType_Ustr(FileName);
// If the caller wants to disallow .\ and ..\ relative path searches, stop them!
if ((Flags & RTL_DOS_SEARCH_PATH_FLAG_DISALLOW_DOT_RELATIVE_PATH_SEARCH) && (PathType == RtlPathTypeRelative)) {
if (FileName->Length >= (2 * sizeof(WCHAR))) {
if (FileName->Buffer[0] == L'.') {
if (IS_PATH_SEPARATOR_U(FileName->Buffer[1])) {
PathType = RtlPathTypeUnknown;
} else if ((FileName->Buffer[1] == L'.') &&
(FileName->Length >= (3 * sizeof(WCHAR))) &&
IS_PATH_SEPARATOR_U(FileName->Buffer[2])) {
PathType = RtlPathTypeUnknown;
}
}
}
}
//
// if the file name is not a relative name, then
// return an if the file does not exist.
//
// If a fully qualified pathname is used in the search, then
// allow access_denied or sharing violations to terminate the
// search. This was the nt 3.1-4.0 behavior, and was changed for the
// loader to handle cases where when walking through a search, we didn't
// terminate the search early because of an inaccessible UNC path component
// be restoring the old behavior in this case, we give the correct (access_denied)
// error codes on fully qualified module lookups, but keep going when bumping
// through search path components
//
if (PathType != RtlPathTypeRelative ) {
if (RtlDoesFileExists_UstrEx(FileName, TRUE)) {
Status = RtlGetFullPathName_UstrEx(
RTL_CONST_CAST(PUNICODE_STRING)(FileName),
StaticString,
DynamicString,
(PUNICODE_STRING *) FullFileNameOut,
FilePartPrefixCch,
NULL,
&PathType,
BytesRequired);
if (!NT_SUCCESS(Status)) {
#if DBG
if ((Status != STATUS_NO_SUCH_FILE) && (Status != STATUS_BUFFER_TOO_SMALL)) {
DbgPrint("%s: Failing because RtlGetFullPathName_UstrEx() on %wZ failed with %08lx\n", __FUNCTION__, FileName, Status);
}
#endif // DBG
goto Exit;
}
} else {
//
// The file wasn't there; let's try adding the default extension if we need to.
//
if ((DefaultExtension == NULL) || (DefaultExtension->Length == 0)) {
#if DBG
// DbgPrint("%s: Failing because RtlDoesFileExists_UstrEx() on %wZ says it does not exist and there is no default extension to apply\n", __FUNCTION__, FileName);
#endif // DBG
Status = STATUS_NO_SUCH_FILE;
goto Exit;
}
DefaultExtensionLength = DefaultExtension->Length;
// If they've asked for SearchPathW() bug compatibility mode, always apply the default
// extension if the file isn't found, even if the file name has an extension.
if (!(Flags & RTL_DOS_SEARCH_PATH_FLAG_APPLY_DEFAULT_EXTENSION_WHEN_NOT_RELATIVE_PATH_EVEN_IF_FILE_HAS_EXTENSION)) {
if (FileName->Length != 0) {
Cursor = FileName->Buffer + (FileName->Length / sizeof(WCHAR));
while (Cursor != FileName->Buffer) {
const WCHAR wch = *--Cursor;
if (IS_PATH_SEPARATOR_U(wch)) {
// it's a slash; we have a filename without an extension...
break;
}
if (wch == L'.') {
// There was an extension. We're just out of luck.
Status = STATUS_NO_SUCH_FILE;
goto Exit;
}
}
}
}
// We need to move the filename into a different buffer.
BiggestPossibleFileName = (FileName->Length + DefaultExtensionLength + sizeof(WCHAR));
if (BiggestPossibleFileName > UNICODE_STRING_MAX_BYTES) {
#if DBG
DbgPrint("%s: Failing because the filename plus extension (%Iu bytes) is too big\n", __FUNCTION__, BiggestPossibleFileName);
#endif // DBG
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
// If the buffer on the stack isn't big enough, allocate one from the heap
if (BiggestPossibleFileName > CandidateString.MaximumLength) {
CandidateString.MaximumLength = (USHORT) BiggestPossibleFileName;
CandidateString.Buffer = (RtlAllocateStringRoutine)(CandidateString.MaximumLength);
if (CandidateString.Buffer == NULL) {
#if DBG
DbgPrint("%s: Failing because allocating the dynamic filename buffer failed\n", __FUNCTION__);
#endif // DBG
Status = STATUS_NO_MEMORY;
goto Exit;
}
}
RtlCopyMemory(CandidateString.Buffer, FileName->Buffer, FileName->Length);
RtlCopyMemory(CandidateString.Buffer + (FileName->Length / sizeof(WCHAR)), DefaultExtension->Buffer, DefaultExtension->Length);
CandidateString.Buffer[(FileName->Length + DefaultExtension->Length) / sizeof(WCHAR)] = UNICODE_NULL;
CandidateString.Length = FileName->Length + DefaultExtension->Length;
if (!RtlDoesFileExists_UstrEx(&CandidateString, TRUE)) {
Status = STATUS_NO_SUCH_FILE;
goto Exit;
}
Status = RtlGetFullPathName_UstrEx(
&CandidateString,
StaticString,
DynamicString,
(PUNICODE_STRING *) FullFileNameOut,
FilePartPrefixCch,
NULL,
&PathType,
BytesRequired);
if (!NT_SUCCESS(Status)) {
#if DBG
if (Status != STATUS_NO_SUCH_FILE) {
DbgPrint("%s: Failing on \"%wZ\" because RtlGetFullPathName_UstrEx() failed with status %08lx\n", __FUNCTION__, &CandidateString, Status);
}
#endif // DBG
goto Exit;
}
}
Status = STATUS_SUCCESS;
goto Exit;
}
// We know it's a relative path at this point. Do we want to try side-by-side
// isolation of the file?
if (Flags & RTL_DOS_SEARCH_PATH_FLAG_APPLY_ISOLATION_REDIRECTION) {
PUNICODE_STRING FullPathStringFound = NULL;
Status = RtlDosApplyFileIsolationRedirection_Ustr(
RTL_DOS_APPLY_FILE_REDIRECTION_USTR_FLAG_RESPECT_DOT_LOCAL, FileName,
DefaultExtension, StaticString, DynamicString, &FullPathStringFound,
NULL, FilePartPrefixCch, BytesRequired);
if (NT_SUCCESS(Status)) {
if (FullFileNameOut != NULL) {
*FullFileNameOut = FullPathStringFound;
}
Status = STATUS_SUCCESS;
goto Exit;
}
if (Status != STATUS_SXS_KEY_NOT_FOUND) {
#if DBG
DbgPrint("%s: Failing because call to RtlDosApplyFileIsolationRedirection_Ustr() failed with status %wZ\n", __FUNCTION__, FileName);
#endif // DBG
goto Exit;
}
}
//
// If a default extension was provided, see if we need to account for it
//
if (DefaultExtension != NULL) {
DefaultExtensionLength = DefaultExtension->Length;
if (FileName->Length != 0) {
Cursor = FileName->Buffer + (FileName->Length / sizeof(WCHAR));
while (Cursor != FileName->Buffer) {
const WCHAR wch = *--Cursor;
if (IS_PATH_SEPARATOR_U(wch)) {
// it's a slash; we have a filename without an extension...
break;
}
if (wch == L'.') {
// There's an extension; ignore the defualt.
DefaultExtension = NULL;
DefaultExtensionLength = 0;
break;
}
}
}
}
if (Path->Length != 0) {
USHORT CchThisSegment;
PCWSTR LastCursor;
Cursor = Path->Buffer + (Path->Length / sizeof(WCHAR));
LastCursor = Cursor;
while (Cursor != Path->Buffer) {
if (*--Cursor == L';') {
CchThisSegment = (USHORT) ((LastCursor - Cursor) - 1);
if (CchThisSegment != 0) {
// If there is not a trailing slash, add one character
if (!IS_PATH_SEPARATOR_U(LastCursor[-1])) {
CchThisSegment++;
}
}
if (CchThisSegment > MaximumPathSegmentLength) {
MaximumPathSegmentLength = CchThisSegment;
}
// LastCursor now points to the semicolon...
LastCursor = Cursor;
}
}
CchThisSegment = (USHORT) (LastCursor - Cursor);
if (CchThisSegment != 0) {
if (!IS_PATH_SEPARATOR_U(LastCursor[-1])) {
CchThisSegment++;
}
}
if (CchThisSegment > MaximumPathSegmentLength) {
MaximumPathSegmentLength = CchThisSegment;
}
// Convert from WCHARs to bytes
MaximumPathSegmentLength *= sizeof(WCHAR);
}
BiggestPossibleFileName =
MaximumPathSegmentLength +
FileName->Length +
DefaultExtensionLength +
sizeof(WCHAR); // don't forget space for a trailing NULL...
// It's all got to fit into a UNICODE_STRING at some point, so check that that's possible
if (BiggestPossibleFileName > UNICODE_STRING_MAX_BYTES) {
#if DBG
DbgPrint("%s: returning STATUS_NAME_TOO_LONG because the computed worst case file name length is %Iu bytes\n", __FUNCTION__, BiggestPossibleFileName);
#endif // DBG
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
// It's tempting to allocate the dynamic buffer here, but if it turns out that
// the file is quickly found in one of the first segments that fits in the
// static buffer, we'll have wasted a heap alloc.
Cursor = Path->Buffer;
EndMarker = Cursor + (Path->Length / sizeof(WCHAR));
while (Cursor != EndMarker) {
PWSTR BufferToFill = NULL;
PWSTR BufferToFillCursor;
PWSTR SegmentEnd = Cursor;
USHORT SegmentSize;
USHORT BytesToCopy;
UNICODE_STRING DebugString;
// Scan ahead for the end of the path buffer or the next semicolon
while ((SegmentEnd != EndMarker) && (*SegmentEnd != L';'))
SegmentEnd++;
SegmentSize = (USHORT) ((SegmentEnd - Cursor) * sizeof(WCHAR));
DebugString.Buffer = Cursor;
DebugString.Length = SegmentSize;
DebugString.MaximumLength = SegmentSize;
BytesToCopy = SegmentSize;
// Add space for a trailing slash if there isn't one.
if ((SegmentSize != 0) && !IS_PATH_SEPARATOR_U(SegmentEnd[-1])) {
SegmentSize += sizeof(WCHAR);
}
// If the string we're using for the candidates isn't big enough, allocate one that is.
if (CandidateString.MaximumLength < (SegmentSize + FileName->Length + DefaultExtensionLength + sizeof(WCHAR))) {
// If CandidateString is already a dynamic buffer, something's hosed because we should have allocated
// the largest one needed the first time we outgrew the static one.
ASSERT(CandidateString.Buffer == StaticCandidateBuffer);
if (CandidateString.Buffer != StaticCandidateBuffer) {
#if DBG
DbgPrint("%s: internal error #1; CandidateString.Buffer = %p; StaticCandidateBuffer = %p\n", __FUNCTION__, CandidateString.Buffer, StaticCandidateBuffer);
#endif // DBG
Status = STATUS_INTERNAL_ERROR;
goto Exit;
}
// If this assert fires, there's either a code bug above where we computed the maximum
// segment length, or someone's changing either the filename, default extension or
// path around on us in another thread. Performing a capture on the buffers seems like
// massive overkill, so we'll just not overrun our buffers here.
ASSERT((SegmentSize + FileName->Length + DefaultExtensionLength) < UNICODE_STRING_MAX_BYTES);
if ((SegmentSize + FileName->Length + DefaultExtensionLength) > UNICODE_STRING_MAX_BYTES) {
#if DBG
DbgPrint("%s: internal error #2; SegmentSize = %u, FileName->Length = %u, DefaultExtensionLength = %u\n", __FUNCTION__,
SegmentSize, FileName->Length, DefaultExtensionLength);
#endif // DBG
Status = STATUS_INTERNAL_ERROR;
goto Exit;
}
CandidateString.MaximumLength = (USHORT) BiggestPossibleFileName;
CandidateString.Buffer = (RtlAllocateStringRoutine)(CandidateString.MaximumLength);
if (CandidateString.Buffer == NULL) {
#if DBG
DbgPrint("%s: Unable to allocate %u byte buffer for path candidate\n", __FUNCTION__, CandidateString.MaximumLength);
#endif // DBG
Status = STATUS_NO_MEMORY;
goto Exit;
}
}
RtlCopyMemory(
CandidateString.Buffer,
Cursor,
BytesToCopy);
BufferToFillCursor = CandidateString.Buffer + (BytesToCopy / sizeof(WCHAR));
// Add a trailing slash if it was omitted. It's safe to index [-1] since
// we know that SegmentSize != 0.
if ((SegmentSize != 0) && (BytesToCopy != SegmentSize))
*BufferToFillCursor++ = L'\\';
RtlCopyMemory(
BufferToFillCursor,
FileName->Buffer,
FileName->Length);
BufferToFillCursor += (FileName->Length / sizeof(WCHAR));
if (DefaultExtension != NULL) {
RtlCopyMemory(
BufferToFillCursor,
DefaultExtension->Buffer,
DefaultExtension->Length);
BufferToFillCursor += (DefaultExtension->Length / sizeof(WCHAR));
}
// And top it off with a unicode null...
*BufferToFillCursor = UNICODE_NULL;
CandidateString.Length = (USHORT) ((BufferToFillCursor - CandidateString.Buffer) * sizeof(WCHAR));
if (RtlDoesFileExists_UEx(CandidateString.Buffer, FALSE)) {
// Run it through the path canonicalizer...
Status = RtlGetFullPathName_UstrEx(
&CandidateString,
StaticString,
DynamicString,
(PUNICODE_STRING *) FullFileNameOut,
FilePartPrefixCch,
NULL,
&PathType,
BytesRequired);
if (NT_SUCCESS(Status))
Status = STATUS_SUCCESS;
else {
#if DBG
if ((Status != STATUS_NO_SUCH_FILE) && (Status != STATUS_BUFFER_TOO_SMALL)) {
DbgPrint("%s: Failing because we thought we found %wZ on the search path, but RtlGetFullPathName_UstrEx() returned %08lx\n", __FUNCTION__, FileName, Status);
}
#endif // DBG
}
goto Exit;
}
if (SegmentEnd != EndMarker)
Cursor = SegmentEnd + 1;
else
Cursor = SegmentEnd;
}
Status = STATUS_NO_SUCH_FILE;
Exit:
if ((CandidateString.Buffer != NULL) &&
(CandidateString.Buffer != StaticCandidateBuffer)) {
RtlFreeUnicodeString(&CandidateString);
}
return Status;
}
VOID
RtlpCheckRelativeDrive(
WCHAR NewDrive
)
/*++
Routine Description:
This function is called whenever we are asked to expand a non
current directory drive relative name ( f:this\is\my\file ). In
this case, we validate the environment variable string to make sure
the current directory at that drive is valid. If not, we trim back to
the root.
Arguments:
NewDrive - Supplies the drive to check
Return Value:
None.
--*/
{
WCHAR EnvVarValueBuffer[DOS_MAX_PATH_LENGTH+12]; // + sizeof (\DosDevices\)
WCHAR EnvVarNameBuffer[4];
UNICODE_STRING EnvVarName;
UNICODE_STRING EnvValue;
NTSTATUS Status;
OBJECT_ATTRIBUTES Obja;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE DirHandle;
ULONG HardErrorValue;
PTEB Teb;
EnvVarNameBuffer[0] = L'=';
EnvVarNameBuffer[1] = (WCHAR)NewDrive;
EnvVarNameBuffer[2] = L':';
EnvVarNameBuffer[3] = UNICODE_NULL;
RtlInitUnicodeString(&EnvVarName,EnvVarNameBuffer);
//
// capture the value in a buffer that has space at the front for the dos devices
// prefix
//
EnvValue.Length = 0;
EnvValue.MaximumLength = DOS_MAX_PATH_LENGTH<<1;
EnvValue.Buffer = &EnvVarValueBuffer[RtlpDosDevicesPrefix.Length>>1];
Status = RtlQueryEnvironmentVariable_U( NULL,
&EnvVarName,
&EnvValue
);
if ( !NT_SUCCESS( Status ) ) {
//
// Otherwise default to root directory of drive
//
EnvValue.Buffer[0] = (WCHAR)NewDrive;
EnvValue.Buffer[1] = L':';
EnvValue.Buffer[2] = L'\\';
EnvValue.Buffer[3] = UNICODE_NULL;
EnvValue.Length = 6;
}
//
// Form the NT name for this directory
//
EnvValue.Length = EnvValue.Length + RtlpDosDevicesPrefix.Length;
EnvValue.MaximumLength = sizeof(EnvValue);
EnvValue.Buffer = EnvVarValueBuffer;
RtlCopyMemory(EnvVarValueBuffer,RtlpDosDevicesPrefix.Buffer,RtlpDosDevicesPrefix.Length);
InitializeObjectAttributes(
&Obja,
&EnvValue,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
Teb = NtCurrentTeb();
HardErrorValue = Teb->HardErrorsAreDisabled;
Teb->HardErrorsAreDisabled = 1;
Status = NtOpenFile(
&DirHandle,
SYNCHRONIZE,
&Obja,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT
);
Teb->HardErrorsAreDisabled = HardErrorValue;
//
// If the open succeeds, then the directory is valid... No need to do anything
// further. If the open fails, then trim back the environment to the root.
//
if ( NT_SUCCESS(Status) ) {
NtClose(DirHandle);
return;
}
RtlpResetDriveEnvironment(NewDrive);
}
//
// The expected calling sequence for RtlDosApplyFileIsolationRedirection_Ustr() is something
// like this:
//
// {
// WCHAR Buffer[MAX_PATH];
// UNICODE_STRING PreAllocatedString;
// UNICODE_STRING DynamicallyAllocatedString;
// PUNICODE_STRING FullPath;
//
// PreAllocatedString.Length = 0;
// PreAllocatedString.MaximumLength = sizeof(Buffer);
// PreAllocatedString.Buffer = Buffer;
//
// Status = RtlDosApplyFileIsolationRedirection_Ustr(
// Flags,
// FileToCheck,
// DefaultExtensionToApply, // for example ".DLL"
// &PreAllocatedString,
// &DynamicallyAllocatedString,
// &FullPath);
// if (NT_ERROR(Status)) return Status;
// // now code uses FullPath as the complete path name...
//
// // In exit paths, always free the dynamic string:
// RtlFreeUnicodeString(&DynamicallyAllocatedString);
// }
//
NTSTATUS
RtlDosApplyFileIsolationRedirection_Ustr(
ULONG Flags,
PCUNICODE_STRING FileNameIn,
PCUNICODE_STRING DefaultExtension,
PUNICODE_STRING PreAllocatedString,
PUNICODE_STRING DynamicallyAllocatedString,
PUNICODE_STRING *FullPath,
ULONG *OutFlags,
SIZE_T *FilePartPrefixCch,
SIZE_T *BytesRequired
)
{
NTSTATUS Status;
ACTIVATION_CONTEXT_SECTION_KEYED_DATA askd = {sizeof(askd)};
PUNICODE_STRING TempString = NULL;
UNICODE_STRING TempDynamicString;
ULONG BytesLeft;
ULONG RealTotalPathLength;
ULONG i;
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION UNALIGNED * DllRedirData;
const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT UNALIGNED * PathSegmentArray;
PCUNICODE_STRING FileName = NULL;
UNICODE_STRING FileNameWithExtension;
WCHAR FileNameWithExtensionBuffer[DOS_MAX_PATH_LENGTH/4];
PWSTR Cursor;
PACTIVATION_CONTEXT ActivationContext = NULL;
PCUNICODE_STRING AssemblyStorageRoot = NULL;
WCHAR ExpandedTemporaryBuffer[DOS_MAX_PATH_LENGTH];
UNICODE_STRING ExpandedTemporaryString;
UNICODE_STRING DllNameUnderLocalDirString = { 0, 0, NULL };
RTL_PATH_TYPE PathType = 0;
PCUNICODE_STRING LocalDllNameString = NULL;
#if DBG_SXS
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_TRACE_LEVEL,
"SXS: Entered %s\n", __FUNCTION__);
#endif // DBG_SXS
if (DynamicallyAllocatedString != NULL) {
DynamicallyAllocatedString->Length = 0;
DynamicallyAllocatedString->MaximumLength = 0;
DynamicallyAllocatedString->Buffer = NULL;
}
if (OutFlags != NULL)
*OutFlags = 0;
if (FilePartPrefixCch != NULL)
*FilePartPrefixCch = 0;
if (BytesRequired != NULL)
*BytesRequired = 0;
TempDynamicString.Length = 0;
TempDynamicString.MaximumLength = 0;
TempDynamicString.Buffer = NULL;
ExpandedTemporaryString.Length = 0;
ExpandedTemporaryString.MaximumLength = sizeof(ExpandedTemporaryBuffer);
ExpandedTemporaryString.Buffer = ExpandedTemporaryBuffer;
FileNameWithExtension.Buffer = NULL;
// Valid input conditions:
// 1. You have to have a filename
// 2. If you specify both the preallocated buffer and the dynamically
// allocated buffer, you need the FullPath parameter to detect
// which was actually populated.
// 3. If you ask for the file part prefix cch, you need an output
// buffer; otherwise you can't know what the cch is relative to.
if (((Flags & ~(RTL_DOS_APPLY_FILE_REDIRECTION_USTR_FLAG_RESPECT_DOT_LOCAL)) != 0) ||
(FileNameIn == NULL) ||
((PreAllocatedString == NULL) && (DynamicallyAllocatedString == NULL) && (FilePartPrefixCch != NULL)) ||
((PreAllocatedString != NULL) &&
(DynamicallyAllocatedString != NULL) &&
(FullPath == NULL))) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"%s - invalid parameter(s)\n"
" FileNameIn = %p\n"
" PreAllocatedString = %p\n"
" DynamicallyAllocatedString = %p\n"
" FullPath = %p\n"
" FilePartPrefixCch = %p\n",
__FUNCTION__,
FileNameIn,
PreAllocatedString,
DynamicallyAllocatedString,
FullPath,
FilePartPrefixCch);
#endif // DBG
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
PathType = RtlDetermineDosPathNameType_Ustr(FileNameIn);
if (PathType!= RtlPathTypeRelative) {
//
// We only redirect relative paths for actctx; bypass all extra work for non-relative paths
// but for local file, we need verify its existence of those files.
//
if ((PathType == RtlPathTypeDriveAbsolute) || (PathType == RtlPathTypeUncAbsolute))
{
// check the existence of the local
PPEB pPeb = NtCurrentPeb();
if ((pPeb != NULL) && (pPeb->ProcessParameters != NULL) && (pPeb->ProcessParameters->Flags & RTL_USER_PROC_DLL_REDIRECTION_LOCAL)) // there is .local
{
// check whether this file is under the app directory
const USHORT cbFullImageNameLength = pPeb->ProcessParameters->ImagePathName.Length;
const LPWSTR pFullImageName = (PWSTR)pPeb->ProcessParameters->ImagePathName.Buffer;
LPWSTR p, p1, t, t1;
USHORT cbPathLength, cbDllPathLength, cbApplicationPathLength;
if (pFullImageName == NULL)
goto SxsKeyNotFound;
p = pFullImageName + cbFullImageNameLength/sizeof(WCHAR) - 1; // point to last character of this name
p1 = NULL;
while (p != pFullImageName) {
if (*p == (WCHAR)'\\') {
p1 = p + 1;
break;
}
p-- ;
}
ASSERT(p1 != NULL);
cbApplicationPathLength = (USHORT)(p1 - pFullImageName) * sizeof(WCHAR);
t = FileNameIn->Buffer + FileNameIn->Length / sizeof(WCHAR) -1; // point to last char of this full-qualified filename
t1 = NULL;
while (t != FileNameIn->Buffer ) {
if (*t == (WCHAR)'\\') {
t1 = t + 1;
break;
}
t-- ;
}
ASSERT(t1 != NULL);
cbDllPathLength = (USHORT)(t1 - FileNameIn->Buffer) * sizeof(WCHAR);
// comparison the path
if (cbDllPathLength == cbApplicationPathLength)
{
UNICODE_STRING ustrAppPath, ustrDllPath;
ustrAppPath.Buffer = pFullImageName;
ustrAppPath.Length = cbApplicationPathLength;
ustrAppPath.MaximumLength = cbApplicationPathLength;
ustrDllPath.Buffer = FileNameIn->Buffer;
ustrDllPath.Length = cbDllPathLength;
ustrDllPath.MaximumLength = cbDllPathLength;
if (RtlCompareUnicodeString(&ustrAppPath, &ustrDllPath, TRUE) == 0 )
{
// .local and local binary
LocalDllNameString = FileNameIn;
goto CopyIntoOutBuffer;
}
}
// for file name with a fullly qualified path, which is not the app dir,
// we need check the appdir and appdir\app.exe.local\ whether a file with the same name exists,
// If so, it is redired. Just like a relative-path filename
goto DoAllWork; // only for .local case
}
}
SxsKeyNotFound:
Status = STATUS_SXS_KEY_NOT_FOUND;
goto Exit;
}
DoAllWork:
// See if we need to default the extension...
if ((DefaultExtension != NULL) && (DefaultExtension->Length != 0)) {
SIZE_T BytesRequired;
if (FileNameIn->Length >= sizeof(WCHAR)) {
Cursor = FileNameIn->Buffer + ((FileNameIn->Length / sizeof(WCHAR)) - 1);
while (Cursor >= FileNameIn->Buffer) {
const WCHAR wch = *Cursor--;
if (IS_PATH_SEPARATOR_U(wch)) {
// we've come to the end of the last path segment; break out.
break;
}
if (wch == L'.') {
// There's an extension; don't default anything.
DefaultExtension = NULL;
break;
}
}
}
// If it's still not null, we have some work to do.
if (DefaultExtension != NULL) {
BytesRequired = FileNameIn->Length + DefaultExtension->Length + sizeof(WCHAR);
if (BytesRequired > UNICODE_STRING_MAX_BYTES) {
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
if (BytesRequired > sizeof(FileNameWithExtensionBuffer)) {
FileNameWithExtension.Buffer = (RtlAllocateStringRoutine)(BytesRequired);
if (FileNameWithExtension.Buffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
FileNameWithExtension.MaximumLength = (USHORT) BytesRequired;
} else {
FileNameWithExtension.Buffer = FileNameWithExtensionBuffer;
FileNameWithExtension.MaximumLength = sizeof(FileNameWithExtensionBuffer);
}
FileNameWithExtension.Length = ((USHORT) BytesRequired - sizeof(WCHAR));
RtlCopyMemory(
FileNameWithExtension.Buffer,
FileNameIn->Buffer,
FileNameIn->Length);
RtlCopyMemory(
FileNameWithExtension.Buffer + (FileNameIn->Length / sizeof(WCHAR)),
DefaultExtension->Buffer,
DefaultExtension->Length);
FileNameWithExtension.Buffer[(FileNameIn->Length + DefaultExtension->Length) / sizeof(WCHAR)] = UNICODE_NULL;
FileName = &FileNameWithExtension;
}
}
// If we didn't munge it, use the pointer to the filename passed in
if (FileName == NULL) {
FileName = FileNameIn;
}
//
// Before the actctx is searched, check whether .local is respected.
// 1) if .local is respected
// 2) get the .local filename
// 3) if the .local file exist,
// 4) return the local filename
// 5) endif
// 6) endif
if ((Flags & RTL_DOS_APPLY_FILE_REDIRECTION_USTR_FLAG_RESPECT_DOT_LOCAL) &&
(NtCurrentPeb()->ProcessParameters != NULL) &&
(NtCurrentPeb()->ProcessParameters->Flags & RTL_USER_PROC_DLL_REDIRECTION_LOCAL))
{
BOOLEAN fLocalExist = FALSE;
//
// \x\foo.exe
// ExpandedTemporaryString is \x\bar.dll
// DllNameUnderLocalDirString is \x\foo.exe.local\bar.dll
//
Status = RtlComputePrivatizedDllName_U(FileName, &ExpandedTemporaryString, &DllNameUnderLocalDirString);
if(!NT_SUCCESS(Status))
goto Exit;
#if DBG && 0
{
BOOLEAN Boolean;
NTSTATUS Status;
HANDLE Handle = NULL;
const PCUNICODE_STRING Files[] = { &DllNameUnderLocalDirString, &ExpandedTemporaryString };
ULONG i;
for (i = 0 ; i != RTL_NUMBER_OF(Files) ; ++i) {
const PCUNICODE_STRING File = Files[i];
Boolean = RtlDoesFileExists_UStr(File);
Status = RtlDoesFileExist3(&Handle, File, FILE_GENERIC_READ, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE);
DbgPrint("RtlDoesFileExists_UStr(%wZ):%s\n", File, Boolean ? "true" : "false");
DbgPrint("RtlDoesFileExist3(%wZ):%x\n", File, Status);
if (Handle != NULL) {
NtClose(Handle);
Handle = NULL;
}
}
}
#endif
if (RtlDoesFileExists_UStr(&DllNameUnderLocalDirString))// there is a local dll, use it
{
LocalDllNameString = &DllNameUnderLocalDirString;
}
else if (RtlDoesFileExists_UStr(&ExpandedTemporaryString))// there is a local dll, use it
{
LocalDllNameString = &ExpandedTemporaryString;
}
CopyIntoOutBuffer:
//
// we have found the .local files
//
if (LocalDllNameString != NULL)
{
if ((PreAllocatedString == NULL) && (DynamicallyAllocatedString == NULL))
{
Status = STATUS_SUCCESS;
// This was just an existence test. return the successful status.
goto Exit;
}
if ((DynamicallyAllocatedString == NULL) &&
(PreAllocatedString->MaximumLength < (LocalDllNameString->Length + sizeof(WCHAR)))) {
if (BytesRequired != NULL)
*BytesRequired = (LocalDllNameString->Length + sizeof(WCHAR));
Status = STATUS_BUFFER_TOO_SMALL;
goto Exit;
}
if ((PreAllocatedString == NULL) ||
(PreAllocatedString->MaximumLength < (LocalDllNameString->Length + sizeof(WCHAR))))
{
TempDynamicString.Buffer = (RtlAllocateStringRoutine)(LocalDllNameString->Length + sizeof(WCHAR));
if (TempDynamicString.Buffer == NULL)
{
Status = STATUS_NO_MEMORY;
goto Exit;
}
TempDynamicString.MaximumLength = (USHORT) (LocalDllNameString->Length + sizeof(WCHAR));
TempString = &TempDynamicString;
} else
{
TempString = PreAllocatedString;
}
RtlCopyMemory(TempString->Buffer, LocalDllNameString->Buffer, LocalDllNameString->Length);
TempString->Length = (USHORT)LocalDllNameString->Length;
TempString->Buffer[TempString->Length / sizeof(WCHAR)] = 0;
if (OutFlags)
*OutFlags |= RTL_DOS_APPLY_FILE_REDIRECTION_USTR_OUTFLAG_DOT_LOCAL_REDIRECT;
goto Done;
}
}
Status = RtlFindActivationContextSectionString(
FIND_ACTIVATION_CONTEXT_SECTION_KEY_RETURN_ACTIVATION_CONTEXT
| FIND_ACTIVATION_CONTEXT_SECTION_KEY_RETURN_FLAGS,
NULL,
ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION,
FileName,
&askd);
if (!NT_SUCCESS(Status)) {
// Map section not found back to key not found so that callers don't
// have to worry about sections being present vs. the lookup key
// being present.
if (Status == STATUS_SXS_SECTION_NOT_FOUND)
Status = STATUS_SXS_KEY_NOT_FOUND;
if (Status != STATUS_SXS_KEY_NOT_FOUND) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - RtlFindActivationContextSectionString() returned ntstatus 0x%08lx\n",
__FUNCTION__,
Status);
#endif // DBG
}
goto Exit;
}
ActivationContext = askd.ActivationContext;
if ((PreAllocatedString == NULL) && (DynamicallyAllocatedString == NULL)) {
Status = STATUS_SUCCESS;
// This was just an existence test. return the successful status.
goto Exit;
}
if ((askd.Length < sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION)) ||
(askd.DataFormatVersion != ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_FORMAT_WHISTLER)) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - Length or data format version of dll redirection data invalid.\n",
__FUNCTION__);
#endif // DBG
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
DllRedirData = (const ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION UNALIGNED *) askd.Data;
// If the entry requires path root resolution, do so!
if (DllRedirData->Flags & ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_OMITS_ASSEMBLY_ROOT) {
NTSTATUS InnerStatus = STATUS_SUCCESS;
// There's no need to support both a dynamic root and environment variable
// expansion.
if (DllRedirData->Flags & ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_EXPAND) {
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
Status = RtlGetAssemblyStorageRoot(
((askd.Flags & ACTIVATION_CONTEXT_SECTION_KEYED_DATA_FLAG_FOUND_IN_PROCESS_DEFAULT)
? RTL_GET_ASSEMBLY_STORAGE_ROOT_FLAG_ACTIVATION_CONTEXT_USE_PROCESS_DEFAULT
: 0)
| ((askd.Flags & ACTIVATION_CONTEXT_SECTION_KEYED_DATA_FLAG_FOUND_IN_SYSTEM_DEFAULT)
? RTL_GET_ASSEMBLY_STORAGE_ROOT_FLAG_ACTIVATION_CONTEXT_USE_SYSTEM_DEFAULT
: 0),
ActivationContext,
askd.AssemblyRosterIndex,
&AssemblyStorageRoot,
&RtlpAssemblyStorageMapResolutionDefaultCallback,
(PVOID) &InnerStatus);
if (NT_ERROR(Status)) {
if (Status == STATUS_CANCELLED) {
if (NT_ERROR(InnerStatus)) {
Status = InnerStatus;
}
}
goto Exit;
}
}
RealTotalPathLength = DllRedirData->TotalPathLength;
if (!(DllRedirData->Flags & ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_INCLUDES_BASE_NAME))
RealTotalPathLength += FileName->Length;
if (AssemblyStorageRoot != NULL)
RealTotalPathLength += AssemblyStorageRoot->Length;
// If the path doesn't fit into the statically allocated buffer and there isn't a dynamic one,
// we're in trouble.
if ((DynamicallyAllocatedString == NULL) &&
(PreAllocatedString->MaximumLength < (RealTotalPathLength + sizeof(WCHAR)))) {
if (BytesRequired != NULL)
*BytesRequired = (RealTotalPathLength + sizeof(WCHAR));
Status = STATUS_BUFFER_TOO_SMALL;
goto Exit;
}
// Allocate the dynamic string if we need to...
if ((PreAllocatedString == NULL) ||
(PreAllocatedString->MaximumLength < (RealTotalPathLength + sizeof(WCHAR)))) {
TempDynamicString.Buffer = (RtlAllocateStringRoutine)(RealTotalPathLength + sizeof(WCHAR));
if (TempDynamicString.Buffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
TempDynamicString.MaximumLength = (USHORT) (RealTotalPathLength + sizeof(WCHAR));
TempString = &TempDynamicString;
} else {
TempString = PreAllocatedString;
}
Cursor = TempString->Buffer;
// Account for the trailing null character in BytesLeft up front...
BytesLeft = TempString->MaximumLength - sizeof(WCHAR);
// If the path segment list extends beyond the section, we're outta here
if ((((ULONG) DllRedirData->PathSegmentOffset) > askd.SectionTotalLength) ||
(((ULONG) (DllRedirData->PathSegmentOffset + (DllRedirData->PathSegmentCount * sizeof(ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT)))) > askd.SectionTotalLength)) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - Path segment array extends beyond section limits\n",
__FUNCTION__);
#endif // DBG
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
// If we have a dynamically obtained storage root, put it in the buffer first.
if (AssemblyStorageRoot != NULL) {
USHORT Length = AssemblyStorageRoot->Length; // capture the value so that we don't possibly overwrite the buffer
if (BytesLeft < Length) {
Status = STATUS_INTERNAL_ERROR; // someone's playing tricks on us with the unicode string
goto Exit;
}
RtlCopyMemory(Cursor, AssemblyStorageRoot->Buffer, Length);
Cursor = (PWSTR) (((ULONG_PTR) Cursor) + Length);
BytesLeft -= Length;
}
PathSegmentArray = (PCACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_SEGMENT) (((ULONG_PTR) askd.SectionBase) + DllRedirData->PathSegmentOffset);
for (i=0; i<DllRedirData->PathSegmentCount; i++)
{
// It would seem that we could just report insufficient buffer here, but the total
// size checked earlier indicated that the FullPath variable was big enough;
// the data structure is hosed.
if (BytesLeft < PathSegmentArray[i].Length) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - path segment %lu overflowed buffer\n",
__FUNCTION__, i);
#endif // DBG
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
// If the character array is outside the bounds of the section, something's hosed.
if ((((ULONG) PathSegmentArray[i].Offset) > askd.SectionTotalLength) ||
(((ULONG) (PathSegmentArray[i].Offset + PathSegmentArray[i].Length)) > askd.SectionTotalLength)) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - path segment %lu at %p (offset: %ld, length: %lu, bounds: %lu) buffer is outside section bounds\n",
__FUNCTION__,
i,
&PathSegmentArray[i].Offset,
PathSegmentArray[i].Offset,
PathSegmentArray[i].Length,
askd.SectionTotalLength);
#endif // DBG
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
RtlCopyMemory(
Cursor,
(PVOID) (((ULONG_PTR) askd.SectionBase) + PathSegmentArray[i].Offset),
PathSegmentArray[i].Length);
Cursor += (PathSegmentArray[i].Length / sizeof(WCHAR));
BytesLeft -= PathSegmentArray[i].Length;
}
if (!(DllRedirData->Flags & ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_INCLUDES_BASE_NAME)) {
if (BytesLeft < FileName->Length) {
#if DBG
DbgPrintEx(
DPFLTR_SXS_ID,
DPFLTR_ERROR_LEVEL,
"SXS: %s - appending base file name overflowed buffer\n",
__FUNCTION__);
#endif // DBG
Status = STATUS_SXS_INVALID_ACTCTXDATA_FORMAT;
goto Exit;
}
RtlCopyMemory(
Cursor,
FileName->Buffer,
FileName->Length);
Cursor += (FileName->Length / sizeof(WCHAR));
BytesLeft -= FileName->Length;
}
*Cursor = L'\0';
TempString->Length = (USHORT) (((ULONG_PTR) Cursor) - ((ULONG_PTR) TempString->Buffer));
// Apply any environment strings as necessary...
if (DllRedirData->Flags & ACTIVATION_CONTEXT_DATA_DLL_REDIRECTION_PATH_EXPAND) {
ULONG ExpandedStringLength;
ExpandedTemporaryString.Length = 0;
ExpandedTemporaryString.MaximumLength = sizeof(ExpandedTemporaryBuffer) - sizeof(WCHAR);
ExpandedTemporaryString.Buffer = ExpandedTemporaryBuffer;
Status = RtlExpandEnvironmentStrings_U(NULL, TempString, &ExpandedTemporaryString, &ExpandedStringLength);
if (NT_ERROR(Status)) {
if (Status != STATUS_BUFFER_TOO_SMALL)
goto Exit;
// If it expands to something larger than UNICODE_MAX_BYTES, there's no hope.
if (ExpandedStringLength > UNICODE_STRING_MAX_BYTES) {
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
ExpandedTemporaryString.Buffer = (RtlAllocateStringRoutine)(ExpandedStringLength);
if (ExpandedTemporaryString.Buffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
// This cast is OK because we already range checked the value.
ExpandedTemporaryString.MaximumLength = (USHORT) ExpandedStringLength;
ExpandedTemporaryString.Length = 0;
Status = RtlExpandEnvironmentStrings_U(NULL, TempString, &ExpandedTemporaryString, &ExpandedStringLength);
if (NT_ERROR(Status))
goto Exit;
ExpandedTemporaryString.Buffer[ExpandedTemporaryString.Length / sizeof(WCHAR)] = L'\0';
// Now move this thing into the caller's buffer
if ((PreAllocatedString != NULL) && (PreAllocatedString->MaximumLength >= ExpandedTemporaryString.MaximumLength)) {
// It does. Just copy the bits and let's go.
RtlCopyMemory(
PreAllocatedString->Buffer,
ExpandedTemporaryString.Buffer,
ExpandedTemporaryString.MaximumLength); // MaximumLength because it includes the trailing null character
PreAllocatedString->Length = ExpandedTemporaryString.Length;
TempString = PreAllocatedString;
} else if (DynamicallyAllocatedString != NULL) {
// If they can take a dynamic string, we'll use this one.
TempString = &ExpandedTemporaryString;
} else {
// Just plain no room.
if (BytesRequired != NULL)
*BytesRequired = ExpandedTemporaryString.MaximumLength;
Status = STATUS_BUFFER_TOO_SMALL;
goto Exit;
}
} else {
ExpandedTemporaryString.Buffer[ExpandedTemporaryString.Length / sizeof(WCHAR)] = L'\0';
// Hey, it succeeded! Let's see if we can fit this into the output static string.
if ((PreAllocatedString != NULL) && ((ExpandedTemporaryString.Length + sizeof(WCHAR)) < PreAllocatedString->MaximumLength)) {
// It fits!
RtlCopyMemory(
PreAllocatedString->Buffer,
ExpandedTemporaryBuffer,
ExpandedTemporaryString.Length + sizeof(WCHAR));
PreAllocatedString->Length = ExpandedTemporaryString.Length;
} else {
// Let's make a dynamic string. It's possible that we already have a dynamic string
// but the chance is pretty darned remote so I won't optimize for it. -mgrier
if ((ExpandedTemporaryString.Length + sizeof(WCHAR)) > UNICODE_STRING_MAX_BYTES) {
Status = STATUS_NAME_TOO_LONG;
goto Exit;
}
ExpandedTemporaryString.MaximumLength = (USHORT) (ExpandedTemporaryString.Length + sizeof(WCHAR));
ExpandedTemporaryString.Buffer = (RtlAllocateStringRoutine)(ExpandedTemporaryString.MaximumLength);
if (ExpandedTemporaryString.Buffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Exit;
}
RtlCopyMemory(
ExpandedTemporaryString.Buffer,
ExpandedTemporaryBuffer,
ExpandedTemporaryString.MaximumLength);
TempString = &ExpandedTemporaryString;
}
}
}
if (OutFlags)
*OutFlags |= RTL_DOS_APPLY_FILE_REDIRECTION_USTR_OUTFLAG_ACTCTX_REDIRECT;
Done:
if (FilePartPrefixCch != NULL) {
//
// set Cursor at the end of the unicode string
//
Cursor = TempString->Buffer + TempString->Length;
while (Cursor != TempString->Buffer) {
if (IS_PATH_SEPARATOR_U(*Cursor)) {
break;
}
Cursor--;
}
*FilePartPrefixCch = (Cursor - TempString->Buffer) + 1;
}
if (TempString != PreAllocatedString)
{
*DynamicallyAllocatedString = *TempString;
//
// TempString->Buffer is dynamically allocated, and this memory would be
// passed to caller, so set it to be NULL to avoid to be freed;
//
TempDynamicString.Buffer = NULL;
if (FullPath != NULL)
*FullPath = DynamicallyAllocatedString;
}
else
if (FullPath != NULL)
*FullPath = PreAllocatedString;
Status = STATUS_SUCCESS;
Exit:
if (DllNameUnderLocalDirString.Buffer != NULL)
(RtlFreeStringRoutine)(DllNameUnderLocalDirString.Buffer);
if (TempDynamicString.Buffer != NULL) {
(RtlFreeStringRoutine)(TempDynamicString.Buffer);
}
if ((FileNameWithExtension.Buffer != NULL) && (FileNameWithExtension.Buffer != FileNameWithExtensionBuffer)) {
(RtlFreeStringRoutine)(FileNameWithExtension.Buffer);
}
if ((ExpandedTemporaryString.Buffer != NULL) && (ExpandedTemporaryString.Buffer != ExpandedTemporaryBuffer)) {
(RtlFreeStringRoutine)(ExpandedTemporaryString.Buffer);
}
if (ActivationContext != NULL)
RtlReleaseActivationContext(ActivationContext);
return Status;
}
#define RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS_OR_NT (0x00000001)
#define RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS (0x00000002)
#define RTLP_LAST_PATH_ELEMENT_PATH_TYPE_NT (0x00000003)
#define RTLP_LAST_PATH_ELEMENT_PATH_TYPE_DOS (0x00000004)
NTSTATUS
NTAPI
RtlpGetLengthWithoutLastPathElement(
IN ULONG Flags,
IN ULONG PathType,
IN PCUNICODE_STRING Path,
OUT ULONG* LengthOut
)
/*++
Routine Description:
Report how long Path would be if you remove its last element.
This is much simpler than RtlRemoveLastDosPathElement.
It is used to implement the other RtlRemoveLast*PathElement.
Arguments:
Flags - room for future expansion
Path - the path is is an NT path or a full DOS path; the various relative DOS
path types do not work, see RtlRemoveLastDosPathElement for them.
Return Value:
STATUS_SUCCESS - the usual hunky-dory
STATUS_NO_MEMORY - the usual stress
STATUS_INVALID_PARAMETER - the usual bug
--*/
{
ULONG Index = 0;
ULONG Length = 0;
NTSTATUS Status = STATUS_SUCCESS;
RTL_PATH_TYPE DosPathType = RtlPathTypeUnknown;
ULONG DosPathFlags = 0;
const RTL_PATH_TYPE* AllowedPathTypes;
ULONG AllowedDosPathTypeBits = (1UL << RtlPathTypeRooted)
| (1UL << RtlPathTypeUncAbsolute)
| (1UL << RtlPathTypeDriveAbsolute)
| (1UL << RtlPathTypeLocalDevice) // "\\?\"
| (1UL << RtlPathTypeRootLocalDevice) // "\\?"
;
WCHAR PathSeperators[2] = { '/', '\\' };
#define LOCAL_IS_PATH_SEPERATOR(ch_) ((ch_) == PathSeperators[0] || (ch_) == PathSeperators[1])
if (LengthOut != NULL) {
*LengthOut = 0;
}
if ( !RTL_SOFT_VERIFY(Path != NULL)
|| !RTL_SOFT_VERIFY(Flags == 0)
|| !RTL_SOFT_VERIFY(LengthOut != NULL)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
Length = RTL_STRING_GET_LENGTH_CHARS(Path);
switch (PathType)
{
default:
case RTLP_LAST_PATH_ELEMENT_PATH_TYPE_DOS:
Status = STATUS_INVALID_PARAMETER;
goto Exit;
case RTLP_LAST_PATH_ELEMENT_PATH_TYPE_NT:
//
// RtlpDetermineDosPathNameType4 calls it "rooted"
// only backslashes are seperators
// path must start with backslash
// second char must not be backslash
//
AllowedDosPathTypeBits = (1UL << RtlPathTypeRooted);
PathSeperators[0] = '\\';
if (Length > 0 && Path->Buffer[0] != '\\'
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (Length > 1 && Path->Buffer[1] == '\\'
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
break;
case RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS:
AllowedDosPathTypeBits &= ~(1UL << RtlPathTypeRooted);
break;
case RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS_OR_NT:
break;
}
if (Length == 0) {
goto Exit;
}
if (!RTL_SOFT_VERIFY(NT_SUCCESS(Status = RtlpDetermineDosPathNameType4(RTL_DETERMINE_DOS_PATH_NAME_TYPE_IN_FLAG_STRICT_WIN32NT, Path, &DosPathType, &DosPathFlags)))) {
goto Exit;
}
if (!RTL_SOFT_VERIFY((1UL << DosPathType) & AllowedDosPathTypeBits)
) {
//KdPrintEx();
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (!RTL_SOFT_VERIFY(
(PathType & RTLP_DETERMINE_DOS_PATH_NAME_TYPE_OUT_FLAG_INVALID) == 0
)) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
// skip one or more trailing path seperators
for ( ; Length != 0 && LOCAL_IS_PATH_SEPERATOR(Path->Buffer[Length - 1]) ; --Length) {
// nothing
}
// skip trailing path element
for ( ; Length != 0 && !LOCAL_IS_PATH_SEPERATOR(Path->Buffer[Length - 1]) ; --Length) {
// nothing
}
// skip one or more in between path seperators
for ( ; Length != 0 && LOCAL_IS_PATH_SEPERATOR(Path->Buffer[Length - 1]) ; --Length) {
// nothing
}
// put back a trailing path seperator, for the sake of c:\ vs. c:
if (Length != 0) {
++Length;
}
//
// Should optionally check for "bad dos roots" here.
//
*LengthOut = Length;
Status = STATUS_SUCCESS;
Exit:
return Status;
#undef LOCAL_IS_PATH_SEPERATOR
}
NTSTATUS
NTAPI
RtlGetLengthWithoutLastNtPathElement(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
OUT ULONG* LengthOut
)
/*++
Routine Description:
Report how long Path would be if you remove its last element.
Arguments:
Flags - room for future expansion
Path - the path is is an NT path; the various DOS path types
do not work, see RtlRemoveLastDosPathElement for them.
Return Value:
STATUS_SUCCESS - the usual hunky-dory
STATUS_NO_MEMORY - the usual stress
STATUS_INVALID_PARAMETER - the usual bug
--*/
{
NTSTATUS Status = RtlpGetLengthWithoutLastPathElement(Flags, RTLP_LAST_PATH_ELEMENT_PATH_TYPE_NT, Path, LengthOut);
return Status;
}
NTSTATUS
NTAPI
RtlGetLengthWithoutLastFullDosOrNtPathElement(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
OUT ULONG* LengthOut
)
/*++
Routine Description:
Report how long Path would be if you remove its last element.
Arguments:
Flags - room for future expansion
Path - the path is is an NT path; the various DOS path types
do not work, see RtlRemoveLastDosPathElement for them.
Return Value:
STATUS_SUCCESS - the usual hunky-dory
STATUS_NO_MEMORY - the usual stress
STATUS_INVALID_PARAMETER - the usual bug
--*/
{
NTSTATUS Status = RtlpGetLengthWithoutLastPathElement(Flags, RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS_OR_NT, Path, LengthOut);
return Status;
}
CONST CHAR*
RtlpDbgBadDosRootPathTypeToString(
IN ULONG Flags,
IN ULONG RootType
)
/*++
Routine Description:
An aid to writing DbgPrint code.
Arguments:
Flags - room for future binary compatible expansion
RootType - fairly specifically what the string is
RTLP_BAD_DOS_ROOT_PATH_WIN32NT_PREFIX - \\? or \\?\
RTLP_BAD_DOS_ROOT_PATH_WIN32NT_UNC_PREFIX - \\?\unc or \\?\unc\
RTLP_BAD_DOS_ROOT_PATH_NT_PATH - \??\ but this i only a rough check
RTLP_BAD_DOS_ROOT_PATH_MACHINE_NO_SHARE - \\machine or \\?\unc\machine
RTLP_GOOD_DOS_ROOT_PATH - none of the above, seems ok
Return Value:
strings like those that describe RootType or "unknown" or empty in free builds
--*/
{
CONST CHAR* s = "";
#if DBG
if (Flags != 0) {
DbgPrint("Invalid parameter to %s ignored\n", __FUNCTION__);
}
switch (RootType
) {
case RTLP_GOOD_DOS_ROOT_PATH : s = "good"; break;
case RTLP_BAD_DOS_ROOT_PATH_WIN32NT_PREFIX : s = "\\\\?\\"; break;
case RTLP_BAD_DOS_ROOT_PATH_WIN32NT_UNC_PREFIX: s = "\\\\?\\unc"; break;
case RTLP_BAD_DOS_ROOT_PATH_NT_PATH : s = "\\??\\"; break;
case RTLP_BAD_DOS_ROOT_PATH_MACHINE_NO_SHARE : s = "\\\\machine or \\\\?\\unc\\machine"; break;
default:
s = "unknown";
DbgPrint("Invalid parameter %0x08Ix to %s ignored\n", RootType, __FUNCTION__);
break;
}
#endif
return s;
}
NTSTATUS
RtlpCheckForBadDosRootPath(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
OUT ULONG* RootType
)
/*++
Routine Description:
Arguments:
Flags - room for future binary compatible expansion
Path - the path to be checked
RootType - fairly specifically what the string is
RTLP_BAD_DOS_ROOT_PATH_WIN32NT_PREFIX - \\? or \\?\
RTLP_BAD_DOS_ROOT_PATH_WIN32NT_UNC_PREFIX - \\?\unc or \\?\unc\
RTLP_BAD_DOS_ROOT_PATH_NT_PATH - \??\ but this i only a rough check
RTLP_BAD_DOS_ROOT_PATH_MACHINE_NO_SHARE - \\machine or \\?\unc\machine
RTLP_GOOD_DOS_ROOT_PATH - none of the above, seems ok
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
Path is NULL
or Flags uses undefined values
--*/
{
ULONG Length = 0;
ULONG Index = 0;
NTSTATUS Status = STATUS_SUCCESS;
BOOLEAN Unc = FALSE;
BOOLEAN Unc1 = FALSE;
BOOLEAN Unc2 = FALSE;
ULONG PiecesSeen = 0;
if (RootType != NULL) {
*RootType = 0;
}
if ( !RTL_SOFT_VERIFY(Path != NULL)
|| !RTL_SOFT_VERIFY(RootType != NULL)
|| !RTL_SOFT_VERIFY(Flags == 0)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
Length = Path->Length / sizeof(Path->Buffer[0]);
if (Length < 3 || !RTL_IS_PATH_SEPERATOR(Path->Buffer[0])) {
*RootType = RTLP_GOOD_DOS_ROOT_PATH;
goto Exit;
}
// prefix \??\ (heuristic, doesn't catch many NT paths)
if (RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpDosDevicesPrefix), RTL_CONST_CAST(PUNICODE_STRING)(Path), TRUE)) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_NT_PATH;
goto Exit;
}
if (!RTL_IS_PATH_SEPERATOR(Path->Buffer[1])) {
*RootType = RTLP_GOOD_DOS_ROOT_PATH;
goto Exit;
}
// == \\?
if (RtlEqualUnicodeString(Path, &RtlpWin32NtRoot, TRUE)) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_WIN32NT_PREFIX;
goto Exit;
}
if (RtlEqualUnicodeString(Path, &RtlpWin32NtRootSlash, TRUE)) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_WIN32NT_PREFIX;
goto Exit;
}
// == \\?\unc
if (RtlEqualUnicodeString(Path, &RtlpWin32NtUncRoot, TRUE)) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_WIN32NT_UNC_PREFIX;
goto Exit;
}
if (RtlEqualUnicodeString(Path, &RtlpWin32NtUncRootSlash, TRUE)) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_WIN32NT_UNC_PREFIX;
goto Exit;
}
// prefix \\ or \\?\unc
// must check the longer string first, or avoid the short circuit (| instead of ||)
Unc = (Unc1 = RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpWin32NtUncRootSlash), RTL_CONST_CAST(PUNICODE_STRING)(Path), TRUE))
|| (Unc2 = RTL_IS_PATH_SEPERATOR(Path->Buffer[1]));
if (!Unc) {
*RootType = RTLP_GOOD_DOS_ROOT_PATH;
goto Exit;
}
//
// it's unc, see if it is only a machine (note that it'd be really nice if FindFirstFile(\\machine\*)
// just worked and we didn't have to care..)
//
// point index at a slash that precedes the machine, anywhere in the run of slashes,
// but after the \\? stuff
if (Unc1) {
Index = (RtlpWin32NtUncRootSlash.Length / sizeof(RtlpWin32NtUncRootSlash.Buffer[0])) - 1;
} else {
ASSERT(Unc2);
Index = 1;
}
ASSERT(RTL_IS_PATH_SEPERATOR(Path->Buffer[Index]));
Length = Path->Length/ sizeof(Path->Buffer[0]);
//
// skip leading slashes
//
for ( ; Index < Length && RTL_IS_PATH_SEPERATOR(Path->Buffer[Index]) ; ++Index) {
PiecesSeen |= 1;
}
// skip the machine name
for ( ; Index < Length && !RTL_IS_PATH_SEPERATOR(Path->Buffer[Index]) ; ++Index) {
PiecesSeen |= 2;
}
// skip the slashes between machine and share
for ( ; Index < Length && RTL_IS_PATH_SEPERATOR(Path->Buffer[Index]) ; ++Index) {
PiecesSeen |= 4;
}
// skip the share (make sure it's at least one char)
for ( ; Index < Length && !RTL_IS_PATH_SEPERATOR(Path->Buffer[Index]) ; ++Index) {
PiecesSeen |= 8;
break;
}
if (PiecesSeen != 0xF) {
*RootType = RTLP_BAD_DOS_ROOT_PATH_MACHINE_NO_SHARE;
goto Exit;
}
Status = STATUS_SUCCESS;
Exit:
return Status;
}
NTSTATUS
NTAPI
RtlpBadDosRootPathToEmptyString(
IN ULONG Flags,
IN OUT PUNICODE_STRING Path
)
/*++
Routine Description:
Arguments:
Flags - room for future binary compatible expansion
Path - the path to be checked and possibly emptied
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
Path is NULL
or Flags uses undefined values
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG RootType = 0;
if (!NT_SUCCESS(Status = RtlpCheckForBadDosRootPath(0, Path, &RootType))) {
goto Exit;
}
//
// this is not invalid parameter, our contract is we go \\machine\share to empty
// \\?\c: to empty, etc.
//
if (RootType != RTLP_GOOD_DOS_ROOT_PATH) {
if (RootType == RTLP_BAD_DOS_ROOT_PATH_NT_PATH) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
Path->Length = 0;
}
Status = STATUS_SUCCESS;
Exit:
return Status;
}
NTSTATUS
NTAPI
RtlGetLengthWithoutLastFullDosPathElement(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
OUT ULONG* LengthOut
)
/*++
Routine Description:
Given a fulldospath, like c:\, \\machine\share, \\?\unc\machine\share, \\?\c:,
return (in an out parameter) the length if the last element was cut off.
Arguments:
Flags - room for future binary compatible expansion
Path - the path to be truncating
LengthOut - the length if the last path element is removed
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
Path is NULL
or LengthOut is NULL
or Flags uses undefined values
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING CheckRootString = { 0 };
//
// parameter validation is done in RtlpGetLengthWithoutLastPathElement
//
Status = RtlpGetLengthWithoutLastPathElement(Flags, RTLP_LAST_PATH_ELEMENT_PATH_TYPE_FULL_DOS, Path, LengthOut);
if (!(NT_SUCCESS(Status))) {
goto Exit;
}
CheckRootString.Buffer = Path->Buffer;
CheckRootString.Length = (USHORT)(*LengthOut * sizeof(*Path->Buffer));
CheckRootString.MaximumLength = CheckRootString.Length;
if (!NT_SUCCESS(Status = RtlpBadDosRootPathToEmptyString(0, &CheckRootString))) {
goto Exit;
}
*LengthOut = RTL_STRING_GET_LENGTH_CHARS(&CheckRootString);
Status = STATUS_SUCCESS;
Exit:
KdPrintEx((
DPFLTR_SXS_ID, DPFLTR_LEVEL_STATUS(Status),
"%s(%d):%s(%wZ): 0x%08lx\n", __FILE__, __LINE__, __FUNCTION__, Path, Status));
return Status;
}
NTSTATUS
NTAPI
RtlAppendPathElement(
IN ULONG Flags,
IN OUT PRTL_UNICODE_STRING_BUFFER Path,
PCUNICODE_STRING ConstElement
)
/*++
Routine Description:
This function appends a path element to a path.
For now, like:
typedef PRTL_UNICODE_STRING_BUFFER PRTL_MUTABLE_PATH;
typedef PCUNICODE_STRING PCRTL_CONSTANT_PATH_ELEMENT;
Maybe something higher level in the future.
The result with regard to trailing slashes aims to be similar to the inputs.
If either Path or ConstElement contains a trailing slash, the result has a trailing slash.
The character used for the in between and trailing slash is picked among the existing
slashes in the strings.
Arguments:
Flags - the ever popular "room for future binary compatible expansion"
Path -
a string representing a path using \\ or / as seperators
ConstElement -
a string representing a path element
this can actually contain multiple \\ or / delimited path elements
only the start and end of the string are examined for slashes
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
Path is NULL
or LengthOut is NULL
STATUS_NO_MEMORY - RtlHeapAllocate failed
STATUS_NAME_TOO_LONG - the resulting string does not fit in a UNICODE_STRING, due to its
use of USHORT instead of ULONG or SIZE_T
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING InBetweenSlashString = RtlpEmptyString;
UNICODE_STRING TrailingSlashString = RtlpEmptyString;
WCHAR Slashes[] = {0,0,0,0};
ULONG i;
UNICODE_STRING PathsToAppend[3]; // possible slash, element, possible slash
WCHAR PathSeperators[2] = { '/', '\\' };
#define LOCAL_IS_PATH_SEPERATOR(ch_) ((ch_) == PathSeperators[0] || (ch_) == PathSeperators[1])
if ( !RTL_SOFT_VERIFY((Flags & ~(RTL_APPEND_PATH_ELEMENT_ONLY_BACKSLASH_IS_SEPERATOR)) == 0)
|| !RTL_SOFT_VERIFY(Path != NULL)
|| !RTL_SOFT_VERIFY(ConstElement != NULL)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if ((Flags & RTL_APPEND_PATH_ELEMENT_ONLY_BACKSLASH_IS_SEPERATOR) != 0) {
PathSeperators[0] = '\\';
}
if (ConstElement->Length != 0) {
UNICODE_STRING Element = *ConstElement;
//
// Note leading and trailing slashes on the inputs.
// So that we know if an in-between slash is needed, and if a trailing slash is needed,
// and to guide what sort of slash to place.
//
i = 0;
if (Path->String.Length != 0) {
ULONG j;
ULONG Length = Path->String.Length / sizeof(WCHAR);
//
// for the sake for dos drive paths, check the first three chars for a slash
//
for (j = 0 ; j < 3 && j < Length ; ++j) {
if (LOCAL_IS_PATH_SEPERATOR(Path->String.Buffer[j])) {
Slashes[i] = Path->String.Buffer[0];
break;
}
}
i += 1;
if (LOCAL_IS_PATH_SEPERATOR(Path->String.Buffer[Path->String.Length/sizeof(WCHAR) - 1])) {
Slashes[i] = Path->String.Buffer[Path->String.Length/sizeof(WCHAR) - 1];
}
}
i = 2;
if (LOCAL_IS_PATH_SEPERATOR(Element.Buffer[0])) {
Slashes[i] = Element.Buffer[0];
}
i += 1;
if (LOCAL_IS_PATH_SEPERATOR(Element.Buffer[Element.Length/sizeof(WCHAR) - 1])) {
Slashes[i] = Element.Buffer[Element.Length/sizeof(WCHAR) - 1];
}
if (!Slashes[1] && !Slashes[2]) {
//
// first string lacks trailing slash and second string lacks leading slash,
// must insert one; we favor the types we have, otherwise use a default
//
InBetweenSlashString.Length = sizeof(WCHAR);
InBetweenSlashString.Buffer = RtlPathSeperatorString.Buffer;
if ((Flags & RTL_APPEND_PATH_ELEMENT_ONLY_BACKSLASH_IS_SEPERATOR) == 0) {
if (Slashes[3]) {
InBetweenSlashString.Buffer = &Slashes[3];
} else if (Slashes[0]) {
InBetweenSlashString.Buffer = &Slashes[0];
}
}
}
if (Slashes[1] && !Slashes[3]) {
//
// first string has a trailing slash and second string does not,
// must add one, the same type
//
TrailingSlashString.Length = sizeof(WCHAR);
if ((Flags & RTL_APPEND_PATH_ELEMENT_ONLY_BACKSLASH_IS_SEPERATOR) == 0) {
TrailingSlashString.Buffer = &Slashes[1];
} else {
TrailingSlashString.Buffer = RtlPathSeperatorString.Buffer;
}
}
if (Slashes[1] && Slashes[2]) {
//
// have both trailing and leading slash, remove leading
//
Element.Buffer += 1;
Element.Length -= sizeof(WCHAR);
Element.MaximumLength -= sizeof(WCHAR);
}
i = 0;
PathsToAppend[i++] = InBetweenSlashString;
PathsToAppend[i++] = Element;
PathsToAppend[i++] = TrailingSlashString;
Status = RtlMultiAppendUnicodeStringBuffer(Path, RTL_NUMBER_OF(PathsToAppend), PathsToAppend);
if (!NT_SUCCESS(Status))
goto Exit;
}
Status = STATUS_SUCCESS;
Exit:
KdPrintEx((
DPFLTR_SXS_ID, DPFLTR_LEVEL_STATUS(Status),
"%s(%d):%s(%wZ, %wZ): 0x%08lx\n", __FILE__, __LINE__, __FUNCTION__, Path ? &Path->String : NULL, ConstElement, Status));
return Status;
#undef LOCAL_IS_PATH_SEPERATOR
}
NTSTATUS
NTAPI
RtlGetLengthWithoutTrailingPathSeperators(
IN ULONG Flags,
IN PCUNICODE_STRING Path,
OUT ULONG* LengthOut
)
/*++
Routine Description:
This function computes the length of the string (in characters) if
trailing path seperators (\\ and /) are removed.
Arguments:
Path -
a string representing a path using \\ or / as seperators
LengthOut -
the length of String (in characters) having removed trailing characters
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
Path is NULL
or LengthOut is NULL
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG Index = 0;
ULONG Length = 0;
if (LengthOut != NULL) {
//
// Arguably this should be Path->Length / sizeof(*Path->Buffer), but as long
// as the callstack is all high quality code, it doesn't matter.
//
*LengthOut = 0;
}
if ( !RTL_SOFT_VERIFY(Path != NULL)
|| !RTL_SOFT_VERIFY(LengthOut != NULL)
|| !RTL_SOFT_VERIFY(Flags == 0)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
Length = Path->Length / sizeof(*Path->Buffer);
for (Index = Length ; Index != 0 ; --Index) {
if (!RTL_IS_PATH_SEPERATOR(Path->Buffer[Index - 1])) {
break;
}
}
//*LengthOut = (Length - Index);
*LengthOut = Index;
Status = STATUS_SUCCESS;
Exit:
KdPrintEx((
DPFLTR_SXS_ID, DPFLTR_LEVEL_STATUS(Status),
"%s(%d):%s(%wZ): 0x%08lx\n", __FILE__, __LINE__, __FUNCTION__, Path, Status));
return Status;
}
NTSTATUS
NTAPI
RtlpApplyLengthFunction(
IN ULONG Flags,
IN SIZE_T SizeOfStruct,
IN OUT PVOID UnicodeStringOrUnicodeStringBuffer,
NTSTATUS (NTAPI* LengthFunction)(ULONG, PCUNICODE_STRING, ULONG*)
)
/*++
Routine Description:
This function is common code for patterns like
#define RtlRemoveTrailingPathSeperators(Path_) \
(RtlpApplyLengthFunction((Path_), sizeof(*(Path_)), RtlGetLengthWithoutTrailingPathSeperators))
#define RtlRemoveLastPathElement(Path_) \
(RtlpApplyLengthFunction((Path_), sizeof(*(Path_)), RtlGetLengthWithoutLastPathElement))
Note that shortening a UNICODE_STRING only changes the length, whereas
shortening a RTL_UNICODE_STRING_BUFFER writes a terminal nul.
I expect this pattern will be less error prone than having clients pass the UNICODE_STRING
contained in the RTL_UNICODE_STRING_BUFFER followed by calling RTL_NUL_TERMINATE_STRING.
And, that pattern cannot be inlined with a macro while also preserving that we
return an NTSTATUS.
Arguments:
Flags - the ever popular "room for future binary compatible expansion"
UnicodeStringOrUnicodeStringBuffer -
a PUNICODE_STRING or PRTL_UNICODE_STRING_BUFFER, as indicated by
SizeOfStruct
SizeOfStruct -
a rough type indicator of UnicodeStringOrUnicodeStringBuffer, to allow for overloading in C
LengthFunction -
computes a length for UnicodeStringOrUnicodeStringBuffer to be shortened too
Return Value:
STATUS_SUCCESS -
STATUS_INVALID_PARAMETER -
SizeOfStruct not one of the expected sizes
or LengthFunction is NULL
or UnicodeStringOrUnicodeStringBuffer is NULL
--*/
{
PUNICODE_STRING UnicodeString = NULL;
PRTL_UNICODE_STRING_BUFFER UnicodeStringBuffer = NULL;
NTSTATUS Status = STATUS_SUCCESS;
ULONG Length = 0;
if (!RTL_SOFT_VERIFY(UnicodeStringOrUnicodeStringBuffer != NULL)) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (!RTL_SOFT_VERIFY(LengthFunction != NULL)) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (!RTL_SOFT_VERIFY(Flags == 0)) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
switch (SizeOfStruct)
{
default:
Status = STATUS_INVALID_PARAMETER;
goto Exit;
case sizeof(*UnicodeString):
UnicodeString = UnicodeStringOrUnicodeStringBuffer;
break;
case sizeof(*UnicodeStringBuffer):
UnicodeStringBuffer = UnicodeStringOrUnicodeStringBuffer;
UnicodeString = &UnicodeStringBuffer->String;
break;
}
Status = (*LengthFunction)(Flags, UnicodeString, &Length);
if (!NT_SUCCESS(Status)) {
goto Exit;
}
UnicodeString->Length = (USHORT)(Length * sizeof(UnicodeString->Buffer[0]));
if (UnicodeStringBuffer != NULL) {
RTL_NUL_TERMINATE_STRING(UnicodeString);
}
Status = STATUS_SUCCESS;
Exit:
KdPrintEx((
DPFLTR_SXS_ID, DPFLTR_LEVEL_STATUS(Status),
"%s(%d):%s(%wZ): 0x%08lx\n", __FILE__, __LINE__, __FUNCTION__, UnicodeString, Status));
return Status;
}
NTSTATUS
NTAPI
RtlNtPathNameToDosPathName(
IN ULONG Flags,
IN OUT PRTL_UNICODE_STRING_BUFFER Path,
OUT ULONG* Disposition OPTIONAL,
IN OUT PWSTR* FilePart OPTIONAL
)
{
NTSTATUS Status = STATUS_SUCCESS;
SIZE_T NtFilePartOffset = 0;
SIZE_T DosFilePartOffset = 0;
BOOLEAN Unc = FALSE;
const static UNICODE_STRING DosUncPrefix = RTL_CONSTANT_STRING(L"\\\\");
PCUNICODE_STRING NtPrefix = NULL;
PCUNICODE_STRING DosPrefix = NULL;
RTL_STRING_LENGTH_TYPE Cch = 0;
if (ARGUMENT_PRESENT(Disposition)) {
*Disposition = 0;
}
if ( !RTL_SOFT_VERIFY(Path != NULL)
|| !RTL_SOFT_VERIFY(Flags == 0)
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
if (ARGUMENT_PRESENT(FilePart) && *FilePart != NULL) {
NtFilePartOffset = *FilePart - Path->String.Buffer;
if (!RTL_SOFT_VERIFY(NtFilePartOffset < RTL_STRING_GET_LENGTH_CHARS(&Path->String))
) {
Status = STATUS_INVALID_PARAMETER;
goto Exit;
}
}
if (RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpDosDevicesUncPrefix), &Path->String, TRUE)
) {
NtPrefix = &RtlpDosDevicesUncPrefix;
DosPrefix = &DosUncPrefix;
if (ARGUMENT_PRESENT(Disposition)) {
*Disposition = RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_UNC;
}
}
else if (RtlPrefixUnicodeString(RTL_CONST_CAST(PUNICODE_STRING)(&RtlpDosDevicesPrefix), &Path->String, TRUE)
) {
NtPrefix = &RtlpDosDevicesPrefix;
DosPrefix = &RtlpEmptyString;
if (ARGUMENT_PRESENT(Disposition)) {
*Disposition = RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_DRIVE;
}
}
else {
//
// It is not recognizably an Nt path produced by RtlDosPathNameToNtPathName_U.
//
if (ARGUMENT_PRESENT(Disposition)) {
RTL_PATH_TYPE PathType = RtlDetermineDosPathNameType_Ustr(&Path->String);
switch (PathType) {
case RtlPathTypeUnknown:
case RtlPathTypeRooted: // NT paths are identified as this
*Disposition = RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_AMBIGUOUS;
break;
//
// "already" dospaths, but not gotten from this function, let's
// give a less good disposition
//
case RtlPathTypeDriveRelative:
case RtlPathTypeRelative:
*Disposition = RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_AMBIGUOUS;
break;
// these are pretty clearly dospaths already
case RtlPathTypeUncAbsolute:
case RtlPathTypeDriveAbsolute:
case RtlPathTypeLocalDevice: // "\\?\" or "\\.\" or "\\?\blah" or "\\.\blah"
case RtlPathTypeRootLocalDevice: // "\\?" or "\\."
*Disposition = RTL_NT_PATH_NAME_TO_DOS_PATH_NAME_ALREADY_DOS;
break;
}
}
goto Exit;
}
Cch =
RTL_STRING_GET_LENGTH_CHARS(&Path->String)
+ RTL_STRING_GET_LENGTH_CHARS(DosPrefix)
- RTL_STRING_GET_LENGTH_CHARS(NtPrefix);
Status =
RtlEnsureUnicodeStringBufferSizeChars(Path, Cch);
if (!NT_SUCCESS(Status)) {
goto Exit;
}
//
// overlapping buffer shuffle...careful.
//
RtlMoveMemory(
Path->String.Buffer + RTL_STRING_GET_LENGTH_CHARS(DosPrefix),
Path->String.Buffer + RTL_STRING_GET_LENGTH_CHARS(NtPrefix),
Path->String.Length - NtPrefix->Length
);
RtlMoveMemory(
Path->String.Buffer,
DosPrefix->Buffer,
DosPrefix->Length
);
Path->String.Length = Cch * sizeof(Path->String.Buffer[0]);
RTL_NUL_TERMINATE_STRING(&Path->String);
if (NtFilePartOffset != 0) {
// review/test..
*FilePart = Path->String.Buffer + (NtFilePartOffset - RTL_STRING_GET_LENGTH_CHARS(NtPrefix) + RTL_STRING_GET_LENGTH_CHARS(DosPrefix));
}
Status = STATUS_SUCCESS;
Exit:
KdPrintEx((
DPFLTR_SXS_ID, DPFLTR_LEVEL_STATUS(Status),
"%s(%d):%s(%wZ): 0x%08lx\n", __FILE__, __LINE__, __FUNCTION__, Path, Status));
return Status;
}