reactos/lib/rtl/path.c
David Quintana 9c589bb131 Sync with trunk r62754.
svn path=/branches/shell-experiments/; revision=62756
2014-04-15 23:01:15 +00:00

2652 lines
90 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS system libraries
* FILE: lib/rtl/path.c
* PURPOSE: Path and current directory functions
* PROGRAMMERS: Wine team
* Thomas Weidenmueller
* Gunnar Dalsnes
* Alex Ionescu (alex.ionescu@reactos.org)
* Pierre Schweitzer (pierre@reactos.org)
*/
/* INCLUDES *******************************************************************/
#include <rtl.h>
#define NDEBUG
#include <debug.h>
/* DEFINITONS and MACROS ******************************************************/
#define MAX_PFX_SIZE 16
#define IS_PATH_SEPARATOR(x) (((x)==L'\\')||((x)==L'/'))
#define RTL_CURDIR_IS_REMOVABLE 0x1
#define RTL_CURDIR_DROP_OLD_HANDLE 0x2
#define RTL_CURDIR_ALL_FLAGS (RTL_CURDIR_DROP_OLD_HANDLE | RTL_CURDIR_IS_REMOVABLE) // 0x3
C_ASSERT(RTL_CURDIR_ALL_FLAGS == OBJ_HANDLE_TAGBITS);
/* GLOBALS ********************************************************************/
static const WCHAR DeviceRootW[] = L"\\\\.\\";
const UNICODE_STRING DeviceRootString = RTL_CONSTANT_STRING(L"\\\\.\\");
const UNICODE_STRING RtlpDosDevicesUncPrefix = RTL_CONSTANT_STRING(L"\\??\\UNC\\");
const UNICODE_STRING RtlpWin32NtRootSlash = RTL_CONSTANT_STRING(L"\\\\?\\");
const UNICODE_STRING RtlpDosSlashCONDevice = RTL_CONSTANT_STRING(L"\\\\.\\CON");
const UNICODE_STRING RtlpDosDevicesPrefix = RTL_CONSTANT_STRING(L"\\??\\");
const UNICODE_STRING RtlpDosLPTDevice = RTL_CONSTANT_STRING(L"LPT");
const UNICODE_STRING RtlpDosCOMDevice = RTL_CONSTANT_STRING(L"COM");
const UNICODE_STRING RtlpDosPRNDevice = RTL_CONSTANT_STRING(L"PRN");
const UNICODE_STRING RtlpDosAUXDevice = RTL_CONSTANT_STRING(L"AUX");
const UNICODE_STRING RtlpDosCONDevice = RTL_CONSTANT_STRING(L"CON");
const UNICODE_STRING RtlpDosNULDevice = RTL_CONSTANT_STRING(L"NUL");
PRTLP_CURDIR_REF RtlpCurDirRef;
/* PRIVATE FUNCTIONS **********************************************************/
RTL_PATH_TYPE
NTAPI
RtlDetermineDosPathNameType_Ustr(IN PCUNICODE_STRING PathString)
{
PWCHAR Path;
ULONG Chars;
Path = PathString->Buffer;
Chars = PathString->Length / sizeof(WCHAR);
/* Return if there are no characters */
if (!Chars) return RtlPathTypeRelative;
/*
* The algorithm is similar to RtlDetermineDosPathNameType_U but here we
* actually check for the path length before touching the characters
*/
if (IS_PATH_SEPARATOR(Path[0]))
{
if ((Chars < 2) || !(IS_PATH_SEPARATOR(Path[1]))) return RtlPathTypeRooted; /* \x */
if ((Chars < 3) || ((Path[2] != L'.') && (Path[2] != L'?'))) return RtlPathTypeUncAbsolute;/* \\x */
if ((Chars >= 4) && (IS_PATH_SEPARATOR(Path[3]))) return RtlPathTypeLocalDevice; /* \\.\x or \\?\x */
if (Chars != 3) return RtlPathTypeUncAbsolute; /* \\.x or \\?x */
return RtlPathTypeRootLocalDevice; /* \\. or \\? */
}
else
{
if ((Chars < 2) || (Path[1] != L':')) return RtlPathTypeRelative; /* x */
if ((Chars < 3) || !(IS_PATH_SEPARATOR(Path[2]))) return RtlPathTypeDriveRelative; /* x: */
return RtlPathTypeDriveAbsolute; /* x:\ */
}
}
ULONG
NTAPI
RtlIsDosDeviceName_Ustr(IN PCUNICODE_STRING PathString)
{
UNICODE_STRING PathCopy;
PWCHAR Start, End;
USHORT PathChars, ColonCount = 0;
USHORT ReturnOffset = 0, ReturnLength, OriginalLength;
WCHAR c;
/* Validate the input */
if (!PathString) return 0;
/* Check what type of path this is */
switch (RtlDetermineDosPathNameType_Ustr(PathString))
{
/* Fail for UNC or unknown paths */
case RtlPathTypeUnknown:
case RtlPathTypeUncAbsolute:
return 0;
/* Make special check for the CON device */
case RtlPathTypeLocalDevice:
if (RtlEqualUnicodeString(PathString, &RtlpDosSlashCONDevice, TRUE))
{
/* This should return 0x80006 */
return MAKELONG(RtlpDosCONDevice.Length, DeviceRootString.Length);
}
return 0;
default:
break;
}
/* Make a copy of the string */
PathCopy = *PathString;
OriginalLength = PathString->Length;
/* Return if there's no characters */
PathChars = PathCopy.Length / sizeof(WCHAR);
if (!PathChars) return 0;
/* Check for drive path and truncate */
if (PathCopy.Buffer[PathChars - 1] == L':')
{
/* Fixup the lengths */
PathCopy.Length -= sizeof(WCHAR);
if (!--PathChars) return 0;
/* Remember this for later */
ColonCount = 1;
}
/* Check for extension or space, and truncate */
do
{
/* Stop if we hit something else than a space or period */
c = PathCopy.Buffer[PathChars - 1];
if ((c != L'.') && (c != L' ')) break;
/* Fixup the lengths */
PathCopy.Length -= sizeof(WCHAR);
/* Remember this for later */
ColonCount++;
} while (--PathChars);
/* Anything still left? */
if (PathChars)
{
/* Loop from the end */
for (End = &PathCopy.Buffer[PathChars - 1];
End >= PathCopy.Buffer;
--End)
{
/* Check if the character is a path or drive separator */
c = *End;
if (IS_PATH_SEPARATOR(c) || ((c == L':') && (End == PathCopy.Buffer + 1)))
{
/* Get the next lower case character */
End++;
c = RtlDowncaseUnicodeChar(*End);
/* Check if it's a DOS device (LPT, COM, PRN, AUX, or NUL) */
if ((End < &PathCopy.Buffer[OriginalLength / sizeof(WCHAR)]) &&
((c == L'l') || (c == L'c') || (c == L'p') || (c == L'a') || (c == L'n')))
{
/* Calculate the offset */
ReturnOffset = (USHORT)((PCHAR)End - (PCHAR)PathCopy.Buffer);
/* Build the final string */
PathCopy.Length = OriginalLength - ReturnOffset - (ColonCount * sizeof(WCHAR));
PathCopy.Buffer = End;
/* Save new amount of chars in the path */
PathChars = PathCopy.Length / sizeof(WCHAR);
break;
}
else
{
return 0;
}
}
}
/* Get the next lower case character and check if it's a DOS device */
c = RtlDowncaseUnicodeChar(*PathCopy.Buffer);
if ((c != L'l') && (c != L'c') && (c != L'p') && (c != L'a') && (c != L'n'))
{
/* Not LPT, COM, PRN, AUX, or NUL */
return 0;
}
}
/* Now skip past any extra extension or drive letter characters */
Start = PathCopy.Buffer;
End = &Start[PathChars];
while (Start < End)
{
c = *Start;
if ((c == L'.') || (c == L':')) break;
Start++;
}
/* And then go backwards to get rid of spaces */
while ((Start > PathCopy.Buffer) && (Start[-1] == L' ')) --Start;
/* Finally see how many characters are left, and that's our size */
PathChars = (USHORT)(Start - PathCopy.Buffer);
PathCopy.Length = PathChars * sizeof(WCHAR);
/* Check if this is a COM or LPT port, which has a digit after it */
if ((PathChars == 4) &&
(iswdigit(PathCopy.Buffer[3]) && (PathCopy.Buffer[3] != L'0')))
{
/* Don't compare the number part, just check for LPT or COM */
PathCopy.Length -= sizeof(WCHAR);
if ((RtlEqualUnicodeString(&PathCopy, &RtlpDosLPTDevice, TRUE)) ||
(RtlEqualUnicodeString(&PathCopy, &RtlpDosCOMDevice, TRUE)))
{
/* Found it */
ReturnLength = sizeof(L"COM1") - sizeof(WCHAR);
return MAKELONG(ReturnLength, ReturnOffset);
}
}
else if ((PathChars == 3) &&
((RtlEqualUnicodeString(&PathCopy, &RtlpDosPRNDevice, TRUE)) ||
(RtlEqualUnicodeString(&PathCopy, &RtlpDosAUXDevice, TRUE)) ||
(RtlEqualUnicodeString(&PathCopy, &RtlpDosNULDevice, TRUE)) ||
(RtlEqualUnicodeString(&PathCopy, &RtlpDosCONDevice, TRUE))))
{
/* Otherwise this was something like AUX, NUL, PRN, or CON */
ReturnLength = sizeof(L"AUX") - sizeof(WCHAR);
return MAKELONG(ReturnLength, ReturnOffset);
}
/* Otherwise, this is not a valid DOS device */
return 0;
}
NTSTATUS
NTAPI
RtlpCheckDeviceName(IN PUNICODE_STRING FileName,
IN ULONG Length,
OUT PBOOLEAN NameInvalid)
{
PWCHAR Buffer;
NTSTATUS Status;
/* Allocate a large enough buffer */
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, FileName->Length);
if (Buffer)
{
/* Assume failure */
*NameInvalid = TRUE;
/* Copy the filename */
RtlCopyMemory(Buffer, FileName->Buffer, FileName->Length);
/* And add a dot at the end */
Buffer[Length / sizeof(WCHAR)] = L'.';
Buffer[(Length / sizeof(WCHAR)) + 1] = UNICODE_NULL;
/* Check if the file exists or not */
*NameInvalid = RtlDoesFileExists_U(Buffer) ? FALSE: TRUE;
/* Get rid of the buffer now */
Status = RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
}
else
{
/* Assume the name is ok, but fail the call */
*NameInvalid = FALSE;
Status = STATUS_NO_MEMORY;
}
/* Return the status */
return Status;
}
/******************************************************************
* RtlpCollapsePath (from WINE)
*
* Helper for RtlGetFullPathName_U
*
* 1) Converts slashes into backslashes and gets rid of duplicated ones;
* 2) Gets rid of . and .. components in the path.
*
* Returns the full path length without its terminating NULL character.
*/
static ULONG
RtlpCollapsePath(PWSTR Path, /* ULONG PathBufferSize, ULONG PathLength, */ ULONG mark, BOOLEAN SkipTrailingPathSeparators)
{
PWSTR p, next;
// FIXME: Do not suppose NULL-terminated strings!!
ULONG PathLength = wcslen(Path);
PWSTR EndBuffer = Path + PathLength; // Path + PathBufferSize / sizeof(WCHAR);
PWSTR EndPath;
/* Convert slashes into backslashes */
for (p = Path; *p; p++)
{
if (*p == L'/') *p = L'\\';
}
/* Collapse duplicate backslashes */
next = Path + max( 1, mark );
for (p = next; *p; p++)
{
if (*p != L'\\' || next[-1] != L'\\') *next++ = *p;
}
*next = UNICODE_NULL;
EndPath = next;
p = Path + mark;
while (*p)
{
if (*p == L'.')
{
switch (p[1])
{
case UNICODE_NULL: /* final . */
if (p > Path + mark) p--;
*p = UNICODE_NULL;
EndPath = p;
continue;
case L'\\': /* .\ component */
next = p + 2;
// ASSERT(EndPath - next == wcslen(next));
RtlMoveMemory(p, next, (EndPath - next + 1) * sizeof(WCHAR));
EndPath -= (next - p);
continue;
case L'.':
if (p[2] == L'\\') /* ..\ component */
{
next = p + 3;
if (p > Path + mark)
{
p--;
while (p > Path + mark && p[-1] != L'\\') p--;
}
// ASSERT(EndPath - next == wcslen(next));
RtlMoveMemory(p, next, (EndPath - next + 1) * sizeof(WCHAR));
EndPath -= (next - p);
continue;
}
else if (p[2] == UNICODE_NULL) /* final .. */
{
if (p > Path + mark)
{
p--;
while (p > Path + mark && p[-1] != L'\\') p--;
if (p > Path + mark) p--;
}
*p = UNICODE_NULL;
EndPath = p;
continue;
}
break;
}
}
/* Skip to the next component */
while (*p && *p != L'\\') p++;
if (*p == L'\\')
{
/* Remove last dot in previous dir name */
if (p > Path + mark && p[-1] == L'.')
{
// ASSERT(EndPath - p == wcslen(p));
RtlMoveMemory(p - 1, p, (EndPath - p + 1) * sizeof(WCHAR));
EndPath--;
}
else
{
p++;
}
}
}
/* Remove trailing backslashes if needed (after the UNC part if it exists) */
if (SkipTrailingPathSeparators)
{
while (p > Path + mark && IS_PATH_SEPARATOR(p[-1])) p--;
}
/* Remove trailing spaces and dots (for all the path) */
while (p > Path && (p[-1] == L' ' || p[-1] == L'.')) p--;
/*
* Zero-out the discarded buffer zone, starting just after
* the path string and going up to the end of the buffer.
* It also NULL-terminate the path string.
*/
ASSERT(EndBuffer >= p);
RtlZeroMemory(p, (EndBuffer - p + 1) * sizeof(WCHAR));
/* Return the real path length */
PathLength = (p - Path);
// ASSERT(PathLength == wcslen(Path));
return (PathLength * sizeof(WCHAR));
}
/******************************************************************
* RtlpSkipUNCPrefix (from WINE)
*
* Helper for RtlGetFullPathName_U
*
* Skips the \\share\dir part of a file name and returns the new position
* (which can point on the last backslash of "dir\").
*/
static SIZE_T
RtlpSkipUNCPrefix(PCWSTR FileNameBuffer)
{
PCWSTR UncPath = FileNameBuffer + 2;
DPRINT("RtlpSkipUNCPrefix(%S)\n", FileNameBuffer);
while (*UncPath && !IS_PATH_SEPARATOR(*UncPath)) UncPath++; /* share name */
while (IS_PATH_SEPARATOR(*UncPath)) UncPath++;
while (*UncPath && !IS_PATH_SEPARATOR(*UncPath)) UncPath++; /* dir name */
/* while (IS_PATH_SEPARATOR(*UncPath)) UncPath++; */
return (UncPath - FileNameBuffer);
}
NTSTATUS
NTAPI
RtlpApplyLengthFunction(IN ULONG Flags,
IN ULONG Type,
IN PVOID UnicodeStringOrUnicodeStringBuffer,
IN PVOID LengthFunction)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
NTAPI
RtlGetLengthWithoutLastFullDosOrNtPathElement(IN ULONG Flags,
IN PWCHAR Path,
OUT PULONG LengthOut)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
NTAPI
RtlComputePrivatizedDllName_U(IN PUNICODE_STRING DllName,
IN PUNICODE_STRING a2,
IN PUNICODE_STRING a3)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
}
ULONG
NTAPI
RtlGetFullPathName_Ustr(
_In_ PUNICODE_STRING FileName,
_In_ ULONG Size,
_Out_z_bytecap_(Size) PWSTR Buffer,
_Out_opt_ PCWSTR *ShortName,
_Out_opt_ PBOOLEAN InvalidName,
_Out_ RTL_PATH_TYPE *PathType)
{
NTSTATUS Status;
PWCHAR FileNameBuffer;
ULONG FileNameLength, FileNameChars, DosLength, DosLengthOffset, FullLength;
BOOLEAN SkipTrailingPathSeparators;
WCHAR c;
ULONG reqsize = 0;
PCWSTR ptr;
PCUNICODE_STRING CurDirName;
UNICODE_STRING EnvVarName, EnvVarValue;
WCHAR EnvVarNameBuffer[4];
ULONG PrefixCut = 0; // Where the path really starts (after the skipped prefix)
PWCHAR Prefix = NULL; // pointer to the string to be inserted as the new path prefix
ULONG PrefixLength = 0;
PWCHAR Source;
ULONG SourceLength;
/* For now, assume the name is valid */
DPRINT("Filename: %wZ\n", FileName);
DPRINT("Size and buffer: %lx %p\n", Size, Buffer);
if (InvalidName) *InvalidName = FALSE;
/* Handle initial path type and failure case */
*PathType = RtlPathTypeUnknown;
if ((FileName->Length == 0) || (FileName->Buffer[0] == UNICODE_NULL)) return 0;
/* Break filename into component parts */
FileNameBuffer = FileName->Buffer;
FileNameLength = FileName->Length;
FileNameChars = FileNameLength / sizeof(WCHAR);
/* Kill trailing spaces */
c = FileNameBuffer[FileNameChars - 1];
while ((FileNameLength != 0) && (c == L' '))
{
/* Keep going, ignoring the spaces */
FileNameLength -= sizeof(WCHAR);
if (FileNameLength != 0) c = FileNameBuffer[FileNameLength / sizeof(WCHAR) - 1];
}
/* Check if anything is left */
if (FileNameLength == 0) return 0;
/*
* Check whether we'll need to skip trailing path separators in the
* computed full path name. If the original file name already contained
* trailing separators, then we keep them in the full path name. On the
* other hand, if the original name didn't contain any trailing separators
* then we'll skip it in the full path name.
*/
SkipTrailingPathSeparators = !IS_PATH_SEPARATOR(FileNameBuffer[FileNameChars - 1]);
/* Check if this is a DOS name */
DosLength = RtlIsDosDeviceName_Ustr(FileName);
DPRINT("DOS length for filename: %lx %wZ\n", DosLength, FileName);
if (DosLength != 0)
{
/* Zero out the short name */
if (ShortName) *ShortName = NULL;
/* See comment for RtlIsDosDeviceName_Ustr if this is confusing... */
DosLengthOffset = HIWORD(DosLength);
DosLength = LOWORD(DosLength);
/* Do we have a DOS length, and does the caller want validity? */
if (InvalidName && (DosLengthOffset != 0))
{
/* Do the check */
Status = RtlpCheckDeviceName(FileName, DosLengthOffset, InvalidName);
/* If the check failed, or the name is invalid, fail here */
if (!NT_SUCCESS(Status)) return 0;
if (*InvalidName) return 0;
}
/* Add the size of the device root and check if it fits in the size */
FullLength = DosLength + DeviceRootString.Length;
if (FullLength < Size)
{
/* Add the device string */
RtlMoveMemory(Buffer, DeviceRootString.Buffer, DeviceRootString.Length);
/* Now add the DOS device name */
RtlMoveMemory((PCHAR)Buffer + DeviceRootString.Length,
(PCHAR)FileNameBuffer + DosLengthOffset,
DosLength);
/* Null terminate */
*(PWCHAR)((ULONG_PTR)Buffer + FullLength) = UNICODE_NULL;
return FullLength;
}
/* Otherwise, there's no space, so return the buffer size needed */
if ((FullLength + sizeof(UNICODE_NULL)) > UNICODE_STRING_MAX_BYTES) return 0;
return FullLength + sizeof(UNICODE_NULL);
}
/* Zero-out the destination buffer. FileName must be different from Buffer */
RtlZeroMemory(Buffer, Size);
/* Get the path type */
*PathType = RtlDetermineDosPathNameType_U(FileNameBuffer);
/**********************************************
** CODE REWRITING IS HAPPENING THERE **
**********************************************/
Source = FileNameBuffer;
SourceLength = FileNameLength;
EnvVarValue.Buffer = NULL;
/* Lock the PEB to get the current directory */
RtlAcquirePebLock();
CurDirName = &NtCurrentPeb()->ProcessParameters->CurrentDirectory.DosPath;
switch (*PathType)
{
case RtlPathTypeUncAbsolute: /* \\foo */
PrefixCut = RtlpSkipUNCPrefix(FileNameBuffer);
break;
case RtlPathTypeLocalDevice: /* \\.\foo */
PrefixCut = 4;
break;
case RtlPathTypeDriveAbsolute: /* c:\foo */
ASSERT(FileNameBuffer[1] == L':');
ASSERT(IS_PATH_SEPARATOR(FileNameBuffer[2]));
Prefix = FileNameBuffer;
PrefixLength = 3 * sizeof(WCHAR);
Source += 3;
SourceLength -= 3 * sizeof(WCHAR);
PrefixCut = 3;
break;
case RtlPathTypeDriveRelative: /* c:foo */
Source += 2;
SourceLength -= 2 * sizeof(WCHAR);
if (RtlUpcaseUnicodeChar(FileNameBuffer[0]) != RtlUpcaseUnicodeChar(CurDirName->Buffer[0]) ||
CurDirName->Buffer[1] != L':')
{
EnvVarNameBuffer[0] = L'=';
EnvVarNameBuffer[1] = FileNameBuffer[0];
EnvVarNameBuffer[2] = L':';
EnvVarNameBuffer[3] = UNICODE_NULL;
EnvVarName.Length = 3 * sizeof(WCHAR);
EnvVarName.MaximumLength = EnvVarName.Length + sizeof(WCHAR);
EnvVarName.Buffer = EnvVarNameBuffer;
// FIXME: Is it possible to use the user-given buffer ?
// RtlInitEmptyUnicodeString(&EnvVarValue, NULL, Size);
EnvVarValue.Length = 0;
EnvVarValue.MaximumLength = (USHORT)Size;
EnvVarValue.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, Size);
if (EnvVarValue.Buffer == NULL)
{
Prefix = NULL;
PrefixLength = 0;
goto Quit;
}
Status = RtlQueryEnvironmentVariable_U(NULL, &EnvVarName, &EnvVarValue);
switch (Status)
{
case STATUS_SUCCESS:
/*
* (From Wine)
* FIXME: Win2k seems to check that the environment
* variable actually points to an existing directory.
* If not, root of the drive is used (this seems also
* to be the only place in RtlGetFullPathName that the
* existence of a part of a path is checked).
*/
EnvVarValue.Buffer[EnvVarValue.Length / sizeof(WCHAR)] = L'\\';
Prefix = EnvVarValue.Buffer;
PrefixLength = EnvVarValue.Length + sizeof(WCHAR); /* Append trailing '\\' */
break;
case STATUS_BUFFER_TOO_SMALL:
reqsize = EnvVarValue.Length + SourceLength + sizeof(UNICODE_NULL);
goto Quit;
default:
DPRINT1("RtlQueryEnvironmentVariable_U returned 0x%08lx\n", Status);
EnvVarNameBuffer[0] = FileNameBuffer[0];
EnvVarNameBuffer[1] = L':';
EnvVarNameBuffer[2] = L'\\';
EnvVarNameBuffer[3] = UNICODE_NULL;
Prefix = EnvVarNameBuffer;
PrefixLength = 3 * sizeof(WCHAR);
RtlFreeHeap(RtlGetProcessHeap(), 0, EnvVarValue.Buffer);
EnvVarValue.Buffer = NULL;
break;
}
PrefixCut = 3;
break;
}
/* Fall through */
DPRINT("RtlPathTypeDriveRelative - Using fall-through to RtlPathTypeRelative\n");
case RtlPathTypeRelative: /* foo */
Prefix = CurDirName->Buffer;
PrefixLength = CurDirName->Length;
if (CurDirName->Buffer[1] != L':')
{
PrefixCut = RtlpSkipUNCPrefix(CurDirName->Buffer);
}
else
{
PrefixCut = 3;
}
break;
case RtlPathTypeRooted: /* \xxx */
if (CurDirName->Buffer[1] == L':')
{
// The path starts with "C:\"
ASSERT(CurDirName->Buffer[1] == L':');
ASSERT(IS_PATH_SEPARATOR(CurDirName->Buffer[2]));
Prefix = CurDirName->Buffer;
PrefixLength = 3 * sizeof(WCHAR); // Skip "C:\"
PrefixCut = 3; // Source index location incremented of + 3
}
else
{
PrefixCut = RtlpSkipUNCPrefix(CurDirName->Buffer);
PrefixLength = PrefixCut * sizeof(WCHAR);
Prefix = CurDirName->Buffer;
}
break;
case RtlPathTypeRootLocalDevice: /* \\. */
Prefix = DeviceRootString.Buffer;
PrefixLength = DeviceRootString.Length;
Source += 3;
SourceLength -= 3 * sizeof(WCHAR);
PrefixCut = 4;
break;
case RtlPathTypeUnknown:
goto Quit;
}
/* Do we have enough space for storing the full path? */
reqsize = PrefixLength;
if (reqsize + SourceLength + sizeof(WCHAR) > Size)
{
/* Not enough space, return needed size (including terminating '\0') */
reqsize += SourceLength + sizeof(WCHAR);
goto Quit;
}
/*
* Build the full path
*/
/* Copy the path's prefix */
if (PrefixLength) RtlMoveMemory(Buffer, Prefix, PrefixLength);
/* Copy the remaining part of the path */
RtlMoveMemory(Buffer + PrefixLength / sizeof(WCHAR), Source, SourceLength + sizeof(WCHAR));
/* Some cleanup */
Prefix = NULL;
if (EnvVarValue.Buffer)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, EnvVarValue.Buffer);
EnvVarValue.Buffer = NULL;
}
/*
* Finally, put the path in canonical form (remove redundant . and ..,
* (back)slashes...) and retrieve the length of the full path name
* (without its terminating null character) (in chars).
*/
reqsize = RtlpCollapsePath(Buffer, /* Size, reqsize, */ PrefixCut, SkipTrailingPathSeparators);
/* Find the file part, which is present after the last path separator */
if (ShortName)
{
ptr = wcsrchr(Buffer, L'\\');
if (ptr) ++ptr; // Skip it
/*
* For UNC paths, the file part is after the \\share\dir part of the path.
*/
PrefixCut = (*PathType == RtlPathTypeUncAbsolute ? PrefixCut : 3);
if (ptr && *ptr && (ptr >= Buffer + PrefixCut))
{
*ShortName = ptr;
}
else
{
/* Zero-out the short name */
*ShortName = NULL;
}
}
Quit:
/* Release PEB lock */
RtlReleasePebLock();
return reqsize;
}
NTSTATUS
NTAPI
RtlpWin32NTNameToNtPathName_U(IN PUNICODE_STRING DosPath,
OUT PUNICODE_STRING NtPath,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
ULONG DosLength;
PWSTR NewBuffer, p;
/* Validate the input */
if (!DosPath) return STATUS_OBJECT_NAME_INVALID;
/* Validate the DOS length */
DosLength = DosPath->Length;
if (DosLength >= UNICODE_STRING_MAX_BYTES) return STATUS_NAME_TOO_LONG;
/* Make space for the new path */
NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
0,
DosLength + sizeof(UNICODE_NULL));
if (!NewBuffer) return STATUS_NO_MEMORY;
/* Copy the prefix, and then the rest of the DOS path, and NULL-terminate */
RtlCopyMemory(NewBuffer, RtlpDosDevicesPrefix.Buffer, RtlpDosDevicesPrefix.Length);
RtlCopyMemory((PCHAR)NewBuffer + RtlpDosDevicesPrefix.Length,
DosPath->Buffer + RtlpDosDevicesPrefix.Length / sizeof(WCHAR),
DosPath->Length - RtlpDosDevicesPrefix.Length);
NewBuffer[DosLength / sizeof(WCHAR)] = UNICODE_NULL;
/* Did the caller send a relative name? */
if (RelativeName)
{
/* Zero initialize it */
RtlInitEmptyUnicodeString(&RelativeName->RelativeName, NULL, 0);
RelativeName->ContainingDirectory = NULL;
RelativeName->CurDirRef = 0;
}
/* Did the caller request a partial name? */
if (PartName)
{
/* Loop from the back until we find a path separator */
p = &NewBuffer[DosLength / sizeof(WCHAR)];
while (--p > NewBuffer)
{
/* We found a path separator, move past it */
if (*p == OBJ_NAME_PATH_SEPARATOR)
{
++p;
break;
}
}
/* Check whether a separator was found and if something remains */
if ((p > NewBuffer) && *p)
{
/* What follows the path separator is the partial name */
*PartName = p;
}
else
{
/* The path ends with a path separator, no partial name */
*PartName = NULL;
}
}
/* Build the final NT path string */
NtPath->Buffer = NewBuffer;
NtPath->Length = (USHORT)DosLength;
NtPath->MaximumLength = (USHORT)DosLength + sizeof(UNICODE_NULL);
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
RtlpDosPathNameToRelativeNtPathName_Ustr(IN BOOLEAN HaveRelative,
IN PCUNICODE_STRING DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
WCHAR BigBuffer[MAX_PATH + 1];
PWCHAR PrefixBuffer, NewBuffer, Buffer;
ULONG MaxLength, PathLength, PrefixLength, PrefixCut, LengthChars, Length;
UNICODE_STRING CapturedDosName, PartNameString, FullPath;
BOOLEAN QuickPath;
RTL_PATH_TYPE InputPathType, BufferPathType;
NTSTATUS Status;
BOOLEAN NameInvalid;
PCURDIR CurrentDirectory;
/* Assume MAX_PATH for now */
DPRINT("Relative: %lx DosName: %wZ NtName: %p, PartName: %p, RelativeName: %p\n",
HaveRelative, DosName, NtName, PartName, RelativeName);
MaxLength = sizeof(BigBuffer);
/* Validate the input */
if (!DosName) return STATUS_OBJECT_NAME_INVALID;
/* Capture input string */
CapturedDosName = *DosName;
/* Check for the presence or absence of the NT prefix "\\?\" form */
// if (!RtlPrefixUnicodeString(&RtlpWin32NtRootSlash, &CapturedDosName, FALSE))
if ((CapturedDosName.Length <= RtlpWin32NtRootSlash.Length) ||
(CapturedDosName.Buffer[0] != RtlpWin32NtRootSlash.Buffer[0]) ||
(CapturedDosName.Buffer[1] != RtlpWin32NtRootSlash.Buffer[1]) ||
(CapturedDosName.Buffer[2] != RtlpWin32NtRootSlash.Buffer[2]) ||
(CapturedDosName.Buffer[3] != RtlpWin32NtRootSlash.Buffer[3]))
{
/* NT prefix not present */
/* Quick path won't be used */
QuickPath = FALSE;
/* Use the static buffer */
Buffer = BigBuffer;
MaxLength += RtlpDosDevicesUncPrefix.Length;
/* Allocate a buffer to hold the path */
NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, MaxLength);
DPRINT("MaxLength: %lx\n", MaxLength);
if (!NewBuffer) return STATUS_NO_MEMORY;
}
else
{
/* NT prefix present */
/* Use the optimized path after acquiring the lock */
QuickPath = TRUE;
NewBuffer = NULL;
}
/* Lock the PEB and check if the quick path can be used */
RtlAcquirePebLock();
if (QuickPath)
{
/* Some simple fixups will get us the correct path */
DPRINT("Quick path\n");
Status = RtlpWin32NTNameToNtPathName_U(&CapturedDosName,
NtName,
PartName,
RelativeName);
/* Release the lock, we're done here */
RtlReleasePebLock();
return Status;
}
/* Call the main function to get the full path name and length */
PathLength = RtlGetFullPathName_Ustr(&CapturedDosName,
MAX_PATH * sizeof(WCHAR),
Buffer,
PartName,
&NameInvalid,
&InputPathType);
if ((NameInvalid) || !(PathLength) || (PathLength > (MAX_PATH * sizeof(WCHAR))))
{
/* Invalid name, fail */
DPRINT("Invalid name: %lx Path Length: %lx\n", NameInvalid, PathLength);
RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
RtlReleasePebLock();
return STATUS_OBJECT_NAME_INVALID;
}
/* Start by assuming the path starts with \??\ (DOS Devices Path) */
PrefixLength = RtlpDosDevicesPrefix.Length;
PrefixBuffer = RtlpDosDevicesPrefix.Buffer;
PrefixCut = 0;
/* Check where it really is */
BufferPathType = RtlDetermineDosPathNameType_U(Buffer);
DPRINT("Buffer: %S Type: %lx\n", Buffer, BufferPathType);
switch (BufferPathType)
{
/* It's actually a UNC path in \??\UNC\ */
case RtlPathTypeUncAbsolute:
PrefixLength = RtlpDosDevicesUncPrefix.Length;
PrefixBuffer = RtlpDosDevicesUncPrefix.Buffer;
PrefixCut = 2;
break;
case RtlPathTypeLocalDevice:
/* We made a good guess, go with it but skip the \??\ */
PrefixCut = 4;
break;
case RtlPathTypeDriveAbsolute:
case RtlPathTypeDriveRelative:
case RtlPathTypeRooted:
case RtlPathTypeRelative:
/* Our guess was good, roll with it */
break;
/* Nothing else is expected */
default:
ASSERT(FALSE);
}
/* Now copy the prefix and the buffer */
RtlCopyMemory(NewBuffer, PrefixBuffer, PrefixLength);
RtlCopyMemory((PCHAR)NewBuffer + PrefixLength,
Buffer + PrefixCut,
PathLength - (PrefixCut * sizeof(WCHAR)));
/* Compute the length */
Length = PathLength + PrefixLength - PrefixCut * sizeof(WCHAR);
LengthChars = Length / sizeof(WCHAR);
/* Setup the actual NT path string and terminate it */
NtName->Buffer = NewBuffer;
NtName->Length = (USHORT)Length;
NtName->MaximumLength = (USHORT)MaxLength;
NewBuffer[LengthChars] = UNICODE_NULL;
DPRINT("New buffer: %S\n", NewBuffer);
DPRINT("NT Name: %wZ\n", NtName);
/* Check if a partial name was requested */
if ((PartName) && (*PartName))
{
/* Convert to Unicode */
Status = RtlInitUnicodeStringEx(&PartNameString, *PartName);
if (NT_SUCCESS(Status))
{
/* Set the partial name */
*PartName = &NewBuffer[LengthChars - (PartNameString.Length / sizeof(WCHAR))];
}
else
{
/* Fail */
RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
RtlReleasePebLock();
return Status;
}
}
/* Check if a relative name was asked for */
if (RelativeName)
{
/* Setup the structure */
RtlInitEmptyUnicodeString(&RelativeName->RelativeName, NULL, 0);
RelativeName->ContainingDirectory = NULL;
RelativeName->CurDirRef = NULL;
/* Check if the input path itself was relative */
if (InputPathType == RtlPathTypeRelative)
{
/* Get current directory */
CurrentDirectory = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
if (CurrentDirectory->Handle)
{
Status = RtlInitUnicodeStringEx(&FullPath, Buffer);
if (!NT_SUCCESS(Status))
{
RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
RtlReleasePebLock();
return Status;
}
/* If current directory is bigger than full path, there's no way */
if (CurrentDirectory->DosPath.Length > FullPath.Length)
{
RtlReleasePebLock();
return Status;
}
/* File is in current directory */
if (RtlEqualUnicodeString(&FullPath, &CurrentDirectory->DosPath, TRUE))
{
/* Make relative name string */
RelativeName->RelativeName.Buffer = (PWSTR)((ULONG_PTR)NewBuffer + PrefixLength + FullPath.Length - PrefixCut * sizeof(WCHAR));
RelativeName->RelativeName.Length = (USHORT)(PathLength - FullPath.Length);
/* If relative name starts with \, skip it */
if (RelativeName->RelativeName.Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
{
RelativeName->RelativeName.Buffer++;
RelativeName->RelativeName.Length -= sizeof(WCHAR);
}
RelativeName->RelativeName.MaximumLength = RelativeName->RelativeName.Length;
DPRINT("RelativeName: %wZ\n", &(RelativeName->RelativeName));
if (!HaveRelative)
{
RelativeName->ContainingDirectory = CurrentDirectory->Handle;
return Status;
}
/* Give back current directory data & reference counter */
RelativeName->CurDirRef = RtlpCurDirRef;
if (RelativeName->CurDirRef)
{
InterlockedIncrement(&RtlpCurDirRef->RefCount);
}
RelativeName->ContainingDirectory = CurrentDirectory->Handle;
}
}
}
}
/* Done */
RtlReleasePebLock();
return STATUS_SUCCESS;
}
NTSTATUS
NTAPI
RtlpDosPathNameToRelativeNtPathName_U(IN BOOLEAN HaveRelative,
IN PCWSTR DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
NTSTATUS Status;
UNICODE_STRING NameString;
/* Create the unicode name */
Status = RtlInitUnicodeStringEx(&NameString, DosName);
if (NT_SUCCESS(Status))
{
/* Call the unicode function */
Status = RtlpDosPathNameToRelativeNtPathName_Ustr(HaveRelative,
&NameString,
NtName,
PartName,
RelativeName);
}
/* Return status */
return Status;
}
BOOLEAN
NTAPI
RtlDosPathNameToRelativeNtPathName_Ustr(IN PCUNICODE_STRING DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
/* Call the internal function */
ASSERT(RelativeName);
return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_Ustr(TRUE,
DosName,
NtName,
PartName,
RelativeName));
}
BOOLEAN
NTAPI
RtlDoesFileExists_UstrEx(IN PCUNICODE_STRING FileName,
IN BOOLEAN SucceedIfBusy)
{
BOOLEAN Result;
RTL_RELATIVE_NAME_U RelativeName;
UNICODE_STRING NtPathName;
PVOID Buffer;
OBJECT_ATTRIBUTES ObjectAttributes;
NTSTATUS Status;
FILE_BASIC_INFORMATION BasicInformation;
/* Get the NT Path */
Result = RtlDosPathNameToRelativeNtPathName_Ustr(FileName,
&NtPathName,
NULL,
&RelativeName);
if (!Result) return FALSE;
/* Save the buffer */
Buffer = NtPathName.Buffer;
/* Check if we have a relative name */
if (RelativeName.RelativeName.Length)
{
/* Use it */
NtPathName = RelativeName.RelativeName;
}
else
{
/* Otherwise ignore it */
RelativeName.ContainingDirectory = NULL;
}
/* Initialize the object attributes */
InitializeObjectAttributes(&ObjectAttributes,
&NtPathName,
OBJ_CASE_INSENSITIVE,
RelativeName.ContainingDirectory,
NULL);
/* Query the attributes and free the buffer now */
Status = ZwQueryAttributesFile(&ObjectAttributes, &BasicInformation);
RtlReleaseRelativeName(&RelativeName);
RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
/* Check if we failed */
if (!NT_SUCCESS(Status))
{
/* Check if we failed because the file is in use */
if ((Status == STATUS_SHARING_VIOLATION) ||
(Status == STATUS_ACCESS_DENIED))
{
/* Check if the caller wants this to be considered OK */
Result = SucceedIfBusy ? TRUE : FALSE;
}
else
{
/* A failure because the file didn't exist */
Result = FALSE;
}
}
else
{
/* The file exists */
Result = TRUE;
}
/* Return the result */
return Result;
}
BOOLEAN
NTAPI
RtlDoesFileExists_UStr(IN PUNICODE_STRING FileName)
{
/* Call the updated API */
return RtlDoesFileExists_UstrEx(FileName, TRUE);
}
BOOLEAN
NTAPI
RtlDoesFileExists_UEx(IN PCWSTR FileName,
IN BOOLEAN SucceedIfBusy)
{
UNICODE_STRING NameString;
/* Create the unicode name*/
if (NT_SUCCESS(RtlInitUnicodeStringEx(&NameString, FileName)))
{
/* Call the unicode function */
return RtlDoesFileExists_UstrEx(&NameString, SucceedIfBusy);
}
/* Fail */
return FALSE;
}
/* PUBLIC FUNCTIONS ***********************************************************/
/*
* @implemented
*/
VOID
NTAPI
RtlReleaseRelativeName(IN PRTL_RELATIVE_NAME_U RelativeName)
{
/* Check if a directory reference was grabbed */
if (RelativeName->CurDirRef)
{
/* Decrease reference count */
if (!InterlockedDecrement(&RelativeName->CurDirRef->RefCount))
{
/* If no one uses it any longer, close handle & free */
NtClose(RelativeName->CurDirRef->Handle);
RtlFreeHeap(RtlGetProcessHeap(), 0, RelativeName->CurDirRef);
}
RelativeName->CurDirRef = NULL;
}
}
/*
* @implemented
*/
ULONG
NTAPI
RtlGetLongestNtPathLength(VOID)
{
/*
* The longest NT path is a DOS path that actually sits on a UNC path (ie:
* a mapped network drive), which is accessed through the DOS Global?? path.
* This is, and has always been equal to, 269 characters, except in Wine
* which claims this is 277. Go figure.
*/
return MAX_PATH + RtlpDosDevicesUncPrefix.Length / sizeof(WCHAR) + sizeof(ANSI_NULL);
}
/*
* @implemented
* @note: the export is called RtlGetLengthWithoutTrailingPathSeperators
* (with a 'e' instead of a 'a' in "Seperators").
*/
NTSTATUS
NTAPI
RtlGetLengthWithoutTrailingPathSeparators(IN ULONG Flags,
IN PCUNICODE_STRING PathString,
OUT PULONG Length)
{
ULONG NumChars;
/* Parameters validation */
if (Length == NULL) return STATUS_INVALID_PARAMETER;
*Length = 0;
if (PathString == NULL) return STATUS_INVALID_PARAMETER;
/* No flags are supported yet */
if (Flags != 0) return STATUS_INVALID_PARAMETER;
NumChars = PathString->Length / sizeof(WCHAR);
/*
* Notice that we skip the last character, therefore:
* - if we have: "some/path/f" we test for: "some/path/"
* - if we have: "some/path/" we test for: "some/path"
* - if we have: "s" we test for: ""
* - if we have: "" then NumChars was already zero and we aren't there
*/
while (NumChars > 0 && IS_PATH_SEPARATOR(PathString->Buffer[NumChars - 1]))
{
--NumChars;
}
*Length = NumChars;
return STATUS_SUCCESS;
}
/*
* @implemented
*/
RTL_PATH_TYPE
NTAPI
RtlDetermineDosPathNameType_U(IN PCWSTR Path)
{
DPRINT("RtlDetermineDosPathNameType_U %S\n", Path);
/* Unlike the newer RtlDetermineDosPathNameType_U we assume 4 characters */
if (IS_PATH_SEPARATOR(Path[0]))
{
if (!IS_PATH_SEPARATOR(Path[1])) return RtlPathTypeRooted; /* \x */
if ((Path[2] != L'.') && (Path[2] != L'?')) return RtlPathTypeUncAbsolute;/* \\x */
if (IS_PATH_SEPARATOR(Path[3])) return RtlPathTypeLocalDevice; /* \\.\x or \\?\x */
if (Path[3]) return RtlPathTypeUncAbsolute; /* \\.x or \\?x */
return RtlPathTypeRootLocalDevice; /* \\. or \\? */
}
else
{
if (!(Path[0]) || (Path[1] != L':')) return RtlPathTypeRelative; /* x */
if (IS_PATH_SEPARATOR(Path[2])) return RtlPathTypeDriveAbsolute; /* x:\ */
return RtlPathTypeDriveRelative; /* x: */
}
}
/*
* @implemented
*/
ULONG
NTAPI
RtlIsDosDeviceName_U(IN PCWSTR Path)
{
UNICODE_STRING PathString;
NTSTATUS Status;
/* Build the string */
Status = RtlInitUnicodeStringEx(&PathString, Path);
if (!NT_SUCCESS(Status)) return 0;
/*
* Returns 0 if name is not valid DOS device name, or DWORD with
* offset in bytes to DOS device name from beginning of buffer in high word
* and size in bytes of DOS device name in low word
*/
return RtlIsDosDeviceName_Ustr(&PathString);
}
/*
* @implemented
*/
ULONG
NTAPI
RtlGetCurrentDirectory_U(
_In_ ULONG MaximumLength,
_Out_bytecap_(MaximumLength) PWSTR Buffer)
{
ULONG Length, Bytes;
PCURDIR CurDir;
PWSTR CurDirName;
DPRINT("RtlGetCurrentDirectory %lu %p\n", MaximumLength, Buffer);
/* Lock the PEB to get the current directory */
RtlAcquirePebLock();
CurDir = &NtCurrentPeb()->ProcessParameters->CurrentDirectory;
/* Get the buffer and character length */
CurDirName = CurDir->DosPath.Buffer;
Length = CurDir->DosPath.Length / sizeof(WCHAR);
ASSERT((CurDirName != NULL) && (Length > 0));
/*
* DosPath.Buffer should always have a trailing slash. There is an assert
* below which checks for this.
*
* This function either returns x:\ for a root (keeping the original buffer)
* or it returns x:\path\foo for a directory (replacing the trailing slash
* with a NULL.
*/
Bytes = Length * sizeof(WCHAR);
if ((Length <= 1) || (CurDirName[Length - 2] == L':'))
{
/* Check if caller does not have enough space */
if (MaximumLength <= Bytes)
{
/* Call has no space for it, fail, add the trailing slash */
RtlReleasePebLock();
return Bytes + sizeof(OBJ_NAME_PATH_SEPARATOR);
}
}
else
{
/* Check if caller does not have enough space */
if (MaximumLength < Bytes)
{
/* Call has no space for it, fail */
RtlReleasePebLock();
return Bytes;
}
}
/* Copy the buffer since we seem to have space */
RtlCopyMemory(Buffer, CurDirName, Bytes);
/* The buffer should end with a path separator */
ASSERT(Buffer[Length - 1] == OBJ_NAME_PATH_SEPARATOR);
/* Again check for our two cases (drive root vs path) */
if ((Length <= 1) || (Buffer[Length - 2] != L':'))
{
/* Replace the trailing slash with a null */
Buffer[Length - 1] = UNICODE_NULL;
--Length;
}
else
{
/* Append the null char since there's no trailing slash */
Buffer[Length] = UNICODE_NULL;
}
/* Release PEB lock */
RtlReleasePebLock();
DPRINT("CurrentDirectory %S\n", Buffer);
return Length * sizeof(WCHAR);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlSetCurrentDirectory_U(IN PUNICODE_STRING Path)
{
PCURDIR CurDir;
NTSTATUS Status;
RTL_PATH_TYPE PathType;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING FullPath, NtName;
PRTLP_CURDIR_REF OldCurDir = NULL;
OBJECT_ATTRIBUTES ObjectAttributes;
FILE_FS_DEVICE_INFORMATION FileFsDeviceInfo;
ULONG SavedLength, CharLength, FullPathLength;
HANDLE OldHandle = NULL, CurDirHandle = NULL, OldCurDirHandle = NULL;
DPRINT("RtlSetCurrentDirectory_U %wZ\n", Path);
/* Initialize for failure case */
RtlInitEmptyUnicodeString(&NtName, NULL, 0);
/* Can't set current directory on DOS device */
if (RtlIsDosDeviceName_Ustr(Path))
{
return STATUS_NOT_A_DIRECTORY;
}
/* Get current directory */
RtlAcquirePebLock();
CurDir = &NtCurrentPeb()->ProcessParameters->CurrentDirectory;
/* Check if we have to drop current handle */
if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_DROP_OLD_HANDLE)
{
OldHandle = CurDir->Handle;
CurDir->Handle = NULL;
}
/* Allocate a buffer for full path (using max possible length */
FullPath.Buffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, CurDir->DosPath.MaximumLength);
if (!FullPath.Buffer)
{
Status = STATUS_NO_MEMORY;
goto Leave;
}
/* Init string */
FullPath.Length = 0;
FullPath.MaximumLength = CurDir->DosPath.MaximumLength;
/* Get new directory full path */
FullPathLength = RtlGetFullPathName_Ustr(Path, FullPath.MaximumLength, FullPath.Buffer, NULL, NULL, &PathType);
if (!FullPathLength)
{
Status = STATUS_OBJECT_NAME_INVALID;
goto Leave;
}
SavedLength = FullPath.MaximumLength;
CharLength = FullPathLength / sizeof(WCHAR);
if (FullPathLength > FullPath.MaximumLength)
{
Status = STATUS_NAME_TOO_LONG;
goto Leave;
}
/* Translate it to NT name */
if (!RtlDosPathNameToNtPathName_U(FullPath.Buffer, &NtName, NULL, NULL))
{
Status = STATUS_OBJECT_NAME_INVALID;
goto Leave;
}
InitializeObjectAttributes(&ObjectAttributes, &NtName,
OBJ_CASE_INSENSITIVE | OBJ_INHERIT,
NULL, NULL);
/* If previous current directory was removable, then check it for dropping */
if (((ULONG_PTR)(CurDir->Handle) & RTL_CURDIR_ALL_FLAGS) == RTL_CURDIR_ALL_FLAGS)
{
/* Get back normal handle */
CurDirHandle = (HANDLE)((ULONG_PTR)(CurDir->Handle) & ~RTL_CURDIR_ALL_FLAGS);
CurDir->Handle = NULL;
/* Get device information */
Status = NtQueryVolumeInformationFile(CurDirHandle,
&IoStatusBlock,
&FileFsDeviceInfo,
sizeof(FileFsDeviceInfo),
FileFsDeviceInformation);
/* Retry without taking care of removable device */
if (!NT_SUCCESS(Status))
{
Status = RtlSetCurrentDirectory_U(Path);
goto Leave;
}
}
else
{
/* Open directory */
Status = NtOpenFile(&CurDirHandle,
SYNCHRONIZE | FILE_TRAVERSE,
&ObjectAttributes,
&IoStatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(Status)) goto Leave;
/* Get device information */
Status = NtQueryVolumeInformationFile(CurDirHandle,
&IoStatusBlock,
&FileFsDeviceInfo,
sizeof(FileFsDeviceInfo),
FileFsDeviceInformation);
if (!NT_SUCCESS(Status)) goto Leave;
}
/* If device is removable, mark handle */
if (FileFsDeviceInfo.Characteristics & FILE_REMOVABLE_MEDIA)
{
CurDirHandle = (HANDLE)((ULONG_PTR)CurDirHandle | RTL_CURDIR_IS_REMOVABLE);
}
FullPath.Length = (USHORT)FullPathLength;
/* If full path isn't \ terminated, do it */
if (FullPath.Buffer[CharLength - 1] != OBJ_NAME_PATH_SEPARATOR)
{
if ((CharLength + 1) * sizeof(WCHAR) > SavedLength)
{
Status = STATUS_NAME_TOO_LONG;
goto Leave;
}
FullPath.Buffer[CharLength] = OBJ_NAME_PATH_SEPARATOR;
FullPath.Buffer[CharLength + 1] = UNICODE_NULL;
FullPath.Length += sizeof(WCHAR);
}
/* If we have previous current directory with only us as reference, save it */
if (RtlpCurDirRef != NULL && RtlpCurDirRef->RefCount == 1)
{
OldCurDirHandle = RtlpCurDirRef->Handle;
}
else
{
/* Allocate new current directory struct saving previous one */
OldCurDir = RtlpCurDirRef;
RtlpCurDirRef = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(RTLP_CURDIR_REF));
if (!RtlpCurDirRef)
{
RtlpCurDirRef = OldCurDir;
OldCurDir = NULL;
Status = STATUS_NO_MEMORY;
goto Leave;
}
/* Set reference to 1 (us) */
RtlpCurDirRef->RefCount = 1;
}
/* Save new data */
CurDir->Handle = CurDirHandle;
RtlpCurDirRef->Handle = CurDirHandle;
CurDirHandle = NULL;
/* Copy full path */
RtlCopyMemory(CurDir->DosPath.Buffer, FullPath.Buffer, FullPath.Length + sizeof(WCHAR));
CurDir->DosPath.Length = FullPath.Length;
Status = STATUS_SUCCESS;
Leave:
RtlReleasePebLock();
if (FullPath.Buffer)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, FullPath.Buffer);
}
if (NtName.Buffer)
{
RtlFreeHeap(RtlGetProcessHeap(), 0, NtName.Buffer);
}
if (CurDirHandle) NtClose(CurDirHandle);
if (OldHandle) NtClose(OldHandle);
if (OldCurDirHandle) NtClose(OldCurDirHandle);
if (OldCurDir && InterlockedDecrement(&OldCurDir->RefCount) == 0)
{
NtClose(OldCurDir->Handle);
RtlFreeHeap(RtlGetProcessHeap(), 0, OldCurDir);
}
return Status;
}
/******************************************************************
* RtlGetFullPathName_U (NTDLL.@)
*
* Returns the number of bytes written to buffer (not including the
* terminating NULL) if the function succeeds, or the required number of bytes
* (including the terminating NULL) if the buffer is too small.
*
* file_part will point to the filename part inside buffer (except if we use
* DOS device name, in which case file_in_buf is NULL)
*
* @implemented
*/
/*
* @implemented
*/
ULONG
NTAPI
RtlGetFullPathName_U(
_In_ PCWSTR FileName,
_In_ ULONG Size,
_Out_z_bytecap_(Size) PWSTR Buffer,
_Out_opt_ PWSTR *ShortName)
{
NTSTATUS Status;
UNICODE_STRING FileNameString;
RTL_PATH_TYPE PathType;
/* Build the string */
Status = RtlInitUnicodeStringEx(&FileNameString, FileName);
if (!NT_SUCCESS(Status)) return 0;
/* Call the extended function */
return RtlGetFullPathName_Ustr(&FileNameString,
Size,
Buffer,
(PCWSTR*)ShortName,
NULL,
&PathType);
}
/*
* @implemented
*/
BOOLEAN
NTAPI
RtlDosPathNameToNtPathName_U(IN PCWSTR DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
/* Call the internal function */
return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(FALSE,
DosName,
NtName,
PartName,
RelativeName));
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlDosPathNameToNtPathName_U_WithStatus(IN PCWSTR DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
/* Call the internal function */
return RtlpDosPathNameToRelativeNtPathName_U(FALSE,
DosName,
NtName,
PartName,
RelativeName);
}
/*
* @implemented
*/
BOOLEAN
NTAPI
RtlDosPathNameToRelativeNtPathName_U(IN PCWSTR DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
/* Call the internal function */
ASSERT(RelativeName);
return NT_SUCCESS(RtlpDosPathNameToRelativeNtPathName_U(TRUE,
DosName,
NtName,
PartName,
RelativeName));
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlDosPathNameToRelativeNtPathName_U_WithStatus(IN PCWSTR DosName,
OUT PUNICODE_STRING NtName,
OUT PCWSTR *PartName,
OUT PRTL_RELATIVE_NAME_U RelativeName)
{
/* Call the internal function */
ASSERT(RelativeName);
return RtlpDosPathNameToRelativeNtPathName_U(TRUE,
DosName,
NtName,
PartName,
RelativeName);
}
/*
* @unimplemented
*/
NTSTATUS NTAPI
RtlNtPathNameToDosPathName(ULONG Unknown1, ULONG Unknown2, ULONG Unknown3, ULONG Unknown4)
{
DPRINT1("RtlNtPathNameToDosPathName: stub\n");
return STATUS_NOT_IMPLEMENTED;
}
/*
* @implemented
*/
ULONG
NTAPI
RtlDosSearchPath_U(IN PCWSTR Path,
IN PCWSTR FileName,
IN PCWSTR Extension,
IN ULONG Size,
IN PWSTR Buffer,
OUT PWSTR *PartName)
{
NTSTATUS Status;
ULONG ExtensionLength, Length, FileNameLength, PathLength;
UNICODE_STRING TempString;
PWCHAR NewBuffer, BufferStart;
PCWSTR p;
/* Check if this is an absolute path */
if (RtlDetermineDosPathNameType_U(FileName) != RtlPathTypeRelative)
{
/* Check if the file exists */
if (RtlDoesFileExists_UEx(FileName, TRUE))
{
/* Get the full name, which does the DOS lookup */
return RtlGetFullPathName_U(FileName, Size, Buffer, PartName);
}
/* Doesn't exist, so fail */
return 0;
}
/* Scan the filename */
p = FileName;
while (*p)
{
/* Looking for an extension */
if (*p == L'.')
{
/* No extension string needed -- it's part of the filename */
Extension = NULL;
break;
}
/* Next character */
p++;
}
/* Do we have an extension? */
if (!Extension)
{
/* Nope, don't worry about one */
ExtensionLength = 0;
}
else
{
/* Build a temporary string to get the extension length */
Status = RtlInitUnicodeStringEx(&TempString, Extension);
if (!NT_SUCCESS(Status)) return 0;
ExtensionLength = TempString.Length;
}
/* Build a temporary string to get the path length */
Status = RtlInitUnicodeStringEx(&TempString, Path);
if (!NT_SUCCESS(Status)) return 0;
PathLength = TempString.Length;
/* Build a temporary string to get the filename length */
Status = RtlInitUnicodeStringEx(&TempString, FileName);
if (!NT_SUCCESS(Status)) return 0;
FileNameLength = TempString.Length;
/* Allocate the buffer for the new string name */
NewBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
0,
FileNameLength +
ExtensionLength +
PathLength +
3 * sizeof(WCHAR));
if (!NewBuffer)
{
/* Fail the call */
DbgPrint("%s: Failing due to out of memory (RtlAllocateHeap failure)\n",
__FUNCTION__);
return 0;
}
/* Final loop to build the path */
while (TRUE)
{
/* Check if we have a valid character */
BufferStart = NewBuffer;
if (*Path)
{
/* Loop as long as there's no semicolon */
while (*Path != L';')
{
/* Copy the next character */
*BufferStart++ = *Path++;
if (!*Path) break;
}
/* We found a semi-colon, to stop path processing on this loop */
if (*Path == L';') ++Path;
}
/* Add a terminating slash if needed */
if ((BufferStart != NewBuffer) && (BufferStart[-1] != OBJ_NAME_PATH_SEPARATOR))
{
*BufferStart++ = OBJ_NAME_PATH_SEPARATOR;
}
/* Bail out if we reached the end */
if (!*Path) Path = NULL;
/* Copy the file name and check if an extension is needed */
RtlCopyMemory(BufferStart, FileName, FileNameLength);
if (ExtensionLength)
{
/* Copy the extension too */
RtlCopyMemory((PCHAR)BufferStart + FileNameLength,
Extension,
ExtensionLength + sizeof(WCHAR));
}
else
{
/* Just NULL-terminate */
*(PWCHAR)((PCHAR)BufferStart + FileNameLength) = UNICODE_NULL;
}
/* Now, does this file exist? */
if (RtlDoesFileExists_UEx(NewBuffer, FALSE))
{
/* Call the full-path API to get the length */
Length = RtlGetFullPathName_U(NewBuffer, Size, Buffer, PartName);
break;
}
/* If we got here, path doesn't exist, so fail the call */
Length = 0;
if (!Path) break;
}
/* Free the allocation and return the length */
RtlFreeHeap(RtlGetProcessHeap(), 0, NewBuffer);
return Length;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlGetFullPathName_UstrEx(IN PUNICODE_STRING FileName,
IN PUNICODE_STRING StaticString,
IN PUNICODE_STRING DynamicString,
IN PUNICODE_STRING *StringUsed,
IN PSIZE_T FilePartSize,
OUT PBOOLEAN NameInvalid,
OUT RTL_PATH_TYPE* PathType,
OUT PSIZE_T LengthNeeded)
{
NTSTATUS Status;
PWCHAR StaticBuffer;
PCWCH ShortName;
ULONG Length;
USHORT StaticLength;
UNICODE_STRING TempDynamicString;
/* Initialize all our locals */
ShortName = NULL;
StaticBuffer = NULL;
TempDynamicString.Buffer = NULL;
/* Initialize the input parameters */
if (StringUsed) *StringUsed = NULL;
if (LengthNeeded) *LengthNeeded = 0;
if (FilePartSize) *FilePartSize = 0;
/* Check for invalid parameters */
if ((DynamicString) && !(StringUsed) && (StaticString))
{
return STATUS_INVALID_PARAMETER;
}
/* Check if we did not get an input string */
if (!StaticString)
{
/* Allocate one */
StaticLength = MAX_PATH * sizeof(WCHAR);
StaticBuffer = RtlpAllocateStringMemory(MAX_PATH * sizeof(WCHAR), TAG_USTR);
if (!StaticBuffer) return STATUS_NO_MEMORY;
}
else
{
/* Use the one we received */
StaticBuffer = StaticString->Buffer;
StaticLength = StaticString->MaximumLength;
}
/* Call the lower-level function */
Length = RtlGetFullPathName_Ustr(FileName,
StaticLength,
StaticBuffer,
&ShortName,
NameInvalid,
PathType);
DPRINT("Length: %u StaticBuffer: %S\n", Length, StaticBuffer);
if (!Length)
{
/* Fail if it failed */
DbgPrint("%s(%d) - RtlGetFullPathName_Ustr() returned 0\n",
__FUNCTION__,
__LINE__);
Status = STATUS_OBJECT_NAME_INVALID;
goto Quickie;
}
/* Check if it fits inside our static string */
if ((StaticString) && (Length < StaticLength))
{
/* Set the final length */
StaticString->Length = (USHORT)Length;
/* Set the file part size */
if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
/* Return the static string if requested */
if (StringUsed) *StringUsed = StaticString;
/* We are done with success */
Status = STATUS_SUCCESS;
goto Quickie;
}
/* Did we not have an input dynamic string ?*/
if (!DynamicString)
{
/* Return the length we need */
if (LengthNeeded) *LengthNeeded = Length;
/* And fail such that the caller can try again */
Status = STATUS_BUFFER_TOO_SMALL;
goto Quickie;
}
/* Check if it fits in our static buffer */
if ((StaticBuffer) && (Length < StaticLength))
{
/* NULL-terminate it */
StaticBuffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
/* Set the settings for the dynamic string the caller sent */
DynamicString->MaximumLength = StaticLength;
DynamicString->Length = (USHORT)Length;
DynamicString->Buffer = StaticBuffer;
/* Set the part size */
if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticBuffer) : 0;
/* Return the dynamic string if requested */
if (StringUsed) *StringUsed = DynamicString;
/* Do not free the static buffer on exit, and return success */
StaticBuffer = NULL;
Status = STATUS_SUCCESS;
goto Quickie;
}
/* Now try again under the PEB lock */
RtlAcquirePebLock();
Length = RtlGetFullPathName_Ustr(FileName,
StaticLength,
StaticBuffer,
&ShortName,
NameInvalid,
PathType);
if (!Length)
{
/* It failed */
DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
__FUNCTION__, __LINE__);
Status = STATUS_OBJECT_NAME_INVALID;
goto Release;
}
/* Check if it fits inside our static string now */
if ((StaticString) && (Length < StaticLength))
{
/* Set the final length */
StaticString->Length = (USHORT)Length;
/* Set the file part size */
if (FilePartSize) *FilePartSize = ShortName ? (ShortName - StaticString->Buffer) : 0;
/* Return the static string if requested */
if (StringUsed) *StringUsed = StaticString;
/* We are done with success */
Status = STATUS_SUCCESS;
goto Release;
}
/* Check if the path won't even fit in a real string */
if ((Length + sizeof(WCHAR)) > UNICODE_STRING_MAX_BYTES)
{
/* Name is way too long, fail */
Status = STATUS_NAME_TOO_LONG;
goto Release;
}
/* Allocate the string to hold the path name now */
TempDynamicString.Buffer = RtlpAllocateStringMemory(Length + sizeof(WCHAR),
TAG_USTR);
if (!TempDynamicString.Buffer)
{
/* Out of memory, fail */
Status = STATUS_NO_MEMORY;
goto Release;
}
/* Add space for a NULL terminator, and now check the full path */
TempDynamicString.MaximumLength = (USHORT)Length + sizeof(UNICODE_NULL);
Length = RtlGetFullPathName_Ustr(FileName,
Length,
TempDynamicString.Buffer,
&ShortName,
NameInvalid,
PathType);
if (!Length)
{
/* Some path error, so fail out */
DbgPrint("%s line %d: RtlGetFullPathName_Ustr() returned 0\n",
__FUNCTION__, __LINE__);
Status = STATUS_OBJECT_NAME_INVALID;
goto Release;
}
/* It should fit in the string we just allocated */
ASSERT(Length < (TempDynamicString.MaximumLength - sizeof(WCHAR)));
if (Length > TempDynamicString.MaximumLength)
{
/* This is really weird and would mean some kind of race */
Status = STATUS_INTERNAL_ERROR;
goto Release;
}
/* Return the file part size */
if (FilePartSize) *FilePartSize = ShortName ? (ShortName - TempDynamicString.Buffer) : 0;
/* Terminate the whole string now */
TempDynamicString.Buffer[Length / sizeof(WCHAR)] = UNICODE_NULL;
/* Finalize the string and return it to the user */
DynamicString->Buffer = TempDynamicString.Buffer;
DynamicString->Length = (USHORT)Length;
DynamicString->MaximumLength = TempDynamicString.MaximumLength;
if (StringUsed) *StringUsed = DynamicString;
/* Return success and make sure we don't free the buffer on exit */
TempDynamicString.Buffer = NULL;
Status = STATUS_SUCCESS;
Release:
/* Release the PEB lock */
RtlReleasePebLock();
Quickie:
/* Free any buffers we should be freeing */
DPRINT("Status: %lx %S %S\n", Status, StaticBuffer, TempDynamicString.Buffer);
if ((StaticString) && (StaticBuffer) && (StaticBuffer != StaticString->Buffer))
{
RtlpFreeMemory(StaticBuffer, TAG_USTR);
}
if (TempDynamicString.Buffer)
{
RtlpFreeMemory(TempDynamicString.Buffer, TAG_USTR);
}
/* Print out any unusual errors */
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);
}
/* Return, we're all done */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
RtlDosSearchPath_Ustr(IN ULONG Flags,
IN PUNICODE_STRING PathString,
IN PUNICODE_STRING FileNameString,
IN PUNICODE_STRING ExtensionString,
IN PUNICODE_STRING CallerBuffer,
IN OUT PUNICODE_STRING DynamicString OPTIONAL,
OUT PUNICODE_STRING* FullNameOut OPTIONAL,
OUT PSIZE_T FilePartSize OPTIONAL,
OUT PSIZE_T LengthNeeded OPTIONAL)
{
WCHAR StaticCandidateBuffer[MAX_PATH];
UNICODE_STRING StaticCandidateString;
NTSTATUS Status;
RTL_PATH_TYPE PathType;
PWCHAR p, End, CandidateEnd, SegmentEnd;
SIZE_T SegmentSize, ByteCount, PathSize, MaxPathSize = 0;
USHORT NamePlusExtLength, WorstCaseLength, ExtensionLength = 0;
PUNICODE_STRING FullIsolatedPath;
DPRINT("DOS Path Search: %lx %wZ %wZ %wZ %wZ %wZ\n",
Flags, PathString, FileNameString, ExtensionString, CallerBuffer, DynamicString);
/* Initialize the input string */
RtlInitEmptyUnicodeString(&StaticCandidateString,
StaticCandidateBuffer,
sizeof(StaticCandidateBuffer));
/* Initialize optional arguments */
if (FullNameOut ) *FullNameOut = NULL;
if (FilePartSize) *FilePartSize = 0;
if (LengthNeeded) *LengthNeeded = 0;
if (DynamicString)
{
DynamicString->Length = DynamicString->MaximumLength = 0;
DynamicString->Buffer = NULL;
}
/* Check for invalid parameters */
if ((Flags & ~7) ||
!(PathString) ||
!(FileNameString) ||
((CallerBuffer) && (DynamicString) && !(FullNameOut)))
{
/* Fail */
DbgPrint("%s: Invalid parameters passed\n", __FUNCTION__);
Status = STATUS_INVALID_PARAMETER;
goto Quickie;
}
/* First check what kind of path this is */
PathType = RtlDetermineDosPathNameType_Ustr(FileNameString);
/* Check if the caller wants to prevent relative .\ and ..\ paths */
if ((Flags & 2) &&
(PathType == RtlPathTypeRelative) &&
(FileNameString->Length >= (2 * sizeof(WCHAR))) &&
(FileNameString->Buffer[0] == L'.') &&
((IS_PATH_SEPARATOR(FileNameString->Buffer[1])) ||
((FileNameString->Buffer[1] == L'.') &&
((FileNameString->Length >= (3 * sizeof(WCHAR))) &&
(IS_PATH_SEPARATOR(FileNameString->Buffer[2]))))))
{
/* Yes, and this path is like that, so make it seem unknown */
PathType = RtlPathTypeUnknown;
}
/* Now check relative vs non-relative paths */
if (PathType == RtlPathTypeRelative)
{
/* Does the caller want SxS? */
if (Flags & 1)
{
/* Apply the SxS magic */
FullIsolatedPath = NULL;
Status = RtlDosApplyFileIsolationRedirection_Ustr(TRUE,
FileNameString,
ExtensionString,
CallerBuffer,
DynamicString,
&FullIsolatedPath,
NULL,
FilePartSize,
LengthNeeded);
if (NT_SUCCESS(Status))
{
/* We found the SxS path, return it */
if (FullNameOut) *FullNameOut = FullIsolatedPath;
goto Quickie;
}
else if (Status != STATUS_SXS_KEY_NOT_FOUND)
{
/* Critical SxS error, fail */
DbgPrint("%s: Failing because call to "
"RtlDosApplyIsolationRedirection_Ustr(%wZ) failed with "
"status 0x%08lx\n",
__FUNCTION__,
FileNameString,
Status);
goto Quickie;
}
}
/* No SxS key found, or not requested, check if there's an extension */
if (ExtensionString)
{
/* Save the extension length, and check if there's a file name */
ExtensionLength = ExtensionString->Length;
if (FileNameString->Length)
{
/* Start parsing the file name */
End = &FileNameString->Buffer[FileNameString->Length / sizeof(WCHAR)];
while (End > FileNameString->Buffer)
{
/* If we find a path separator, there's no extension */
if (IS_PATH_SEPARATOR(*--End)) break;
/* Otherwise, did we find an extension dot? */
if (*End == L'.')
{
/* Ignore what the caller sent it, use the filename's */
ExtensionString = NULL;
ExtensionLength = 0;
break;
}
}
}
}
/* Check if we got a path */
if (PathString->Length)
{
/* Start parsing the path name, looking for path separators */
End = &PathString->Buffer[PathString->Length / sizeof(WCHAR)];
p = End;
while ((p > PathString->Buffer) && (*--p == L';'))
{
/* This is the size of the path -- handle a trailing slash */
PathSize = End - p - 1;
if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
/* Check if we found a bigger path than before */
if (PathSize > MaxPathSize) MaxPathSize = PathSize;
/* Keep going with the path after this path separator */
End = p;
}
/* This is the trailing path, run the same code as above */
PathSize = End - p;
if ((PathSize) && !(IS_PATH_SEPARATOR(*(End - 1)))) PathSize++;
if (PathSize > MaxPathSize) MaxPathSize = PathSize;
/* Finally, convert the largest path size into WCHAR */
MaxPathSize *= sizeof(WCHAR);
}
/* Use the extension, the file name, and the largest path as the size */
WorstCaseLength = ExtensionLength +
FileNameString->Length +
(USHORT)MaxPathSize +
sizeof(UNICODE_NULL);
if (WorstCaseLength > UNICODE_STRING_MAX_BYTES)
{
/* It has to fit in a registry string, if not, fail here */
DbgPrint("%s returning STATUS_NAME_TOO_LONG because the computed "
"worst case file name length is %Iu bytes\n",
__FUNCTION__,
WorstCaseLength);
Status = STATUS_NAME_TOO_LONG;
goto Quickie;
}
/* Scan the path now, to see if we can find the file */
p = PathString->Buffer;
End = &p[PathString->Length / sizeof(WCHAR)];
while (p < End)
{
/* Find out where this path ends */
for (SegmentEnd = p;
((SegmentEnd != End) && (*SegmentEnd != L';'));
SegmentEnd++);
/* Compute the size of this path */
ByteCount = SegmentSize = (SegmentEnd - p) * sizeof(WCHAR);
/* Handle trailing slash if there isn't one */
if ((SegmentSize) && !(IS_PATH_SEPARATOR(*(SegmentEnd - 1))))
{
/* Add space for one */
SegmentSize += sizeof(OBJ_NAME_PATH_SEPARATOR);
}
/* Now check if our initial static buffer is too small */
if (StaticCandidateString.MaximumLength <
(SegmentSize + ExtensionLength + FileNameString->Length))
{
/* At this point we should've been using our static buffer */
ASSERT(StaticCandidateString.Buffer == StaticCandidateBuffer);
if (StaticCandidateString.Buffer != StaticCandidateBuffer)
{
/* Something is really messed up if this was the dynamic string */
DbgPrint("%s: internal error #1; "
"CandidateString.Buffer = %p; "
"StaticCandidateBuffer = %p\n",
__FUNCTION__,
StaticCandidateString.Buffer,
StaticCandidateBuffer);
Status = STATUS_INTERNAL_ERROR;
goto Quickie;
}
/* We checked before that the maximum possible size shoudl fit! */
ASSERT((SegmentSize + FileNameString->Length + ExtensionLength) <
UNICODE_STRING_MAX_BYTES);
if ((SegmentSize + ExtensionLength + FileNameString->Length) >
(UNICODE_STRING_MAX_BYTES - sizeof(WCHAR)))
{
/* For some reason it's not fitting anymore. Something messed up */
DbgPrint("%s: internal error #2; SegmentSize = %u, "
"FileName->Length = %u, DefaultExtensionLength = %u\n",
__FUNCTION__,
SegmentSize,
FileNameString->Length,
ExtensionLength);
Status = STATUS_INTERNAL_ERROR;
goto Quickie;
}
/* Now allocate the dynamic string */
StaticCandidateString.MaximumLength = FileNameString->Length +
WorstCaseLength;
StaticCandidateString.Buffer = RtlpAllocateStringMemory(WorstCaseLength,
TAG_USTR);
if (!StaticCandidateString.Buffer)
{
/* Out of memory, fail */
DbgPrint("%s: Unable to allocate %u byte buffer for path candidate\n",
__FUNCTION__,
StaticCandidateString.MaximumLength);
Status = STATUS_NO_MEMORY;
goto Quickie;
}
}
/* Copy the path in the string */
RtlCopyMemory(StaticCandidateString.Buffer, p, ByteCount);
/* Get to the end of the string, and add the trailing slash if missing */
CandidateEnd = &StaticCandidateString.Buffer[ByteCount / sizeof(WCHAR)];
if ((SegmentSize) && (SegmentSize != ByteCount))
{
*CandidateEnd++ = OBJ_NAME_PATH_SEPARATOR;
}
/* Copy the filename now */
RtlCopyMemory(CandidateEnd,
FileNameString->Buffer,
FileNameString->Length);
CandidateEnd += (FileNameString->Length / sizeof(WCHAR));
/* Check if there was an extension */
if (ExtensionString)
{
/* Copy the extension too */
RtlCopyMemory(CandidateEnd,
ExtensionString->Buffer,
ExtensionString->Length);
CandidateEnd += (ExtensionString->Length / sizeof(WCHAR));
}
/* We are done, terminate it */
*CandidateEnd = UNICODE_NULL;
/* Now set the final length of the string so it becomes valid */
StaticCandidateString.Length = (USHORT)(CandidateEnd -
StaticCandidateString.Buffer) *
sizeof(WCHAR);
/* Check if this file exists */
DPRINT("BUFFER: %S\n", StaticCandidateString.Buffer);
if (RtlDoesFileExists_UEx(StaticCandidateString.Buffer, FALSE))
{
/* Awesome, it does, now get the full path */
Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
CallerBuffer,
DynamicString,
(PUNICODE_STRING*)FullNameOut,
FilePartSize,
NULL,
&PathType,
LengthNeeded);
if (!(NT_SUCCESS(Status)) &&
((Status != STATUS_NO_SUCH_FILE) &&
(Status != STATUS_BUFFER_TOO_SMALL)))
{
DbgPrint("%s: Failing because we thought we found %wZ on "
"the search path, but RtlGetfullPathNameUStrEx() "
"returned %08lx\n",
__FUNCTION__,
&StaticCandidateString,
Status);
}
DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
goto Quickie;
}
else
{
/* Otherwise, move to the next path */
if (SegmentEnd != End)
{
/* Handle the case of the path separator trailing */
p = SegmentEnd + 1;
}
else
{
p = SegmentEnd;
}
}
}
/* Loop finished and we didn't break out -- fail */
Status = STATUS_NO_SUCH_FILE;
}
else
{
/* We have a full path, so check if it does exist */
DPRINT("%wZ\n", FileNameString);
if (!RtlDoesFileExists_UstrEx(FileNameString, TRUE))
{
/* It doesn't exist, did we have an extension? */
if (!(ExtensionString) || !(ExtensionString->Length))
{
/* No extension, so just fail */
Status = STATUS_NO_SUCH_FILE;
goto Quickie;
}
/* There was an extension, check if the filename already had one */
if (!(Flags & 4) && (FileNameString->Length))
{
/* Parse the filename */
p = FileNameString->Buffer;
End = &p[FileNameString->Length / sizeof(WCHAR)];
while (End > p)
{
/* If there's a path separator, there's no extension */
if (IS_PATH_SEPARATOR(*--End)) break;
/* Othwerwise, did we find an extension dot? */
if (*End == L'.')
{
/* File already had an extension, so fail */
Status = STATUS_NO_SUCH_FILE;
goto Quickie;
}
}
}
/* So there is an extension, we'll try again by adding it */
NamePlusExtLength = FileNameString->Length +
ExtensionString->Length +
sizeof(UNICODE_NULL);
if (NamePlusExtLength > UNICODE_STRING_MAX_BYTES)
{
/* It won't fit in any kind of valid string, so fail */
DbgPrint("%s: Failing because filename plus extension (%Iu bytes) is too big\n",
__FUNCTION__,
NamePlusExtLength);
Status = STATUS_NAME_TOO_LONG;
goto Quickie;
}
/* Fill it fit in our temporary string? */
if (NamePlusExtLength > StaticCandidateString.MaximumLength)
{
/* It won't fit anymore, allocate a dynamic string for it */
StaticCandidateString.MaximumLength = NamePlusExtLength;
StaticCandidateString.Buffer = RtlpAllocateStringMemory(NamePlusExtLength,
TAG_USTR);
if (!StaticCandidateString.Buffer)
{
/* Ran out of memory, so fail */
DbgPrint("%s: Failing because allocating the dynamic filename buffer failed\n",
__FUNCTION__);
Status = STATUS_NO_MEMORY;
goto Quickie;
}
}
/* Copy the filename */
RtlCopyUnicodeString(&StaticCandidateString, FileNameString);
/* Copy the extension */
RtlAppendUnicodeStringToString(&StaticCandidateString,
ExtensionString);
DPRINT("SB: %wZ\n", &StaticCandidateString);
/* And check if this file now exists */
if (!RtlDoesFileExists_UstrEx(&StaticCandidateString, TRUE))
{
/* Still no joy, fail out */
Status = STATUS_NO_SUCH_FILE;
goto Quickie;
}
/* File was found, get the final full path */
Status = RtlGetFullPathName_UstrEx(&StaticCandidateString,
CallerBuffer,
DynamicString,
(PUNICODE_STRING*)FullNameOut,
FilePartSize,
NULL,
&PathType,
LengthNeeded);
if (!(NT_SUCCESS(Status)) && (Status != STATUS_NO_SUCH_FILE))
{
DbgPrint("%s: Failing on \"%wZ\" because RtlGetfullPathNameUStrEx() "
"failed with status %08lx\n",
__FUNCTION__,
&StaticCandidateString,
Status);
}
DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
}
else
{
/* File was found on the first try, get the final full path */
Status = RtlGetFullPathName_UstrEx(FileNameString,
CallerBuffer,
DynamicString,
(PUNICODE_STRING*)FullNameOut,
FilePartSize,
NULL,
&PathType,
LengthNeeded);
if (!(NT_SUCCESS(Status)) &&
((Status != STATUS_NO_SUCH_FILE) &&
(Status != STATUS_BUFFER_TOO_SMALL)))
{
DbgPrint("%s: Failing because RtlGetfullPathNameUStrEx() on %wZ "
"failed with status %08lx\n",
__FUNCTION__,
FileNameString,
Status);
}
DPRINT("Status: %lx BUFFER: %S\n", Status, CallerBuffer->Buffer);
}
}
Quickie:
/* Anything that was not an error, turn into STATUS_SUCCESS */
if (NT_SUCCESS(Status)) Status = STATUS_SUCCESS;
/* Check if we had a dynamic string */
if ((StaticCandidateString.Buffer) &&
(StaticCandidateString.Buffer != StaticCandidateBuffer))
{
/* Free it */
RtlFreeUnicodeString(&StaticCandidateString);
}
/* Return the status */
return Status;
}
/*
* @implemented
*/
BOOLEAN
NTAPI
RtlDoesFileExists_U(IN PCWSTR FileName)
{
/* Call the new function */
return RtlDoesFileExists_UEx(FileName, TRUE);
}
/* EOF */