3004 lines
88 KiB
C
3004 lines
88 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1998-1999 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
filenames.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
this is the code that handles the file lists (exclusion/inclusion).
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Neal Christiansen (nealch) 03-Jan-2001
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.h"
|
||
|
|
||
|
|
||
|
//
|
||
|
// Local prototypes
|
||
|
//
|
||
|
|
||
|
NTSTATUS
|
||
|
SrpExpandShortNames(
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
IN BOOLEAN expandFileNameComponenet
|
||
|
);
|
||
|
|
||
|
NTSTATUS
|
||
|
SrpExpandPathOfFileName(
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
);
|
||
|
|
||
|
|
||
|
//
|
||
|
// linker commands
|
||
|
//
|
||
|
|
||
|
#ifdef ALLOC_PRAGMA
|
||
|
|
||
|
#pragma alloc_text( PAGE, SrpGetFileName )
|
||
|
#pragma alloc_text( PAGE, SrpGetFileNameFromFileObject )
|
||
|
#pragma alloc_text( PAGE, SrpGetFileNameOpenById )
|
||
|
#pragma alloc_text( PAGE, SrpExpandShortNames )
|
||
|
#pragma alloc_text( PAGE, SrpExpandPathOfFileName )
|
||
|
#pragma alloc_text( PAGE, SrpRemoveStreamName )
|
||
|
#pragma alloc_text( PAGE, SrpExpandDestPath )
|
||
|
#pragma alloc_text( PAGE, SrpInitNameControl )
|
||
|
#pragma alloc_text( PAGE, SrpCleanupNameControl )
|
||
|
#pragma alloc_text( PAGE, SrpReallocNameControl )
|
||
|
#pragma alloc_text( PAGE, SrpExpandFileName )
|
||
|
#pragma alloc_text( PAGE, SrIsFileEligible )
|
||
|
#pragma alloc_text( PAGE, SrFileNameContainsStream )
|
||
|
#pragma alloc_text( PAGE, SrFileAlreadyExists )
|
||
|
#pragma alloc_text( PAGE, SrIsFileStream )
|
||
|
|
||
|
#endif // ALLOC_PRAGMA
|
||
|
|
||
|
|
||
|
#if DBG
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This presently scans the filename looking for two backslashes in a row.
|
||
|
If so
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
--***************************************************************************/
|
||
|
VOID
|
||
|
VALIDATE_FILENAME(
|
||
|
IN PUNICODE_STRING pName
|
||
|
)
|
||
|
{
|
||
|
ULONG i;
|
||
|
ULONG len;
|
||
|
|
||
|
if (pName && (pName->Length > 0))
|
||
|
{
|
||
|
len = (pName->Length/sizeof(WCHAR))-1;
|
||
|
|
||
|
//
|
||
|
// Setup to scan the name
|
||
|
//
|
||
|
|
||
|
for (i=0;
|
||
|
i < len;
|
||
|
i++ )
|
||
|
{
|
||
|
//
|
||
|
// See if there are adjacent backslashes
|
||
|
//
|
||
|
|
||
|
if (pName->Buffer[i] == L'\\' &&
|
||
|
pName->Buffer[i+1] == L'\\')
|
||
|
{
|
||
|
if (FlagOn(global->DebugControl,
|
||
|
SR_DEBUG_VERBOSE_ERRORS|SR_DEBUG_BREAK_ON_ERROR))
|
||
|
{
|
||
|
KdPrint(("sr!VALIDATE_FILENAME: Detected adjacent backslashes in \"%wZ\"\n",
|
||
|
pName));
|
||
|
}
|
||
|
|
||
|
if (FlagOn(global->DebugControl,SR_DEBUG_BREAK_ON_ERROR))
|
||
|
{
|
||
|
DbgBreakPoint();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine will try and get the name of the given file object. This
|
||
|
will allocate a buffer if it needs to. Because of deadlock issues we
|
||
|
do not call ObQueryNameString. Instead we ask the file system for the
|
||
|
name by generating our own IRPs.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the extension for the device this file is on
|
||
|
pFileObject - the fileObject for the file we want the name of.
|
||
|
pNameControl - a structure used to manage the name of the file
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
status of the operation
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpGetFileName (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl
|
||
|
)
|
||
|
{
|
||
|
PFILE_NAME_INFORMATION nameInfo;
|
||
|
NTSTATUS status;
|
||
|
ULONG volNameLength = (ULONG)pExtension->pNtVolumeName->Length;
|
||
|
ULONG returnLength;
|
||
|
ULONG fullNameLength;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(IS_VALID_FILE_OBJECT( pFileObject ) && (pFileObject->Vpb != NULL));
|
||
|
ASSERT(pNameCtrl->AllocatedBuffer == NULL);
|
||
|
|
||
|
//
|
||
|
// Use the small buffer in the structure (that will handle most cases)
|
||
|
// for the name. Then get the name
|
||
|
//
|
||
|
|
||
|
nameInfo = (PFILE_NAME_INFORMATION)pNameCtrl->SmallBuffer;
|
||
|
|
||
|
status = SrQueryInformationFile( pExtension->pTargetDevice,
|
||
|
pFileObject,
|
||
|
nameInfo,
|
||
|
pNameCtrl->BufferSize - sizeof(WCHAR),
|
||
|
FileNameInformation,
|
||
|
&returnLength );
|
||
|
|
||
|
//
|
||
|
// If the buffer was too small, get a bigger one.
|
||
|
//
|
||
|
|
||
|
if (status == STATUS_BUFFER_OVERFLOW)
|
||
|
{
|
||
|
//
|
||
|
// The buffer was too small, allocate one big enough (including volume
|
||
|
// name and terminating NULL)
|
||
|
//
|
||
|
|
||
|
status = SrpReallocNameControl( pNameCtrl,
|
||
|
nameInfo->FileNameLength +
|
||
|
volNameLength +
|
||
|
SHORT_NAME_EXPANSION_SPACE +
|
||
|
sizeof(WCHAR),
|
||
|
NULL );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the allocated buffer and get the name again
|
||
|
//
|
||
|
|
||
|
nameInfo = (PFILE_NAME_INFORMATION)pNameCtrl->AllocatedBuffer;
|
||
|
|
||
|
status = SrQueryInformationFile( pExtension->pTargetDevice,
|
||
|
pFileObject,
|
||
|
nameInfo,
|
||
|
pNameCtrl->BufferSize - sizeof(WCHAR),
|
||
|
FileNameInformation,
|
||
|
&returnLength );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Handle QueryInformation errors
|
||
|
//
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We now have the filename. Calucalte how much space the full name
|
||
|
// (including device name) will be. Include space for a terminating
|
||
|
// NULL. See if there is room in the current buffer.
|
||
|
//
|
||
|
|
||
|
fullNameLength = nameInfo->FileNameLength + volNameLength;
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( pNameCtrl, fullNameLength);
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Slide the file name down to make room for the device name. Account
|
||
|
// for the header in the FILE_NAME_INFORMATION structure
|
||
|
//
|
||
|
|
||
|
RtlMoveMemory( &pNameCtrl->Name.Buffer[volNameLength/sizeof(WCHAR) ],
|
||
|
nameInfo->FileName,
|
||
|
nameInfo->FileNameLength );
|
||
|
|
||
|
RtlCopyMemory( pNameCtrl->Name.Buffer,
|
||
|
pExtension->pNtVolumeName->Buffer,
|
||
|
volNameLength );
|
||
|
|
||
|
pNameCtrl->Name.Length = (USHORT)fullNameLength;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Gets the file name and volume name for a file that is not yet opened.
|
||
|
This is necessary in the MJ_CREATE where work is done prior to the file
|
||
|
being opened by the fsd.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - completion code.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpGetFileNameFromFileObject (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
ULONG fullNameLength;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(IS_VALID_FILE_OBJECT(pFileObject));
|
||
|
ASSERT(pExtension->pNtVolumeName != NULL);
|
||
|
ASSERT(pExtension->pNtVolumeName->Length > 0);
|
||
|
ASSERT(pNameCtrl->AllocatedBuffer == NULL);
|
||
|
|
||
|
//
|
||
|
// first see if this is a relative open
|
||
|
//
|
||
|
|
||
|
if (pFileObject->RelatedFileObject != NULL)
|
||
|
{
|
||
|
//
|
||
|
// get the full name of the related file object
|
||
|
//
|
||
|
|
||
|
status = SrpGetFileName( pExtension,
|
||
|
pFileObject->RelatedFileObject,
|
||
|
pNameCtrl );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
ASSERT(pNameCtrl->Name.Length > 0);
|
||
|
|
||
|
//
|
||
|
// make sure the buffer is still large enough. Note that we account
|
||
|
// for a trailing null to be added as well as the poosible addition
|
||
|
// of a separator character.
|
||
|
//
|
||
|
|
||
|
fullNameLength = pNameCtrl->Name.Length +
|
||
|
sizeof(WCHAR) + // could be a seperator
|
||
|
pFileObject->FileName.Length;
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( pNameCtrl, fullNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// put on the slash seperator if it is missing
|
||
|
//
|
||
|
|
||
|
if ((pFileObject->FileName.Length > 0) &&
|
||
|
(pFileObject->FileName.Buffer[0] != L'\\') &&
|
||
|
(pFileObject->FileName.Buffer[0] != L':') &&
|
||
|
(pNameCtrl->Name.Buffer[(pNameCtrl->Name.Length/sizeof(WCHAR))-1] != L'\\'))
|
||
|
{
|
||
|
pNameCtrl->Name.Buffer[pNameCtrl->Name.Length/sizeof(WCHAR)] = L'\\';
|
||
|
pNameCtrl->Name.Length += sizeof(WCHAR);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// now append the file's name part
|
||
|
//
|
||
|
|
||
|
status = RtlAppendUnicodeStringToString( &pNameCtrl->Name,
|
||
|
&pFileObject->FileName );
|
||
|
ASSERT(STATUS_SUCCESS == status);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// this is a full path open off of the volume
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// Make sure the buffer is large enough. Note that we account
|
||
|
// for a trailing null to be added.
|
||
|
//
|
||
|
|
||
|
fullNameLength = pExtension->pNtVolumeName->Length + pFileObject->FileName.Length;
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( pNameCtrl, fullNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// set the volume name
|
||
|
//
|
||
|
|
||
|
RtlCopyUnicodeString( &pNameCtrl->Name,
|
||
|
pExtension->pNtVolumeName );
|
||
|
|
||
|
//
|
||
|
// now append the file's name part (it already has the prefix '\\')
|
||
|
//
|
||
|
|
||
|
status = RtlAppendUnicodeStringToString( &pNameCtrl->Name,
|
||
|
&pFileObject->FileName );
|
||
|
ASSERT(STATUS_SUCCESS == status);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The main reson we come through this path is because we are in pre-
|
||
|
// create and we got the name from the file object. We need to expand
|
||
|
// the path to remove any mount points from it.
|
||
|
//
|
||
|
|
||
|
status = SrpExpandPathOfFileName( pExtension,
|
||
|
pNameCtrl,
|
||
|
pReasonableErrorForUnOpenedName );
|
||
|
|
||
|
Cleanup:
|
||
|
#if DBG
|
||
|
if ((STATUS_MOUNT_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_OBJECT_PATH_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_INVALID == status) ||
|
||
|
(STATUS_REPARSE_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_NOT_SAME_DEVICE == status))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Gets the file name if we have a file object that has not be fully
|
||
|
opened yet and it has been opened by ID. In this case, the file must
|
||
|
already exist. We will try to open the file by id to get a fully
|
||
|
initialized file object, then use that file object to query the file name.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - completion code.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpGetFileNameOpenById (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
|
IO_STATUS_BLOCK IoStatusBlock;
|
||
|
NTSTATUS Status;
|
||
|
HANDLE FileHandle = NULL;
|
||
|
PFILE_OBJECT pReopenedFileObject = NULL;
|
||
|
SRP_NAME_CONTROL FileNameCtrl;
|
||
|
ULONG FileNameLength;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
*pReasonableErrorForUnOpenedName = FALSE;
|
||
|
|
||
|
SrpInitNameControl( &FileNameCtrl );
|
||
|
|
||
|
//
|
||
|
// Make sure that we have a valid file name in the file object.
|
||
|
//
|
||
|
|
||
|
if (pFileObject->FileName.Length == 0)
|
||
|
{
|
||
|
Status = STATUS_OBJECT_NAME_INVALID;
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
goto SrpGetFileNameOpenById_Exit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build up our name so that it is in the format of:
|
||
|
// \Device\HarddiskVolume1\[id in binary format]
|
||
|
// We have the device name in our device extension and we have
|
||
|
// the binary format of the file id in pFileObject->FileName.
|
||
|
//
|
||
|
|
||
|
FileNameLength = pExtension->pNtVolumeName->Length +
|
||
|
sizeof( L'\\' ) +
|
||
|
pFileObject->FileName.Length;
|
||
|
|
||
|
Status = SrpNameCtrlBufferCheck( &FileNameCtrl,
|
||
|
FileNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( Status ))
|
||
|
{
|
||
|
goto SrpGetFileNameOpenById_Exit;
|
||
|
}
|
||
|
|
||
|
RtlCopyUnicodeString( &(FileNameCtrl.Name), pExtension->pNtVolumeName );
|
||
|
|
||
|
//
|
||
|
// Check to see if we need to add a '\' separator.
|
||
|
//
|
||
|
|
||
|
if (pFileObject->FileName.Buffer[0] != L'\\')
|
||
|
{
|
||
|
RtlAppendUnicodeToString( &(FileNameCtrl.Name), L"\\" );
|
||
|
}
|
||
|
|
||
|
RtlAppendUnicodeStringToString( &(FileNameCtrl.Name), &(pFileObject->FileName) );
|
||
|
|
||
|
InitializeObjectAttributes( &ObjectAttributes,
|
||
|
&(FileNameCtrl.Name),
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
Status = SrIoCreateFile( &FileHandle,
|
||
|
GENERIC_READ,
|
||
|
&ObjectAttributes,
|
||
|
&IoStatusBlock,
|
||
|
NULL,
|
||
|
0,
|
||
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||
|
FILE_OPEN,
|
||
|
FILE_OPEN_BY_FILE_ID,
|
||
|
NULL,
|
||
|
0,
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
pExtension->pTargetDevice );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( Status ))
|
||
|
{
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
goto SrpGetFileNameOpenById_Exit;
|
||
|
}
|
||
|
|
||
|
Status = ObReferenceObjectByHandle( FileHandle,
|
||
|
0,
|
||
|
*IoFileObjectType,
|
||
|
KernelMode,
|
||
|
(PVOID *) &pReopenedFileObject,
|
||
|
NULL );
|
||
|
|
||
|
if (!NT_SUCCESS( Status ))
|
||
|
{
|
||
|
goto SrpGetFileNameOpenById_Exit;
|
||
|
}
|
||
|
|
||
|
Status = SrpGetFileName( pExtension,
|
||
|
pReopenedFileObject,
|
||
|
pNameCtrl );
|
||
|
|
||
|
CHECK_STATUS( Status );
|
||
|
|
||
|
SrpGetFileNameOpenById_Exit:
|
||
|
|
||
|
SrpCleanupNameControl( &FileNameCtrl );
|
||
|
|
||
|
if (pReopenedFileObject != NULL)
|
||
|
{
|
||
|
ObDereferenceObject( pReopenedFileObject );
|
||
|
}
|
||
|
|
||
|
if (FileHandle != NULL)
|
||
|
{
|
||
|
ZwClose( FileHandle );
|
||
|
}
|
||
|
|
||
|
RETURN ( Status );
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This will scan for short file names in the filename buffer and expand
|
||
|
them inplace. if we need to we will reallocate the name buffer to
|
||
|
grow it.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - completion code.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpExpandShortNames (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
IN BOOLEAN expandFileNameComponent
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status = STATUS_SUCCESS;
|
||
|
ULONG idx;
|
||
|
ULONG start;
|
||
|
ULONG end;
|
||
|
ULONG nameLen;
|
||
|
LONG shortNameLen;
|
||
|
LONG copyLen;
|
||
|
LONG delta;
|
||
|
HANDLE directoryHandle = NULL;
|
||
|
PFILE_NAMES_INFORMATION pFileEntry = NULL;
|
||
|
OBJECT_ATTRIBUTES objectAttributes;
|
||
|
IO_STATUS_BLOCK ioStatusBlock;
|
||
|
UNICODE_STRING shortFileName;
|
||
|
UNICODE_STRING expandedFileName;
|
||
|
USHORT newFileNameLength;
|
||
|
USHORT savedFileNameLength;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT( IS_VALID_SR_STREAM_STRING( &pNameCtrl->Name, pNameCtrl->StreamNameLength) );
|
||
|
VALIDATE_FILENAME( &pNameCtrl->Name );
|
||
|
|
||
|
nameLen = pNameCtrl->Name.Length / sizeof(WCHAR);
|
||
|
|
||
|
//
|
||
|
// scan the entire string
|
||
|
//
|
||
|
|
||
|
for (idx = 0; idx < nameLen; idx++)
|
||
|
{
|
||
|
//
|
||
|
//
|
||
|
// in this example the pointers are like this:
|
||
|
//
|
||
|
// \Device\HarddiskDmVolumes\PhysicalDmVolumes\
|
||
|
// BlockVolume3\Progra~1\office.exe
|
||
|
// ^ ^
|
||
|
// | |
|
||
|
// start end
|
||
|
//
|
||
|
// pStart always points to the last seen '\\' .
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// is this a potential start of a path part?
|
||
|
//
|
||
|
|
||
|
if (pNameCtrl->Name.Buffer[idx] == L'\\')
|
||
|
{
|
||
|
//
|
||
|
// yes, save current position
|
||
|
//
|
||
|
|
||
|
start = idx;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// does this current path part contain a short version (~)
|
||
|
//
|
||
|
|
||
|
else if (pNameCtrl->Name.Buffer[idx] == L'~')
|
||
|
{
|
||
|
//
|
||
|
// we need to expand this part.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// find the end (a ending slash or end of string)
|
||
|
//
|
||
|
|
||
|
while ((idx < nameLen) && (pNameCtrl->Name.Buffer[idx] != L'\\'))
|
||
|
{
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we are looking at the filename component (we hit the end
|
||
|
// of the string) and we are not to expand this component, quit
|
||
|
// now
|
||
|
//
|
||
|
|
||
|
if ((idx >= nameLen) && !expandFileNameComponent)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// at this point idx points either to the NULL or to the
|
||
|
// subsequent '\\', so we will use this as our
|
||
|
//
|
||
|
|
||
|
end = idx;
|
||
|
|
||
|
ASSERT(pNameCtrl->Name.Buffer[start] == L'\\');
|
||
|
ASSERT((end >= nameLen) || (pNameCtrl->Name.Buffer[end] == L'\\'));
|
||
|
|
||
|
//
|
||
|
// Get the length of the file component that we think might be a
|
||
|
// short name. Only try and get the expanded name if the
|
||
|
// component length is <= an 8.3 name length.
|
||
|
//
|
||
|
|
||
|
shortNameLen = end - start - 1;
|
||
|
|
||
|
//
|
||
|
// shortNameLen counts the number of characters, not bytes, in the
|
||
|
// name, therefore we compare this against SR_SHORT_NAME_CHARS, not
|
||
|
// SR_SHORT_NAME_CHARS * sizeof(WCHAR)
|
||
|
//
|
||
|
|
||
|
if (shortNameLen > SR_SHORT_NAME_CHARS)
|
||
|
{
|
||
|
//
|
||
|
// This name is too long to be a short name. Make end the
|
||
|
// next start and keep looping to look at the next path
|
||
|
// component.
|
||
|
//
|
||
|
|
||
|
start = end;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// We have a potential shortname here.
|
||
|
//
|
||
|
// Change the file name length to only include the parent
|
||
|
// directory of the current name component (including
|
||
|
// the trailing slash).
|
||
|
//
|
||
|
|
||
|
savedFileNameLength = pNameCtrl->Name.Length;
|
||
|
pNameCtrl->Name.Length = (USHORT)(start+1)*sizeof(WCHAR);
|
||
|
|
||
|
//
|
||
|
// now open the parent directory
|
||
|
//
|
||
|
|
||
|
ASSERT(directoryHandle == NULL);
|
||
|
|
||
|
InitializeObjectAttributes( &objectAttributes,
|
||
|
&pNameCtrl->Name,
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
status = SrIoCreateFile(
|
||
|
&directoryHandle,
|
||
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
||
|
&objectAttributes,
|
||
|
&ioStatusBlock,
|
||
|
NULL, // AllocationSize
|
||
|
FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, // ShareAccess
|
||
|
FILE_OPEN, // OPEN_EXISTING
|
||
|
FILE_DIRECTORY_FILE
|
||
|
| FILE_OPEN_FOR_BACKUP_INTENT
|
||
|
| FILE_SYNCHRONOUS_IO_NONALERT, //create options
|
||
|
NULL,
|
||
|
0, // EaLength
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
pExtension->pTargetDevice );
|
||
|
|
||
|
//
|
||
|
// Now that we have the directory open, restore the original
|
||
|
// saved file name length.
|
||
|
//
|
||
|
|
||
|
pNameCtrl->Name.Length = savedFileNameLength;
|
||
|
|
||
|
//
|
||
|
// Handle an open error
|
||
|
//
|
||
|
|
||
|
#if DBG
|
||
|
if (STATUS_MOUNT_POINT_NOT_RESOLVED == status)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// This is directory is through a mount point, so we don't
|
||
|
// care about it here. We will wait and take care of it
|
||
|
// when the name has been resolved to the correct volume.
|
||
|
//
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate a buffer to receive the filename if we don't
|
||
|
// already have one.
|
||
|
//
|
||
|
|
||
|
if (pFileEntry == NULL)
|
||
|
{
|
||
|
pFileEntry = ExAllocatePoolWithTag(
|
||
|
PagedPool,
|
||
|
SR_FILE_ENTRY_LENGTH,
|
||
|
SR_FILE_ENTRY_TAG );
|
||
|
|
||
|
if (pFileEntry == NULL)
|
||
|
{
|
||
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// now set just the file part for the query
|
||
|
//
|
||
|
|
||
|
shortFileName.Buffer = &pNameCtrl->Name.Buffer[start+1];
|
||
|
shortFileName.Length = (USHORT)shortNameLen * sizeof(WCHAR);
|
||
|
shortFileName.MaximumLength = shortFileName.Length;
|
||
|
|
||
|
//
|
||
|
// query the file entry to get the long name
|
||
|
//
|
||
|
|
||
|
pFileEntry->FileNameLength = 0;
|
||
|
|
||
|
status = ZwQueryDirectoryFile( directoryHandle,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
&ioStatusBlock,
|
||
|
pFileEntry,
|
||
|
SR_FILE_ENTRY_LENGTH,
|
||
|
FileNamesInformation,
|
||
|
TRUE, // ReturnSingleEntry
|
||
|
&shortFileName,
|
||
|
TRUE ); // RestartScan
|
||
|
|
||
|
//
|
||
|
// it's possible that this file doesn't exist yet. new
|
||
|
// creates with a literal '~' in the name. or creates
|
||
|
// with non-existant directories in the path with ~'s in
|
||
|
// them
|
||
|
//
|
||
|
|
||
|
if (status == STATUS_NO_SUCH_FILE)
|
||
|
{
|
||
|
status = STATUS_SUCCESS;
|
||
|
|
||
|
//
|
||
|
// exit the 'for' loop
|
||
|
//
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
else if (status == STATUS_UNMAPPABLE_CHARACTER)
|
||
|
{
|
||
|
//
|
||
|
// this appears to be ok. fat returns this if there
|
||
|
// are funny characters in the name, but still gives us
|
||
|
// the full name back.
|
||
|
//
|
||
|
|
||
|
status = STATUS_SUCCESS;
|
||
|
}
|
||
|
else if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
ASSERT(pFileEntry->FileNameLength > 0);
|
||
|
|
||
|
//
|
||
|
// did it expand?
|
||
|
//
|
||
|
|
||
|
expandedFileName.Buffer = pFileEntry->FileName;
|
||
|
expandedFileName.Length = (USHORT)pFileEntry->FileNameLength;
|
||
|
expandedFileName.MaximumLength = (USHORT)pFileEntry->FileNameLength;
|
||
|
|
||
|
if (RtlCompareUnicodeString(&expandedFileName,&shortFileName,TRUE) != 0)
|
||
|
{
|
||
|
SrTrace(EXPAND_SHORT_NAMES, ("sr!SrpExpandShortNames: expanded \"%wZ\" to \"%wZ\"\n", &shortFileName, &expandedFileName));
|
||
|
|
||
|
//
|
||
|
// Is there room in the current filename buffer for the
|
||
|
// expanded file name
|
||
|
//
|
||
|
|
||
|
newFileNameLength = ((pNameCtrl->Name.Length - shortFileName.Length) +
|
||
|
expandedFileName.Length);
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( pNameCtrl,
|
||
|
(ULONG)(newFileNameLength +
|
||
|
pNameCtrl->StreamNameLength));
|
||
|
|
||
|
if (!NT_SUCCESS( status )) {
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// shuffle things around to make room for the expanded name
|
||
|
//
|
||
|
|
||
|
delta = ((LONG)expandedFileName.Length - (LONG)shortFileName.Length)/
|
||
|
(LONG)sizeof(WCHAR);
|
||
|
|
||
|
//
|
||
|
// Open a hole for the name
|
||
|
//
|
||
|
|
||
|
if (delta != 0)
|
||
|
{
|
||
|
copyLen = (pNameCtrl->Name.Length +
|
||
|
pNameCtrl->StreamNameLength) -
|
||
|
(end * sizeof(WCHAR));
|
||
|
ASSERT(copyLen >= 0);
|
||
|
|
||
|
if (copyLen > 0)
|
||
|
{
|
||
|
RtlMoveMemory( &pNameCtrl->Name.Buffer[end + delta],
|
||
|
&pNameCtrl->Name.Buffer[end],
|
||
|
copyLen );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// now copy over the expanded name
|
||
|
//
|
||
|
|
||
|
RtlCopyMemory(&pNameCtrl->Name.Buffer[start + 1],
|
||
|
pFileEntry->FileName,
|
||
|
pFileEntry->FileNameLength );
|
||
|
|
||
|
//
|
||
|
// and update our current index and lengths.
|
||
|
//
|
||
|
|
||
|
idx += delta;
|
||
|
pNameCtrl->Name.Length = (USHORT)newFileNameLength;
|
||
|
nameLen = newFileNameLength / sizeof(WCHAR);
|
||
|
|
||
|
//
|
||
|
// always NULL terminate
|
||
|
//
|
||
|
|
||
|
//pNameCtrl->Name.Buffer[pNameCtrl->Name.Length/sizeof(WCHAR)] = UNICODE_NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// close the directory handle.
|
||
|
//
|
||
|
|
||
|
ZwClose( directoryHandle );
|
||
|
directoryHandle = NULL;
|
||
|
|
||
|
//
|
||
|
// We may have just expanded a name component. Make sure
|
||
|
// that we still have valid name and stream name components.
|
||
|
//
|
||
|
|
||
|
ASSERT( IS_VALID_SR_STREAM_STRING( &pNameCtrl->Name, pNameCtrl->StreamNameLength) );
|
||
|
|
||
|
//
|
||
|
// skip start ahead to the next spot (the next slash or NULL)
|
||
|
//
|
||
|
|
||
|
start = idx;
|
||
|
end = -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Cleanup state and return
|
||
|
//
|
||
|
Cleanup:
|
||
|
|
||
|
if (NULL != pFileEntry)
|
||
|
{
|
||
|
ExFreePool(pFileEntry);
|
||
|
NULLPTR(pFileEntry);
|
||
|
}
|
||
|
|
||
|
if (NULL != directoryHandle)
|
||
|
{
|
||
|
ZwClose(directoryHandle);
|
||
|
NULLPTR(directoryHandle);
|
||
|
}
|
||
|
|
||
|
#if DBG
|
||
|
|
||
|
if (STATUS_MOUNT_POINT_NOT_RESOLVED == status) {
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
VALIDATE_FILENAME( &pNameCtrl->Name );
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine will take the given full path filename, extract the PATH
|
||
|
portion, get its value and substitue it back into the original name
|
||
|
if it is different. We do this to handle volume mount points as well
|
||
|
as normallizing the name to a common format. NOTE: this does NOT
|
||
|
expand short names, but it will normalize the volume name to the
|
||
|
\Device\HarddiskVolume1 format.
|
||
|
|
||
|
We do this by:
|
||
|
open the parent to get the real path to the parent. we can't just
|
||
|
open the target as it might not exist yet. we are sure the parent
|
||
|
of the target always exists. if it doesn't, the rename will fail,
|
||
|
and we are ok.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the SR device extension for the volume we are working on
|
||
|
pNameCtrl - the name control structure that contains the name we are to
|
||
|
expand.
|
||
|
pReasonableErrorForUnOpenedName - this will be set to TRUE if we hit
|
||
|
a reasonable error (e.g., something that leads us to believe the
|
||
|
original operation will also fail) during our work to expand the
|
||
|
path.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - STATUS_SUCCESS if we were able to expand the path without
|
||
|
error, or the appropriate error code otherwise. If we did hit a
|
||
|
reasonable error during this process, we will return that error
|
||
|
here.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpExpandPathOfFileName (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
|
IO_STATUS_BLOCK IoStatusBlock;
|
||
|
SRP_NAME_CONTROL localNameCtrl;
|
||
|
ULONG TokenLength = 0;
|
||
|
PWSTR pToken;
|
||
|
PWCHAR pOrigBuffer;
|
||
|
HANDLE FileHandle = NULL;
|
||
|
PFILE_OBJECT pFileObject = NULL;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
//
|
||
|
// Initialize state
|
||
|
//
|
||
|
|
||
|
SrpInitNameControl( &localNameCtrl );
|
||
|
|
||
|
//
|
||
|
// Throughout this function, if this name contains a stream component,
|
||
|
// we want to manipulate the name as if the stream was part of the
|
||
|
// file name. So add this amount to the pNameCtrl->Name.Length for
|
||
|
// now. At the end of this routine, we will decrement
|
||
|
// pNameCtrl->Name.Length appropriately.
|
||
|
//
|
||
|
|
||
|
ASSERT( IS_VALID_SR_STREAM_STRING( &pNameCtrl->Name, pNameCtrl->StreamNameLength) );
|
||
|
|
||
|
pNameCtrl->Name.Length += pNameCtrl->StreamNameLength;
|
||
|
ASSERT( pNameCtrl->Name.Length <= pNameCtrl->Name.MaximumLength );
|
||
|
|
||
|
//
|
||
|
// find the filename part in the full path
|
||
|
//
|
||
|
|
||
|
status = SrFindCharReverse( pNameCtrl->Name.Buffer,
|
||
|
pNameCtrl->Name.Length,
|
||
|
L'\\',
|
||
|
&pToken,
|
||
|
&TokenLength );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The token pointer points to the last '\' on the original file and the
|
||
|
// length includes that '\'. Adjust the token pointer and length to not
|
||
|
// include it. Note that it is possible for a directory name to get
|
||
|
// down this path if the user tries to open a directory for overwrite or
|
||
|
// supercede. This open will fail, but we will try to lookup the name
|
||
|
// anyway.
|
||
|
//
|
||
|
|
||
|
ASSERT(*pToken == L'\\');
|
||
|
ASSERT(TokenLength >= sizeof(WCHAR));
|
||
|
pToken++;
|
||
|
TokenLength -= sizeof(WCHAR);
|
||
|
|
||
|
//
|
||
|
// Take the filename part out of the original name.
|
||
|
// NOTE: This does not take the '\' out of the name. If we do and this
|
||
|
// is the root directory of the volume, the ZwCreateFile will
|
||
|
// result in a volume open instead of an open on the root directory
|
||
|
// of the volume.
|
||
|
// NOTE: We specifically are sending this command to the TOP
|
||
|
// of the filter stack chain because we want to resolve
|
||
|
// the mount points. We need the name with all mount points
|
||
|
// resolved so that we log the correct name in the change.log.
|
||
|
//
|
||
|
|
||
|
ASSERT(pNameCtrl->Name.Length > TokenLength);
|
||
|
|
||
|
pNameCtrl->Name.Length -= (USHORT)TokenLength;
|
||
|
|
||
|
InitializeObjectAttributes( &ObjectAttributes,
|
||
|
&pNameCtrl->Name,
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
status = SrIoCreateFile( &FileHandle,
|
||
|
SYNCHRONIZE,
|
||
|
&ObjectAttributes,
|
||
|
&IoStatusBlock,
|
||
|
NULL,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
|
FILE_OPEN, // OPEN_EXISTING
|
||
|
FILE_SYNCHRONOUS_IO_NONALERT
|
||
|
| FILE_OPEN_FOR_BACKUP_INTENT,
|
||
|
NULL,
|
||
|
0, // EaLength
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
NULL ); // go to TOP of filter stack
|
||
|
|
||
|
//
|
||
|
// not there? that's ok, the rename will fail then.
|
||
|
//
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK(status))
|
||
|
{
|
||
|
//
|
||
|
// It is resonable for pre-create to get an error here
|
||
|
//
|
||
|
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// reference the file object
|
||
|
//
|
||
|
|
||
|
status = ObReferenceObjectByHandle( FileHandle,
|
||
|
0,
|
||
|
*IoFileObjectType,
|
||
|
KernelMode,
|
||
|
(PVOID *) &pFileObject,
|
||
|
NULL );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now we need to make sure that we are still on the same volume. The
|
||
|
// above open will have resolved any mount points and that could have
|
||
|
// taken us to another volume in the system.
|
||
|
//
|
||
|
|
||
|
if (IoGetRelatedDeviceObject( pFileObject ) !=
|
||
|
IoGetAttachedDevice( pExtension->pDeviceObject )) {
|
||
|
|
||
|
status = STATUS_NOT_SAME_DEVICE;
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the name of the parent directory, this will handle
|
||
|
// resolving mount locations
|
||
|
//
|
||
|
|
||
|
status = SrpGetFileName( pExtension,
|
||
|
pFileObject,
|
||
|
&localNameCtrl );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Make sure there is a slash on the end of the new string (since our
|
||
|
// source string always has a slash) before we see if they are the same
|
||
|
//
|
||
|
|
||
|
ASSERT(localNameCtrl.Name.Length > 0);
|
||
|
|
||
|
if (localNameCtrl.Name.Buffer[(localNameCtrl.Name.Length/sizeof(WCHAR))-1] != L'\\')
|
||
|
{
|
||
|
status = SrpNameCtrlBufferCheck( &localNameCtrl,
|
||
|
localNameCtrl.Name.Length+sizeof(WCHAR) );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
localNameCtrl.Name.Buffer[(localNameCtrl.Name.Length/sizeof(WCHAR))] = L'\\';
|
||
|
localNameCtrl.Name.Length += sizeof(WCHAR);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// See if the directory name is different. If not just return now
|
||
|
//
|
||
|
|
||
|
if (RtlCompareUnicodeString( &pNameCtrl->Name,
|
||
|
&localNameCtrl.Name,
|
||
|
TRUE ) == 0)
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Worst case the new name is longer so make sure our buffer is big
|
||
|
// enough. If the new buffer is smaller then we know we won't need
|
||
|
// to allocate more name.
|
||
|
//
|
||
|
|
||
|
status = SrpNameCtrlBufferCheckKeepOldBuffer( pNameCtrl,
|
||
|
localNameCtrl.Name.Length + TokenLength,
|
||
|
&pOrigBuffer );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The name did change. Shuffle the file name left or right based on
|
||
|
// the new estimated length of the path. Note that we need to do the
|
||
|
// move first because pToken is a pointer into this string where the
|
||
|
// filename used to be.
|
||
|
//
|
||
|
|
||
|
RtlMoveMemory( &pNameCtrl->Name.Buffer[localNameCtrl.Name.Length/sizeof(WCHAR)],
|
||
|
pToken,
|
||
|
TokenLength );
|
||
|
|
||
|
//
|
||
|
// We may have had to allocate a new buffer for this new name. If there
|
||
|
// happened to be an old allocated buffer (the system is deisgned so this
|
||
|
// should never happen) then we had the SrNameCtrlBufferCheckKeepOldBuffer
|
||
|
// macro return us the old buffer because pToken still pointed into it.
|
||
|
// We now need to free that buffer.
|
||
|
//
|
||
|
|
||
|
if (pOrigBuffer)
|
||
|
{
|
||
|
ExFreePool(pOrigBuffer);
|
||
|
NULLPTR(pToken);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Copy the path portion of the name and set the length
|
||
|
//
|
||
|
|
||
|
RtlCopyMemory( pNameCtrl->Name.Buffer,
|
||
|
localNameCtrl.Name.Buffer,
|
||
|
localNameCtrl.Name.Length );
|
||
|
|
||
|
pNameCtrl->Name.Length = localNameCtrl.Name.Length /*+
|
||
|
(USHORT)TokenLength*/; // token length is now added in cleanup
|
||
|
|
||
|
//
|
||
|
// Handle cleanup
|
||
|
//
|
||
|
|
||
|
Cleanup:
|
||
|
|
||
|
//
|
||
|
// Always restore the name length back to its original size -- adjust
|
||
|
// for TokenLength and StreamNameLength;
|
||
|
//
|
||
|
|
||
|
pNameCtrl->Name.Length += (USHORT)TokenLength;
|
||
|
pNameCtrl->Name.Length -= pNameCtrl->StreamNameLength;
|
||
|
|
||
|
if (pFileObject != NULL)
|
||
|
{
|
||
|
ObDereferenceObject(pFileObject);
|
||
|
NULLPTR(pFileObject);
|
||
|
}
|
||
|
|
||
|
if (FileHandle != NULL)
|
||
|
{
|
||
|
ZwClose(FileHandle);
|
||
|
NULLPTR(FileHandle);
|
||
|
}
|
||
|
|
||
|
SrpCleanupNameControl( &localNameCtrl );
|
||
|
|
||
|
#if DBG
|
||
|
if ((STATUS_MOUNT_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_OBJECT_PATH_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_INVALID == status) ||
|
||
|
(STATUS_REPARSE_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_NOT_SAME_DEVICE == status))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
VOID
|
||
|
SrpRemoveExtraDataFromStream (
|
||
|
PUNICODE_STRING pStreamComponent,
|
||
|
PUSHORT AmountToRemove
|
||
|
)
|
||
|
{
|
||
|
UNICODE_STRING dataName;
|
||
|
UNICODE_STRING endOfName;
|
||
|
|
||
|
ASSERT( pStreamComponent != NULL );
|
||
|
ASSERT( AmountToRemove != NULL );
|
||
|
|
||
|
*AmountToRemove = 0;
|
||
|
|
||
|
//
|
||
|
// Determine if we have an extra ":$DATA" to remove from the stream name.
|
||
|
//
|
||
|
|
||
|
RtlInitUnicodeString( &dataName, L":$DATA" );
|
||
|
|
||
|
if (pStreamComponent->Length >= dataName.Length)
|
||
|
{
|
||
|
endOfName.Buffer = &(pStreamComponent->Buffer[
|
||
|
(pStreamComponent->Length - dataName.Length) /
|
||
|
sizeof(WCHAR) ]);
|
||
|
endOfName.Length = dataName.Length;
|
||
|
endOfName.MaximumLength = dataName.Length;
|
||
|
|
||
|
//
|
||
|
// If the end of the stream name matches ":$DATA" strip it off
|
||
|
//
|
||
|
|
||
|
if (RtlEqualUnicodeString( &dataName, &endOfName, TRUE))
|
||
|
{
|
||
|
USHORT endOfStream;
|
||
|
|
||
|
*AmountToRemove += dataName.Length;
|
||
|
|
||
|
//
|
||
|
// We may still have one trailing ':' since
|
||
|
// filename.txt::$DATA is a valid way to open the default
|
||
|
// stream of a file.
|
||
|
//
|
||
|
|
||
|
if ((pStreamComponent->Length + dataName.Length) > 0)
|
||
|
{
|
||
|
endOfStream = ((pStreamComponent->Length - dataName.Length)/sizeof(WCHAR))-1;
|
||
|
|
||
|
if (pStreamComponent->Buffer[endOfStream] == L':')
|
||
|
{
|
||
|
*AmountToRemove += sizeof(L':');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This will look to see if the given file name has a stream name on it.
|
||
|
If so it will remove it from the string.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pNameControl - a structure used to manage the name of the file
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None
|
||
|
|
||
|
--***************************************************************************/
|
||
|
VOID
|
||
|
SrpRemoveStreamName (
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl
|
||
|
)
|
||
|
{
|
||
|
INT i;
|
||
|
INT countOfColons = 0;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
//
|
||
|
// search for a potential stream name to strip.
|
||
|
//
|
||
|
|
||
|
ASSERT(pNameCtrl->Name.Length > 0);
|
||
|
for ( i = (pNameCtrl->Name.Length / sizeof(WCHAR)) - 1;
|
||
|
i >= 0;
|
||
|
i -= 1 )
|
||
|
{
|
||
|
if (pNameCtrl->Name.Buffer[i] == L'\\')
|
||
|
{
|
||
|
//
|
||
|
// hit the end of the file part. stop looking
|
||
|
//
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
else if (pNameCtrl->Name.Buffer[i] == L':')
|
||
|
{
|
||
|
USHORT delta;
|
||
|
|
||
|
//
|
||
|
// Track the number of colons we see so that we know
|
||
|
// what we need to try to strip at the end.
|
||
|
//
|
||
|
|
||
|
countOfColons ++;
|
||
|
|
||
|
//
|
||
|
// strip the stream name (save how much we stripped)
|
||
|
//
|
||
|
|
||
|
delta = pNameCtrl->Name.Length - (USHORT)(i * sizeof(WCHAR));
|
||
|
|
||
|
pNameCtrl->StreamNameLength += delta;
|
||
|
pNameCtrl->Name.Length -= delta;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (countOfColons == 2)
|
||
|
{
|
||
|
UNICODE_STRING streamName;
|
||
|
USHORT amountToRemove = 0;
|
||
|
|
||
|
//
|
||
|
// We have an extra ":$DATA" to remove from the stream name.
|
||
|
//
|
||
|
|
||
|
streamName.Length = streamName.MaximumLength = pNameCtrl->StreamNameLength;
|
||
|
streamName.Buffer = pNameCtrl->Name.Buffer + (pNameCtrl->Name.Length/sizeof(WCHAR));
|
||
|
|
||
|
SrpRemoveExtraDataFromStream( &streamName,
|
||
|
&amountToRemove );
|
||
|
|
||
|
pNameCtrl->StreamNameLength -= amountToRemove;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine will construct a full nt path name for the target of a
|
||
|
rename or link operation. The name will be completely normalized for SR's
|
||
|
lookup and logging purposes.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - SR's device extension for the volume on which this file resides.
|
||
|
RootDirectory - Handle to the root directory for which this rename/link is
|
||
|
relative
|
||
|
pFileName - If this is rename\link that is relative to the original file,
|
||
|
this is the target name.
|
||
|
FileNameLength - The length in bytes of pFileName.
|
||
|
pOriginalFileContext - The file context for the file that is being renamed
|
||
|
or linked to.
|
||
|
pOriginalFileObject - The file object that is being renamed or linked to.
|
||
|
ppNewName - The normalized name that this function generates. The caller
|
||
|
is responsible for freeing the memory allocated.
|
||
|
pReasonableErrorForUnOpenedName - Set to TRUE if we hit an error during
|
||
|
the name normalization path that is reasonable since this operation
|
||
|
has not yet been validated by the file system.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - Completion status.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpExpandDestPath (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN HANDLE RootDirectory,
|
||
|
IN ULONG FileNameLength,
|
||
|
IN PWSTR pFileName,
|
||
|
IN PSR_STREAM_CONTEXT pOriginalFileContext,
|
||
|
IN PFILE_OBJECT pOriginalFileObject,
|
||
|
OUT PUNICODE_STRING *ppNewName,
|
||
|
OUT PUSHORT pNewNameStreamLength,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
UNICODE_STRING NewName;
|
||
|
ULONG TokenLength;
|
||
|
PWSTR pToken;
|
||
|
PFILE_OBJECT pDirectory = NULL;
|
||
|
SRP_NAME_CONTROL newNameCtrl;
|
||
|
ULONG fullNameLength;
|
||
|
UNICODE_STRING OrigName;
|
||
|
SRP_NAME_CONTROL originalNameCtrl;
|
||
|
BOOLEAN freeOriginalNameCtrl = FALSE;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(IS_VALID_SR_DEVICE_EXTENSION(pExtension));
|
||
|
ASSERT(pFileName != NULL);
|
||
|
ASSERT(pOriginalFileContext != NULL);
|
||
|
ASSERT(ppNewName != NULL);
|
||
|
ASSERT(pNewNameStreamLength != NULL);
|
||
|
|
||
|
//
|
||
|
// Initialize state
|
||
|
//
|
||
|
|
||
|
*ppNewName = NULL;
|
||
|
*pNewNameStreamLength = 0;
|
||
|
SrpInitNameControl( &newNameCtrl );
|
||
|
|
||
|
//
|
||
|
// fill in the new name to a UNICODE_STRING
|
||
|
//
|
||
|
|
||
|
NewName.Length = (USHORT)FileNameLength;
|
||
|
NewName.MaximumLength = (USHORT)FileNameLength;
|
||
|
NewName.Buffer = pFileName;
|
||
|
|
||
|
//
|
||
|
// construct a fully qualified name we can use to open the parent
|
||
|
// dir
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// is this a directory relative op?
|
||
|
//
|
||
|
|
||
|
if (RootDirectory != NULL)
|
||
|
{
|
||
|
//
|
||
|
// reference the directory file object
|
||
|
//
|
||
|
|
||
|
status = ObReferenceObjectByHandle( RootDirectory,
|
||
|
0,
|
||
|
*IoFileObjectType,
|
||
|
KernelMode,
|
||
|
(PVOID *) &pDirectory,
|
||
|
NULL );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// get path name for the directory
|
||
|
//
|
||
|
|
||
|
status = SrpGetFileName( pExtension,
|
||
|
pDirectory,
|
||
|
&newNameCtrl );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
fullNameLength = newNameCtrl.Name.Length +
|
||
|
NewName.Length +
|
||
|
sizeof(WCHAR); //space for seperator
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( &newNameCtrl, fullNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status )) {
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Slap on the relative part now
|
||
|
//
|
||
|
// We may need to add a slash separator if it is missing.
|
||
|
//
|
||
|
|
||
|
if ((NewName.Buffer[0] != L'\\') &&
|
||
|
(NewName.Buffer[0] != L':') &&
|
||
|
(newNameCtrl.Name.Buffer[(newNameCtrl.Name.Length/sizeof(WCHAR))-1] != L'\\'))
|
||
|
{
|
||
|
newNameCtrl.Name.Buffer[newNameCtrl.Name.Length/sizeof(WCHAR)] = L'\\';
|
||
|
newNameCtrl.Name.Length += sizeof(WCHAR);
|
||
|
}
|
||
|
|
||
|
RtlAppendUnicodeStringToString( &newNameCtrl.Name,
|
||
|
&NewName );
|
||
|
|
||
|
//
|
||
|
// done with the object now
|
||
|
//
|
||
|
|
||
|
ObDereferenceObject(pDirectory);
|
||
|
pDirectory = NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// is this a same directory rename\link creation ?
|
||
|
//
|
||
|
|
||
|
else if (NewName.Buffer[0] != L'\\')
|
||
|
{
|
||
|
PUNICODE_STRING pOriginalName;
|
||
|
|
||
|
ASSERT(RootDirectory == NULL);
|
||
|
|
||
|
if (!FlagOn( pOriginalFileContext->Flags, CTXFL_IsInteresting ))
|
||
|
{
|
||
|
//
|
||
|
// We don't have a name for this file, so generate the fully
|
||
|
// qualified name.
|
||
|
//
|
||
|
|
||
|
SrpInitNameControl( &originalNameCtrl );
|
||
|
freeOriginalNameCtrl = TRUE;
|
||
|
|
||
|
status = SrpGetFileName( pExtension,
|
||
|
pOriginalFileObject,
|
||
|
&originalNameCtrl );
|
||
|
|
||
|
if (!NT_SUCCESS( status )) {
|
||
|
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
pOriginalName = &(originalNameCtrl.Name);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// This file is interesting, so we have a name in the context.
|
||
|
//
|
||
|
|
||
|
pOriginalName = &(pOriginalFileContext->FileName);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We are doing either a same directory rename/link or renaming a
|
||
|
// stream of this file. We can figure out which by looking for a ':'
|
||
|
// as the first character in the NewName. In either case,
|
||
|
// NewName should have no '\'s in it.
|
||
|
|
||
|
|
||
|
status = SrFindCharReverse( NewName.Buffer,
|
||
|
NewName.Length,
|
||
|
L'\\',
|
||
|
&pToken,
|
||
|
&TokenLength );
|
||
|
|
||
|
if (status != STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
status = STATUS_OBJECT_NAME_INVALID;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
if (NewName.Buffer[0] == ':')
|
||
|
{
|
||
|
USHORT CurrentFileNameLength;
|
||
|
USHORT AmountToRemove = 0;
|
||
|
|
||
|
//
|
||
|
// We are renaming a stream on this file. This is the easy
|
||
|
// case because we can build up the new name without any more
|
||
|
// parsing of the original name.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// NewName currently contains the new stream name component. It
|
||
|
// may have the extra $DATA at the end of the stream name
|
||
|
// and we want to strip that part off.
|
||
|
//
|
||
|
|
||
|
SrpRemoveExtraDataFromStream( &NewName,
|
||
|
&AmountToRemove );
|
||
|
|
||
|
NewName.Length -= AmountToRemove;
|
||
|
|
||
|
//
|
||
|
// Calculate the full length of the name with stream and upgrade
|
||
|
// our buffer if we need to.
|
||
|
//
|
||
|
|
||
|
fullNameLength = pOriginalName->Length + NewName.Length;
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( &newNameCtrl, fullNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// insert the orignal file name into the string
|
||
|
//
|
||
|
|
||
|
RtlCopyUnicodeString( &newNameCtrl.Name,
|
||
|
pOriginalName );
|
||
|
|
||
|
//
|
||
|
// Append the stream name component on, but remember the current
|
||
|
// length of the file name, since will will restore that after
|
||
|
// the append operation to maintain our file name format.
|
||
|
//
|
||
|
|
||
|
CurrentFileNameLength = newNameCtrl.Name.Length;
|
||
|
|
||
|
RtlAppendUnicodeStringToString( &newNameCtrl.Name,
|
||
|
&NewName );
|
||
|
|
||
|
newNameCtrl.Name.Length = CurrentFileNameLength;
|
||
|
newNameCtrl.StreamNameLength = NewName.Length;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// get the length of the filename portion of the source
|
||
|
// path
|
||
|
//
|
||
|
|
||
|
status = SrFindCharReverse( pOriginalName->Buffer,
|
||
|
pOriginalName->Length,
|
||
|
L'\\',
|
||
|
&pToken,
|
||
|
&TokenLength );
|
||
|
|
||
|
if (!NT_SUCCESS(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Leave the prefix character ('\') on the path
|
||
|
//
|
||
|
|
||
|
TokenLength -= sizeof(WCHAR);
|
||
|
|
||
|
ASSERT(pOriginalName->Length > TokenLength);
|
||
|
OrigName.Length = (USHORT) (pOriginalName->Length - TokenLength);
|
||
|
OrigName.MaximumLength = OrigName.Length;
|
||
|
OrigName.Buffer = pOriginalName->Buffer;
|
||
|
|
||
|
//
|
||
|
// Calculate the full length of the name and upgrade our
|
||
|
// buffer if we need to.
|
||
|
//
|
||
|
|
||
|
fullNameLength = OrigName.Length + NewName.Length;
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( &newNameCtrl, fullNameLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// insert the orignal file name into the string
|
||
|
//
|
||
|
|
||
|
RtlCopyUnicodeString( &newNameCtrl.Name,
|
||
|
&OrigName );
|
||
|
|
||
|
//
|
||
|
// Append the new name on
|
||
|
//
|
||
|
|
||
|
RtlAppendUnicodeStringToString( &newNameCtrl.Name,
|
||
|
&NewName );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(NewName.Buffer[0] == L'\\');
|
||
|
|
||
|
//
|
||
|
// it's already fully quailifed, simply allocate a buffer and
|
||
|
// copy it in so we can post process expand mount points and
|
||
|
// shortnames
|
||
|
//
|
||
|
|
||
|
status = SrpNameCtrlBufferCheck( &newNameCtrl, NewName.Length );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Copy the name into the buffer
|
||
|
//
|
||
|
|
||
|
RtlCopyUnicodeString( &newNameCtrl.Name,
|
||
|
&NewName );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// NULL terminate the name
|
||
|
//
|
||
|
|
||
|
ASSERT(newNameCtrl.Name.Length > 0);
|
||
|
|
||
|
//
|
||
|
// Since this may be a raw path name from the user, try and expand
|
||
|
// the path so that we will eliminate the mount points. After this
|
||
|
// call, the name will be normalized to
|
||
|
// \Device\HarddiskVolume1\[fullpath]
|
||
|
//
|
||
|
|
||
|
status = SrpExpandPathOfFileName( pExtension,
|
||
|
&newNameCtrl,
|
||
|
pReasonableErrorForUnOpenedName );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now expand any shortnames in the path
|
||
|
//
|
||
|
|
||
|
status = SrpExpandShortNames( pExtension,
|
||
|
&newNameCtrl,
|
||
|
FALSE );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK(status))
|
||
|
{
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate a string buffer to return and copy the name to it
|
||
|
//
|
||
|
|
||
|
*ppNewName = ExAllocatePoolWithTag( PagedPool,
|
||
|
sizeof( UNICODE_STRING ) +
|
||
|
newNameCtrl.Name.Length +
|
||
|
newNameCtrl.StreamNameLength,
|
||
|
SR_FILENAME_BUFFER_TAG );
|
||
|
|
||
|
if (NULL == *ppNewName)
|
||
|
{
|
||
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto Cleanup;
|
||
|
}
|
||
|
|
||
|
(*ppNewName)->MaximumLength = newNameCtrl.Name.Length + newNameCtrl.StreamNameLength;
|
||
|
(*ppNewName)->Buffer = (PWCHAR)((PWCHAR)((*ppNewName) + 1));
|
||
|
|
||
|
//
|
||
|
// Since we need to copy the steam information also, do the copy
|
||
|
// ourselves here instead of relying on the Unicode function.
|
||
|
//
|
||
|
|
||
|
RtlCopyMemory( (*ppNewName)->Buffer,
|
||
|
newNameCtrl.Name.Buffer,
|
||
|
(*ppNewName)->MaximumLength );
|
||
|
|
||
|
(*ppNewName)->Length = newNameCtrl.Name.Length;
|
||
|
*pNewNameStreamLength = newNameCtrl.StreamNameLength;
|
||
|
|
||
|
//
|
||
|
// Handle cleanup of state
|
||
|
//
|
||
|
|
||
|
Cleanup:
|
||
|
if (pDirectory != NULL)
|
||
|
{
|
||
|
ObDereferenceObject(pDirectory);
|
||
|
NULLPTR(pDirectory);
|
||
|
}
|
||
|
|
||
|
SrpCleanupNameControl( &newNameCtrl );
|
||
|
|
||
|
if (freeOriginalNameCtrl)
|
||
|
{
|
||
|
SrpCleanupNameControl( &originalNameCtrl );
|
||
|
}
|
||
|
|
||
|
#if DBG
|
||
|
if ((STATUS_MOUNT_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_OBJECT_PATH_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_NOT_FOUND == status) ||
|
||
|
(STATUS_OBJECT_NAME_INVALID == status) ||
|
||
|
(STATUS_REPARSE_POINT_NOT_RESOLVED == status) ||
|
||
|
(STATUS_NOT_SAME_DEVICE == status))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This will initialize the name control structure
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None
|
||
|
|
||
|
--***************************************************************************/
|
||
|
VOID
|
||
|
SrpInitNameControl (
|
||
|
IN PSRP_NAME_CONTROL pNameCtrl
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
pNameCtrl->AllocatedBuffer = NULL;
|
||
|
pNameCtrl->StreamNameLength = 0;
|
||
|
pNameCtrl->BufferSize = sizeof(pNameCtrl->SmallBuffer);
|
||
|
RtlInitEmptyUnicodeString( &pNameCtrl->Name,
|
||
|
(PWCHAR)pNameCtrl->SmallBuffer,
|
||
|
pNameCtrl->BufferSize );
|
||
|
//pNameCtrl->Name.Buffer[0] = UNICODE_NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This will cleanup the name control structure
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None
|
||
|
|
||
|
--***************************************************************************/
|
||
|
VOID
|
||
|
SrpCleanupNameControl (
|
||
|
IN PSRP_NAME_CONTROL pNameCtrl
|
||
|
)
|
||
|
{
|
||
|
PAGED_CODE();
|
||
|
|
||
|
if (NULL != pNameCtrl->AllocatedBuffer)
|
||
|
{
|
||
|
ExFreePool( pNameCtrl->AllocatedBuffer );
|
||
|
pNameCtrl->AllocatedBuffer = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine will allocate a new larger name buffer and put it into the
|
||
|
NameControl structure. If there is already an allocated buffer it will
|
||
|
be freed. It will also copy any name information from the old buffer
|
||
|
into the new buffer.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pNameCtrl - the name control we need a bigger buffer for
|
||
|
newSize - size of the new buffer
|
||
|
retOrignalBuffer - if defined, receives the buffer that we were
|
||
|
going to free. if NULL was returned no buffer
|
||
|
needed to be freed.
|
||
|
WARNING: if this parameter is defined and a
|
||
|
non-null value is returned then the
|
||
|
call MUST free this memory else the
|
||
|
memory will be lost.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpReallocNameControl (
|
||
|
IN PSRP_NAME_CONTROL pNameCtrl,
|
||
|
ULONG newSize,
|
||
|
PWCHAR *retOriginalBuffer OPTIONAL
|
||
|
)
|
||
|
{
|
||
|
PCHAR newBuffer;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(newSize > pNameCtrl->BufferSize);
|
||
|
|
||
|
//
|
||
|
// Flag no buffer to return yet
|
||
|
//
|
||
|
|
||
|
if (retOriginalBuffer)
|
||
|
{
|
||
|
*retOriginalBuffer = NULL;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate the new buffer
|
||
|
//
|
||
|
|
||
|
newBuffer = ExAllocatePoolWithTag( PagedPool,
|
||
|
newSize,
|
||
|
SR_FILENAME_BUFFER_TAG );
|
||
|
|
||
|
if (NULL == newBuffer)
|
||
|
{
|
||
|
return STATUS_INSUFFICIENT_RESOURCES;
|
||
|
}
|
||
|
|
||
|
SrTrace( CONTEXT_LOG, ("Sr!SrpReallocNameControl: Realloc: (%p) oldSz=%05x newSz=%05x \"%.*S\"\n",
|
||
|
pNameCtrl,
|
||
|
pNameCtrl->BufferSize,
|
||
|
newSize,
|
||
|
(pNameCtrl->Name.Length+
|
||
|
pNameCtrl->StreamNameLength)/sizeof(WCHAR),
|
||
|
pNameCtrl->Name.Buffer));
|
||
|
//
|
||
|
// Copy data from old buffer if there is any, including any stream
|
||
|
// name component.
|
||
|
//
|
||
|
|
||
|
if ((pNameCtrl->Name.Length + pNameCtrl->StreamNameLength) > 0)
|
||
|
{
|
||
|
ASSERT(newSize > (USHORT)(pNameCtrl->Name.Length + pNameCtrl->StreamNameLength));
|
||
|
RtlCopyMemory( newBuffer,
|
||
|
pNameCtrl->Name.Buffer,
|
||
|
(pNameCtrl->Name.Length + pNameCtrl->StreamNameLength) );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we had an old buffer free it if the caller doesn't want
|
||
|
// it passed back to him. This is done because there are
|
||
|
// cases where the caller has a pointer into the old buffer so
|
||
|
// it can't be freed yet. The caller must free this memory.
|
||
|
//
|
||
|
|
||
|
if (NULL != pNameCtrl->AllocatedBuffer)
|
||
|
{
|
||
|
if (retOriginalBuffer)
|
||
|
{
|
||
|
*retOriginalBuffer = (PWCHAR)pNameCtrl->AllocatedBuffer;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ExFreePool(pNameCtrl->AllocatedBuffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the new buffer into the name control
|
||
|
//
|
||
|
|
||
|
pNameCtrl->AllocatedBuffer = newBuffer;
|
||
|
pNameCtrl->BufferSize = newSize;
|
||
|
|
||
|
pNameCtrl->Name.Buffer = (PWCHAR)newBuffer;
|
||
|
pNameCtrl->Name.MaximumLength = (USHORT)newSize;
|
||
|
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
This routine does the following things:
|
||
|
- Get the FULL path name of the given file object
|
||
|
- Will expand any short names in the path to long names
|
||
|
- Will remove any stream names.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - Completion status.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrpExpandFileName (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN ULONG EventFlags,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
//
|
||
|
// If we are in pre-create, use the name in the FILE_OBJECT. Also if
|
||
|
// there is no related file object and the name starts with a slash,
|
||
|
// just use the name in the FILE_OBJECT.
|
||
|
//
|
||
|
|
||
|
if (FlagOn( EventFlags, SrEventOpenById ))
|
||
|
{
|
||
|
status = SrpGetFileNameOpenById( pExtension,
|
||
|
pFileObject,
|
||
|
pNameCtrl,
|
||
|
pReasonableErrorForUnOpenedName);
|
||
|
|
||
|
}
|
||
|
else if (FlagOn( EventFlags, SrEventInPreCreate ))
|
||
|
{
|
||
|
status = SrpGetFileNameFromFileObject( pExtension,
|
||
|
pFileObject,
|
||
|
pNameCtrl,
|
||
|
pReasonableErrorForUnOpenedName );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// Ask the file system for the name
|
||
|
//
|
||
|
|
||
|
status = SrpGetFileName( pExtension,
|
||
|
pFileObject,
|
||
|
pNameCtrl );
|
||
|
}
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
//
|
||
|
// Remove the stream name from the file name (if defined)
|
||
|
//
|
||
|
|
||
|
SrpRemoveStreamName( pNameCtrl );
|
||
|
|
||
|
//
|
||
|
// Expand any short names in the filename
|
||
|
//
|
||
|
|
||
|
status = SrpExpandShortNames( pExtension,
|
||
|
pNameCtrl,
|
||
|
TRUE );
|
||
|
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This will see if we care about this file. During this process it looks
|
||
|
up the full file name and returns it.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the extension for the device this file is on
|
||
|
pFileObject - the fileobject being handled
|
||
|
IsDirectory - TRUE if this is a directory, else FALSE
|
||
|
EventFlags - The flags portion of the current event. This is used to
|
||
|
determine if we are in the pre-create path or if this file is being
|
||
|
opened by file id.
|
||
|
pNameControl - a structure used to manage the name of the file
|
||
|
pIsInteresting - returns if this file should be monitored
|
||
|
pReasonableErrorForUnOpenedName -
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
NTSTATUS - Completion status
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrIsFileEligible (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN BOOLEAN IsDirectory,
|
||
|
IN SR_EVENT_TYPE EventFlags,
|
||
|
IN OUT PSRP_NAME_CONTROL pNameCtrl,
|
||
|
OUT PBOOLEAN pIsInteresting,
|
||
|
OUT PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
|
||
|
PAGED_CODE();
|
||
|
|
||
|
ASSERT(IS_VALID_SR_DEVICE_EXTENSION(pExtension));
|
||
|
ASSERT(IS_VALID_FILE_OBJECT(pFileObject));
|
||
|
|
||
|
//
|
||
|
// Assume the file is NOT interesting
|
||
|
//
|
||
|
|
||
|
*pIsInteresting = FALSE;
|
||
|
|
||
|
//
|
||
|
// is anyone monitoring this system at all?
|
||
|
//
|
||
|
|
||
|
if (!SR_LOGGING_ENABLED(pExtension))
|
||
|
{
|
||
|
return SR_STATUS_VOLUME_DISABLED;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// have we loaded the file config information
|
||
|
//
|
||
|
|
||
|
if (!_globals.BlobInfoLoaded)
|
||
|
{
|
||
|
status = SrReadBlobInfo();
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( status ))
|
||
|
{
|
||
|
ASSERT(!_globals.BlobInfoLoaded);
|
||
|
|
||
|
//
|
||
|
// We couldn't load the lookup blob for some reason, but
|
||
|
// we have already handled the error so mark that this is a
|
||
|
// resonable error, so just let the error propogate out.
|
||
|
//
|
||
|
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
ASSERT(_globals.BlobInfoLoaded);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Always query the name
|
||
|
//
|
||
|
|
||
|
status = SrpExpandFileName( pExtension,
|
||
|
pFileObject,
|
||
|
EventFlags,
|
||
|
pNameCtrl,
|
||
|
pReasonableErrorForUnOpenedName );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if this file name is longer than SR_MAX_PATH. If so,
|
||
|
// this file is not interesting.
|
||
|
//
|
||
|
|
||
|
if (!IS_FILENAME_VALID_LENGTH( pExtension,
|
||
|
&(pNameCtrl->Name),
|
||
|
pNameCtrl->StreamNameLength ))
|
||
|
{
|
||
|
*pIsInteresting = FALSE;
|
||
|
return STATUS_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// is this is a file, check the extension for a match
|
||
|
//
|
||
|
|
||
|
if (!IsDirectory)
|
||
|
{
|
||
|
//
|
||
|
// does this extension match? do this first as we can do this
|
||
|
// pretty quick like
|
||
|
//
|
||
|
|
||
|
status = SrIsExtInteresting( &pNameCtrl->Name,
|
||
|
pIsInteresting );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Is this not interesting
|
||
|
//
|
||
|
|
||
|
if (!*pIsInteresting)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check to see if this file has a stream component. If so,
|
||
|
// we need to check to see if this is a named stream on a
|
||
|
// file or directory. We are only interested in streams on
|
||
|
// files.
|
||
|
//
|
||
|
|
||
|
if (pNameCtrl->StreamNameLength > 0)
|
||
|
{
|
||
|
status = SrIsFileStream( pExtension,
|
||
|
pNameCtrl,
|
||
|
pIsInteresting,
|
||
|
pReasonableErrorForUnOpenedName );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( status ) || !*pIsInteresting)
|
||
|
{
|
||
|
return status;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// see if this is a file that we should monitor?
|
||
|
//
|
||
|
|
||
|
status = SrIsPathInteresting( &pNameCtrl->Name,
|
||
|
pExtension->pNtVolumeName,
|
||
|
IsDirectory,
|
||
|
pIsInteresting );
|
||
|
|
||
|
RETURN(status);
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine does a quick scan of the file object's name to see if it
|
||
|
contains the stream name delimiter ':'.
|
||
|
|
||
|
Note: This routine assumes that the name in the file object is valid,
|
||
|
therefore this routine should only be called from SrCreate.
|
||
|
|
||
|
Note2: We only need to rely on the name components in the
|
||
|
pFileObject->FileName field because for our purposes this is sufficient.
|
||
|
If this filed doesn't contain the ':' delimiter, we are either not
|
||
|
opening a stream or we are doing a self-relative open of a stream
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the SR extension the current volume
|
||
|
pFileObject - the current fileobject to be opened
|
||
|
pFileContext - if provided, we will get the name from the context
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Returns TRUE if the file name contains a steam delimiter or FALSE otherwise.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
BOOLEAN
|
||
|
SrFileNameContainsStream (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN PSR_STREAM_CONTEXT pFileContext OPTIONAL
|
||
|
)
|
||
|
{
|
||
|
PUNICODE_STRING pFileName;
|
||
|
NTSTATUS status;
|
||
|
PWCHAR pToken;
|
||
|
ULONG tokenLength;
|
||
|
|
||
|
ASSERT( IS_VALID_SR_DEVICE_EXTENSION( pExtension ) );
|
||
|
ASSERT( IS_VALID_FILE_OBJECT( pFileObject ) );
|
||
|
|
||
|
//
|
||
|
// If we've already cached the attributes of this volume, do a quick
|
||
|
// check to see if this FS supports named streams. If not, we don't need
|
||
|
// to do any more work here.
|
||
|
//
|
||
|
|
||
|
if (pExtension->CachedFsAttributes &&
|
||
|
!FlagOn( pExtension->FsAttributes, FILE_NAMED_STREAMS ))
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (pFileContext != NULL)
|
||
|
{
|
||
|
//
|
||
|
// If we've got a pFileContext, it has all the stream information
|
||
|
// in it already, so just use that.
|
||
|
//
|
||
|
|
||
|
if (pFileContext->StreamNameLength == 0)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pFileName = &(pFileObject->FileName);
|
||
|
|
||
|
status = SrFindCharReverse( pFileName->Buffer,
|
||
|
pFileName->Length,
|
||
|
L':',
|
||
|
&pToken,
|
||
|
&tokenLength );
|
||
|
|
||
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
//
|
||
|
// We didn't find a ':', therefore this doen't have a stream component
|
||
|
// in the name.
|
||
|
//
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
else if (status == STATUS_SUCCESS)
|
||
|
{
|
||
|
//
|
||
|
// We found a ':', so there is a stream component in this name.
|
||
|
//
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// We should never reach this path.
|
||
|
//
|
||
|
|
||
|
ASSERT( FALSE );
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine opens the file-only component of the file name (ignoring
|
||
|
any stream component) to see if the unnamed data stream for this file
|
||
|
already exists.
|
||
|
|
||
|
Note: This routine assumes that the name in the file object is valid,
|
||
|
therefore this routine should only be called from SrCreate.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the SR extension the current volume
|
||
|
pFileObject - the current fileobject to be opened
|
||
|
pFileContext - if provided, we will get the name from the context
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Returns TRUE if the file name contains a steam delimiter or FALSE otherwise.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
BOOLEAN
|
||
|
SrFileAlreadyExists (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN PFILE_OBJECT pFileObject,
|
||
|
IN PSR_STREAM_CONTEXT pFileContext OPTIONAL
|
||
|
)
|
||
|
{
|
||
|
SRP_NAME_CONTROL nameCtrl;
|
||
|
BOOLEAN cleanupNameCtrl = FALSE;
|
||
|
BOOLEAN reasonableError;
|
||
|
NTSTATUS status;
|
||
|
BOOLEAN unnamedStreamExists = FALSE;
|
||
|
OBJECT_ATTRIBUTES objectAttributes;
|
||
|
HANDLE fileHandle = NULL;
|
||
|
IO_STATUS_BLOCK ioStatus;
|
||
|
PUNICODE_STRING pFileName;
|
||
|
|
||
|
if (pFileContext == NULL)
|
||
|
{
|
||
|
SrpInitNameControl( &nameCtrl );
|
||
|
cleanupNameCtrl = TRUE;
|
||
|
|
||
|
status = SrpGetFileNameFromFileObject( pExtension,
|
||
|
pFileObject,
|
||
|
&nameCtrl,
|
||
|
&reasonableError );
|
||
|
|
||
|
if (!NT_SUCCESS_NO_DBGBREAK( status ))
|
||
|
{
|
||
|
goto SrFileAlreadyHasUnnamedStream_Exit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remove the stream name from the file name (if defined)
|
||
|
//
|
||
|
|
||
|
SrpRemoveStreamName( &nameCtrl );
|
||
|
pFileName = &(nameCtrl.Name);
|
||
|
|
||
|
//
|
||
|
// The stream component just resolved down to the default data stream,
|
||
|
// go exit now without doing the open.
|
||
|
//
|
||
|
|
||
|
if (nameCtrl.StreamNameLength == 0)
|
||
|
{
|
||
|
goto SrFileAlreadyHasUnnamedStream_Exit;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pFileName = &(pFileContext->FileName);
|
||
|
|
||
|
//
|
||
|
// This name should have a stream component, that's the reason we are
|
||
|
// in this path.
|
||
|
//
|
||
|
|
||
|
ASSERT( pFileContext->StreamNameLength > 0 );
|
||
|
}
|
||
|
|
||
|
|
||
|
InitializeObjectAttributes( &objectAttributes,
|
||
|
pFileName,
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
status = SrIoCreateFile( &fileHandle,
|
||
|
FILE_READ_ATTRIBUTES,
|
||
|
&objectAttributes,
|
||
|
&ioStatus,
|
||
|
NULL,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
||
|
FILE_OPEN,
|
||
|
0,
|
||
|
NULL,
|
||
|
0,
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
pExtension->pTargetDevice );
|
||
|
|
||
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
//
|
||
|
// The unnamed data stream doesn't exist, so the creation of this
|
||
|
// stream is also going to created the unnamed data stream on this
|
||
|
// file.
|
||
|
//
|
||
|
|
||
|
unnamedStreamExists = FALSE;
|
||
|
}
|
||
|
else if (status == STATUS_SUCCESS)
|
||
|
{
|
||
|
//
|
||
|
// The unnamed data stream does exist, so the creation of this
|
||
|
// stream is just going to create a new stream on this file.
|
||
|
//
|
||
|
|
||
|
unnamedStreamExists = TRUE;
|
||
|
}
|
||
|
else if (status == STATUS_DELETE_PENDING)
|
||
|
{
|
||
|
//
|
||
|
// This file already exists but is about to be deleted.
|
||
|
//
|
||
|
|
||
|
unnamedStreamExists = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CHECK_STATUS( status );
|
||
|
}
|
||
|
|
||
|
SrFileAlreadyHasUnnamedStream_Exit:
|
||
|
|
||
|
if (fileHandle != NULL)
|
||
|
{
|
||
|
ZwClose( fileHandle );
|
||
|
}
|
||
|
|
||
|
if (cleanupNameCtrl)
|
||
|
{
|
||
|
SrpCleanupNameControl( &nameCtrl );
|
||
|
}
|
||
|
|
||
|
return unnamedStreamExists;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine determines if a name containing a stream component is a
|
||
|
named stream on a directory or on a file. To this this, this routine opens
|
||
|
the current file name ignoring any stream component in the name.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - the SR extension the current volume
|
||
|
pNameCtrl - the SRP_NAME_CTRL structure that has the complete name.
|
||
|
pIsFileStream - set to TRUE if this is a stream on a file, or FALSE if
|
||
|
this is a stream on a directory.
|
||
|
pReasonableErrorForUnOpenedName - set to TRUE if we hit an error trying
|
||
|
to do this open.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Returns STATUS_SUCCESS if we were able to determine if the parent to the
|
||
|
stream is a file or directory, or the error we hit in the open path
|
||
|
otherwise.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrIsFileStream (
|
||
|
PSR_DEVICE_EXTENSION pExtension,
|
||
|
PSRP_NAME_CONTROL pNameCtrl,
|
||
|
PBOOLEAN pIsFileStream,
|
||
|
PBOOLEAN pReasonableErrorForUnOpenedName
|
||
|
)
|
||
|
{
|
||
|
OBJECT_ATTRIBUTES objectAttributes;
|
||
|
HANDLE fileHandle = NULL;
|
||
|
NTSTATUS status;
|
||
|
IO_STATUS_BLOCK ioStatus;
|
||
|
|
||
|
ASSERT( pIsFileStream != NULL );
|
||
|
ASSERT( pReasonableErrorForUnOpenedName != NULL );
|
||
|
|
||
|
*pReasonableErrorForUnOpenedName = FALSE;
|
||
|
|
||
|
InitializeObjectAttributes( &objectAttributes,
|
||
|
&(pNameCtrl->Name),
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
status = SrIoCreateFile( &fileHandle,
|
||
|
FILE_READ_ATTRIBUTES,
|
||
|
&objectAttributes,
|
||
|
&ioStatus,
|
||
|
NULL,
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
||
|
FILE_OPEN,
|
||
|
FILE_NON_DIRECTORY_FILE,
|
||
|
NULL,
|
||
|
0,
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
pExtension->pTargetDevice );
|
||
|
|
||
|
if (status == STATUS_FILE_IS_A_DIRECTORY)
|
||
|
{
|
||
|
status = STATUS_SUCCESS;
|
||
|
*pIsFileStream = FALSE;
|
||
|
}
|
||
|
else if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
//
|
||
|
// We must be creating a new file with this stream operation,
|
||
|
// therefore the parent of this stream must be a file and not
|
||
|
// a directory.
|
||
|
//
|
||
|
|
||
|
status = STATUS_SUCCESS;
|
||
|
*pIsFileStream = TRUE;
|
||
|
}
|
||
|
else if (!NT_SUCCESS_NO_DBGBREAK( status ))
|
||
|
{
|
||
|
*pReasonableErrorForUnOpenedName = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pIsFileStream = TRUE;
|
||
|
}
|
||
|
|
||
|
if (fileHandle)
|
||
|
{
|
||
|
ZwClose( fileHandle );
|
||
|
}
|
||
|
|
||
|
RETURN( status );
|
||
|
}
|
||
|
|
||
|
/***************************************************************************++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This routine checks to see if the long name for this file was tunneled. If
|
||
|
so, the user could have opened the file by its short name, but it will have
|
||
|
a long name associated with it. For correctness, we need to log operations
|
||
|
on this file via the long name.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
pExtension - The SR extension the current volume
|
||
|
ppFileContext - This reference parameter passes in the current file context
|
||
|
for this file and may get replaced with a new file context if we need
|
||
|
to replace the name in this context. If this is the case, this
|
||
|
routine will properly cleanup the context passed in and the caller is
|
||
|
responsible for cleaning up the context passed out.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Returns STATUS_SUCCESS if the check for tunneling was successful and the
|
||
|
ppFileContext was updated as needed. If there was some error, the
|
||
|
appropriate error status is returned. Just like when we create our
|
||
|
original contexts, if an error occurs while doing this work, we must
|
||
|
generate a volume error and go into pass through mode. This routine will
|
||
|
generation the volume error and the caller should just get out of this
|
||
|
IO path.
|
||
|
|
||
|
--***************************************************************************/
|
||
|
NTSTATUS
|
||
|
SrCheckForNameTunneling (
|
||
|
IN PSR_DEVICE_EXTENSION pExtension,
|
||
|
IN OUT PSR_STREAM_CONTEXT *ppFileContext
|
||
|
)
|
||
|
{
|
||
|
NTSTATUS status;
|
||
|
PWCHAR pFileNameComponentBuffer = NULL;
|
||
|
ULONG FileNameComponentLength = 0;
|
||
|
PWCHAR pTildaPosition = NULL;
|
||
|
ULONG TildaPositionLength;
|
||
|
HANDLE parentDirectory = NULL;
|
||
|
PSR_STREAM_CONTEXT pOrigCtx;
|
||
|
|
||
|
ASSERT( ppFileContext != NULL );
|
||
|
|
||
|
pOrigCtx = *ppFileContext;
|
||
|
ASSERT( pOrigCtx != NULL);
|
||
|
|
||
|
//
|
||
|
// First, see if this file is interesting and if this pFileObject
|
||
|
// represents a file. Name tunneling is not done on directory names.
|
||
|
//
|
||
|
|
||
|
if (FlagOn( pOrigCtx->Flags, CTXFL_IsDirectory ) ||
|
||
|
!FlagOn( pOrigCtx->Flags, CTXFL_IsInteresting ))
|
||
|
{
|
||
|
status = STATUS_SUCCESS;
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Find the file name component of name we have in pOrigCtx.
|
||
|
//
|
||
|
|
||
|
status = SrFindCharReverse( pOrigCtx->FileName.Buffer,
|
||
|
pOrigCtx->FileName.Length,
|
||
|
L'\\',
|
||
|
&pFileNameComponentBuffer,
|
||
|
&FileNameComponentLength );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
ASSERT( FileNameComponentLength > sizeof( L'\\' ) );
|
||
|
ASSERT( pFileNameComponentBuffer[0] == L'\\' );
|
||
|
|
||
|
//
|
||
|
// Move past the leading '\' of the file name since we want to keep that
|
||
|
// with the parent directory name.
|
||
|
//
|
||
|
|
||
|
pFileNameComponentBuffer ++;
|
||
|
FileNameComponentLength -= sizeof( WCHAR );
|
||
|
|
||
|
//
|
||
|
// We've got the file name component. Now see if it is a candidate for
|
||
|
// tunneling of the long name. It will be a candidate if:
|
||
|
// * The name is (SR_SHORT_NAME_CHARS) or less.
|
||
|
// * The name contains a '~'.
|
||
|
//
|
||
|
|
||
|
if (FileNameComponentLength > ((SR_SHORT_NAME_CHARS) * sizeof (WCHAR)))
|
||
|
{
|
||
|
//
|
||
|
// This name is too long to be a short name. We're done.
|
||
|
//
|
||
|
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
status = SrFindCharReverse( pFileNameComponentBuffer,
|
||
|
FileNameComponentLength,
|
||
|
L'~',
|
||
|
&pTildaPosition,
|
||
|
&TildaPositionLength );
|
||
|
|
||
|
if (status == STATUS_OBJECT_NAME_NOT_FOUND)
|
||
|
{
|
||
|
//
|
||
|
// This name doesn't have a '~' therefore, it cannot be a short
|
||
|
// name.
|
||
|
//
|
||
|
|
||
|
status = STATUS_SUCCESS;
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OBJECT_ATTRIBUTES objectAttributes;
|
||
|
UNICODE_STRING fileNameComponent;
|
||
|
UNICODE_STRING parentDirectoryName;
|
||
|
UNICODE_STRING fsFileName;
|
||
|
IO_STATUS_BLOCK ioStatus;
|
||
|
PFILE_BOTH_DIR_INFORMATION pFileBothDirInfo;
|
||
|
# define FILE_BOTH_DIR_SIZE (sizeof( FILE_BOTH_DIR_INFORMATION ) + (256 * sizeof( WCHAR )))
|
||
|
PCHAR pFileBothDirBuffer [FILE_BOTH_DIR_SIZE];
|
||
|
|
||
|
pFileBothDirInfo = (PFILE_BOTH_DIR_INFORMATION) pFileBothDirBuffer;
|
||
|
|
||
|
//
|
||
|
// This name contains a '~', therefore we need to open the parent directory
|
||
|
// and query for FileBothNamesInformation for this file to get the
|
||
|
// possibly tunneled long name.
|
||
|
//
|
||
|
|
||
|
parentDirectoryName.Length =
|
||
|
parentDirectoryName.MaximumLength =
|
||
|
(pOrigCtx->FileName.Length - (USHORT)FileNameComponentLength);
|
||
|
parentDirectoryName.Buffer = pOrigCtx->FileName.Buffer;
|
||
|
|
||
|
InitializeObjectAttributes( &objectAttributes,
|
||
|
&parentDirectoryName,
|
||
|
OBJ_KERNEL_HANDLE,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
status = SrIoCreateFile(
|
||
|
&parentDirectory,
|
||
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
||
|
&objectAttributes,
|
||
|
&ioStatus,
|
||
|
NULL, // AllocationSize
|
||
|
FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_NORMAL,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, // ShareAccess
|
||
|
FILE_OPEN, // OPEN_EXISTING
|
||
|
FILE_DIRECTORY_FILE
|
||
|
| FILE_OPEN_FOR_BACKUP_INTENT
|
||
|
| FILE_SYNCHRONOUS_IO_NONALERT, //create options
|
||
|
NULL,
|
||
|
0, // EaLength
|
||
|
IO_IGNORE_SHARE_ACCESS_CHECK,
|
||
|
pExtension->pTargetDevice );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build a unicode string with for the file name component.
|
||
|
//
|
||
|
|
||
|
fileNameComponent.Buffer = pFileNameComponentBuffer;
|
||
|
fileNameComponent.Length =
|
||
|
fileNameComponent.MaximumLength = (USHORT)FileNameComponentLength;
|
||
|
|
||
|
status = ZwQueryDirectoryFile( parentDirectory,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
&ioStatus,
|
||
|
pFileBothDirInfo,
|
||
|
FILE_BOTH_DIR_SIZE,
|
||
|
FileBothDirectoryInformation,
|
||
|
TRUE,
|
||
|
&fileNameComponent,
|
||
|
TRUE );
|
||
|
|
||
|
if (!NT_SUCCESS( status ))
|
||
|
{
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
fsFileName.Buffer = &(pFileBothDirInfo->FileName[0]);
|
||
|
fsFileName.Length =
|
||
|
fsFileName.MaximumLength =
|
||
|
(USHORT)pFileBothDirInfo->FileNameLength;
|
||
|
|
||
|
if (RtlCompareUnicodeString( &fsFileName, &fileNameComponent, TRUE ) != 0)
|
||
|
{
|
||
|
PSR_STREAM_CONTEXT ctx;
|
||
|
ULONG contextSize;
|
||
|
ULONG fileNameLength;
|
||
|
|
||
|
//
|
||
|
// Name tunneling did occur. Now we need to create a new context
|
||
|
// with the updated name for this file.
|
||
|
//
|
||
|
|
||
|
fileNameLength = parentDirectoryName.Length + sizeof( L'\\' ) +
|
||
|
fsFileName.Length + pOrigCtx->StreamNameLength;
|
||
|
contextSize = fileNameLength + sizeof( SR_STREAM_CONTEXT );
|
||
|
|
||
|
ctx = ExAllocatePoolWithTag( PagedPool,
|
||
|
contextSize,
|
||
|
SR_STREAM_CONTEXT_TAG );
|
||
|
|
||
|
if (!ctx)
|
||
|
{
|
||
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
goto SrCheckForNameTunneling_Exit;
|
||
|
}
|
||
|
|
||
|
#if DBG
|
||
|
INC_STATS(TotalContextCreated);
|
||
|
INC_STATS(TotalContextIsEligible);
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// Initialize the context structure from the components we've
|
||
|
// got. We can copy over most everything but the full name from
|
||
|
// the pOrigCtx. We also need to initialize the filename
|
||
|
// correctly when this copy is through.
|
||
|
//
|
||
|
|
||
|
RtlCopyMemory( ctx,
|
||
|
pOrigCtx,
|
||
|
(sizeof(SR_STREAM_CONTEXT) + parentDirectoryName.Length) );
|
||
|
|
||
|
RtlInitEmptyUnicodeString( &ctx->FileName,
|
||
|
(PWCHAR)(ctx + 1),
|
||
|
fileNameLength );
|
||
|
ctx->FileName.MaximumLength = (USHORT)fileNameLength;
|
||
|
ctx->FileName.Length = parentDirectoryName.Length;
|
||
|
|
||
|
//
|
||
|
// Append trailing slash if one is not already there
|
||
|
// NOTE: About fix for bug 374479
|
||
|
// I believe the append below is unnecessary because the
|
||
|
// code above this guarentees that the path always has
|
||
|
// a trailing slash. But because this fix is occuring so
|
||
|
// late in the release I decided to simply add a check to
|
||
|
// see if we should add the slash. If so we will add it.
|
||
|
// I believe the following 6 lines of code can be deleted
|
||
|
// in a future version of SR.
|
||
|
//
|
||
|
|
||
|
ASSERT(ctx->FileName.Length > 0);
|
||
|
|
||
|
if (ctx->FileName.Buffer[(ctx->FileName.Length/sizeof(WCHAR))-1] != L'\\')
|
||
|
{
|
||
|
RtlAppendUnicodeToString( &ctx->FileName, L"\\" );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Append file name
|
||
|
//
|
||
|
|
||
|
RtlAppendUnicodeStringToString( &ctx->FileName, &fsFileName );
|
||
|
|
||
|
if (pOrigCtx->StreamNameLength > 0)
|
||
|
{
|
||
|
//
|
||
|
// This file has a stream name component so copy that over now.
|
||
|
// The ctx->StreamNameLength should already be correctly set.
|
||
|
//
|
||
|
|
||
|
ASSERT( ctx->StreamNameLength == pOrigCtx->StreamNameLength );
|
||
|
RtlCopyMemory( &(ctx->FileName.Buffer[ctx->FileName.Length/sizeof( WCHAR )]),
|
||
|
&(pOrigCtx->FileName.Buffer[pOrigCtx->FileName.Length/sizeof( WCHAR )]),
|
||
|
pOrigCtx->StreamNameLength );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now we are done with the original file context and we want
|
||
|
// to return our new one.
|
||
|
//
|
||
|
|
||
|
status = STATUS_SUCCESS;
|
||
|
SrReleaseContext( pOrigCtx );
|
||
|
*ppFileContext = ctx;
|
||
|
|
||
|
VALIDATE_FILENAME( &ctx->FileName );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SrCheckForNameTunneling_Exit:
|
||
|
|
||
|
//
|
||
|
// If we have an error, we need to generate a volume error here.
|
||
|
//
|
||
|
|
||
|
if (CHECK_FOR_VOLUME_ERROR( status ))
|
||
|
{
|
||
|
//
|
||
|
// Trigger the failure notification to the service
|
||
|
//
|
||
|
|
||
|
NTSTATUS tempStatus = SrNotifyVolumeError( pExtension,
|
||
|
&(pOrigCtx->FileName),
|
||
|
status,
|
||
|
SrEventFileCreate );
|
||
|
CHECK_STATUS(tempStatus);
|
||
|
}
|
||
|
|
||
|
if ( parentDirectory != NULL )
|
||
|
{
|
||
|
ZwClose( parentDirectory );
|
||
|
}
|
||
|
|
||
|
RETURN( status );
|
||
|
}
|