You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
10816 lines
335 KiB
10816 lines
335 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FileInfo.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the set and query file information routines for Ntfs
|
|
called by the dispatch driver.
|
|
|
|
Author:
|
|
|
|
Brian Andrew [BrianAn] 15-Jan-1992
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (NTFS_BUG_CHECK_FILEINFO)
|
|
|
|
//
|
|
// The local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_FILEINFO)
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('FFtN')
|
|
|
|
#define SIZEOF_FILE_NAME_INFORMATION (FIELD_OFFSET( FILE_NAME_INFORMATION, FileName[0]) \
|
|
+ sizeof( WCHAR ))
|
|
|
|
//
|
|
// Local flags for rename and set link
|
|
//
|
|
|
|
#define TRAVERSE_MATCH (0x00000001)
|
|
#define EXACT_CASE_MATCH (0x00000002)
|
|
#define ACTIVELY_REMOVE_SOURCE_LINK (0x00000004)
|
|
#define REMOVE_SOURCE_LINK (0x00000008)
|
|
#define REMOVE_TARGET_LINK (0x00000010)
|
|
#define ADD_TARGET_LINK (0x00000020)
|
|
#define REMOVE_TRAVERSE_LINK (0x00000040)
|
|
#define REUSE_TRAVERSE_LINK (0x00000080)
|
|
#define MOVE_TO_NEW_DIR (0x00000100)
|
|
#define ADD_PRIMARY_LINK (0x00000200)
|
|
#define OVERWRITE_SOURCE_LINK (0x00000400)
|
|
|
|
//
|
|
// Additional local flags for set link
|
|
//
|
|
|
|
#define CREATE_IN_NEW_DIR (0x00000400)
|
|
|
|
//
|
|
// Local procedure prototypes
|
|
//
|
|
|
|
//
|
|
// VOID
|
|
// NtfsBuildLastFileName (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PFILE_OBJECT FileObject,
|
|
// IN ULONG FileNameOffset,
|
|
// OUT PUNICODE_STRING FileName
|
|
// );
|
|
//
|
|
|
|
#define NtfsBuildLastFileName(IC,FO,OFF,FN) { \
|
|
(FN)->MaximumLength = (FN)->Length = (FO)->FileName.Length - OFF; \
|
|
(FN)->Buffer = (PWSTR) Add2Ptr( (FO)->FileName.Buffer, OFF ); \
|
|
}
|
|
|
|
VOID
|
|
NtfsQueryBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_BASIC_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryStandardInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_STANDARD_INFORMATION Buffer,
|
|
IN OUT PULONG Length,
|
|
IN PCCB Ccb OPTIONAL
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryInternalInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_INTERNAL_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryEaInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_EA_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryAttributeTagInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PFILE_ATTRIBUTE_TAG_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_POSITION_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsQueryNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PULONG Length,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsQueryAlternateNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN PLCB Lcb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsQueryStreamsInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_STREAM_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsQueryCompressedFileSize (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_COMPRESSION_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
VOID
|
|
NtfsQueryNetworkOpenInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetDispositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetRenameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PBOOLEAN VcbAcquired
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetLinkInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PBOOLEAN VcbAcquired
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetShortNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetAllocationInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetEndOfFileInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb OPTIONAL,
|
|
IN BOOLEAN VcbAcquired
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsSetValidDataLengthInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCheckScbForLinkRemoval (
|
|
IN PSCB Scb,
|
|
OUT PSCB *BatchOplockScb,
|
|
OUT PULONG BatchOplockCount
|
|
);
|
|
|
|
VOID
|
|
NtfsFindTargetElements (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT TargetFileObject,
|
|
IN PSCB ParentScb,
|
|
OUT PSCB *TargetParentScb,
|
|
OUT PUNICODE_STRING FullTargetFileName,
|
|
OUT PUNICODE_STRING TargetFileName
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsCheckLinkForNewLink (
|
|
IN PFCB Fcb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
OUT PULONG LinkFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsCheckLinkForRename (
|
|
IN PFCB Fcb,
|
|
IN PLCB Lcb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN PUNICODE_STRING TargetFileName,
|
|
IN BOOLEAN IgnoreCase,
|
|
IN OUT PULONG RenameFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsCleanupLinkForRemoval (
|
|
IN PFCB PreviousFcb,
|
|
IN PSCB ParentScb,
|
|
IN BOOLEAN ExistingFcb
|
|
);
|
|
|
|
VOID
|
|
NtfsUpdateFcbFromLinkRemoval (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN UNICODE_STRING FileName,
|
|
IN UCHAR FileNameFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsReplaceLinkInDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR FileNameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsMoveLinkToNewDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PUNICODE_STRING NewFullLinkName,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR NewLinkNameFlags,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN OUT PLCB Lcb,
|
|
IN ULONG RenameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsRenameLinkInDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN OUT PLCB Lcb,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR FileNameFlags,
|
|
IN ULONG RenameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
);
|
|
|
|
VOID
|
|
NtfsUpdateFileDupInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb OPTIONAL
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsStreamRename(
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN BOOLEAN ReplaceIfExists,
|
|
IN PUNICODE_STRING NewStreamName
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsPrepareToShrinkFileSize (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
LONGLONG NewFileSize
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsCheckTreeForBatchOplocks (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PSCB DirectoryScb
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsCheckLinkForNewLink)
|
|
#pragma alloc_text(PAGE, NtfsCheckLinkForRename)
|
|
#pragma alloc_text(PAGE, NtfsCheckScbForLinkRemoval)
|
|
#pragma alloc_text(PAGE, NtfsCheckTreeForBatchOplocks)
|
|
#pragma alloc_text(PAGE, NtfsCleanupLinkForRemoval)
|
|
#pragma alloc_text(PAGE, NtfsCommonQueryInformation)
|
|
#pragma alloc_text(PAGE, NtfsCommonSetInformation)
|
|
#pragma alloc_text(PAGE, NtfsFindTargetElements)
|
|
#pragma alloc_text(PAGE, NtfsMoveLinkToNewDir)
|
|
#pragma alloc_text(PAGE, NtfsPrepareToShrinkFileSize)
|
|
#pragma alloc_text(PAGE, NtfsQueryAlternateNameInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryBasicInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryEaInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryAttributeTagInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryInternalInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryNameInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryPositionInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryStandardInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryStreamsInfo)
|
|
#pragma alloc_text(PAGE, NtfsQueryCompressedFileSize)
|
|
#pragma alloc_text(PAGE, NtfsQueryNetworkOpenInfo)
|
|
#pragma alloc_text(PAGE, NtfsRenameLinkInDir)
|
|
#pragma alloc_text(PAGE, NtfsReplaceLinkInDir)
|
|
#pragma alloc_text(PAGE, NtfsSetAllocationInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetBasicInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetDispositionInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetEndOfFileInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetLinkInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetPositionInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetRenameInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetShortNameInfo)
|
|
#pragma alloc_text(PAGE, NtfsSetValidDataLengthInfo)
|
|
#pragma alloc_text(PAGE, NtfsStreamRename)
|
|
#pragma alloc_text(PAGE, NtfsUpdateFcbFromLinkRemoval)
|
|
#pragma alloc_text(PAGE, NtfsUpdateFileDupInfo)
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
NtfsFsdSetInformation (
|
|
IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements the FSD part of set file information.
|
|
|
|
Arguments:
|
|
|
|
VolumeDeviceObject - Supplies the volume device object where the
|
|
file exists
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The FSD status for the IRP
|
|
|
|
--*/
|
|
|
|
{
|
|
TOP_LEVEL_CONTEXT TopLevelContext;
|
|
PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIRP_CONTEXT IrpContext = NULL;
|
|
|
|
ULONG LogFileFullCount = 0;
|
|
|
|
ASSERT_IRP( Irp );
|
|
|
|
UNREFERENCED_PARAMETER( VolumeDeviceObject );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsFsdSetInformation\n") );
|
|
|
|
//
|
|
// Call the common set Information routine
|
|
//
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, FALSE, FALSE );
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
//
|
|
// We are either initiating this request or retrying it.
|
|
//
|
|
|
|
if (IrpContext == NULL) {
|
|
|
|
//
|
|
// Allocate and initialize the Irp.
|
|
//
|
|
|
|
NtfsInitializeIrpContext( Irp, CanFsdWait( Irp ), &IrpContext );
|
|
|
|
//
|
|
// Initialize the thread top level structure, if needed.
|
|
//
|
|
|
|
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
|
|
|
|
} else if (Status == STATUS_LOG_FILE_FULL) {
|
|
|
|
NtfsCheckpointForLogFileFull( IrpContext );
|
|
|
|
LogFileFullCount += 1;
|
|
|
|
if (LogFileFullCount >= 2) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
|
|
}
|
|
}
|
|
|
|
Status = NtfsCommonSetInformation( IrpContext, Irp );
|
|
break;
|
|
|
|
} except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
|
|
|
NTSTATUS ExceptionCode;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
//
|
|
// We had some trouble trying to perform the requested
|
|
// operation, so we'll abort the I/O request with
|
|
// the error status that we get back from the
|
|
// execption code
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
ExceptionCode = GetExceptionCode();
|
|
|
|
if ((ExceptionCode == STATUS_FILE_DELETED) &&
|
|
(IrpSp->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation)) {
|
|
|
|
IrpContext->ExceptionStatus = ExceptionCode = STATUS_SUCCESS;
|
|
}
|
|
|
|
Status = NtfsProcessException( IrpContext, Irp, ExceptionCode );
|
|
}
|
|
|
|
} while (Status == STATUS_CANT_WAIT ||
|
|
Status == STATUS_LOG_FILE_FULL);
|
|
|
|
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext );
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFsdSetInformation -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsCommonQueryInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the common routine for query file information called by both the
|
|
fsd and fsp threads.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp to process
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
|
|
ULONG Length;
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
PVOID Buffer;
|
|
|
|
BOOLEAN FcbAcquired = FALSE;
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
BOOLEAN FsRtlHeaderLocked = FALSE;
|
|
PFILE_ALL_INFORMATION AllInfo;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current Irp stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCommonQueryInformation\n") );
|
|
DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
|
|
DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
|
|
DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.QueryFile.Length) );
|
|
DebugTrace( 0, Dbg, ("FileInformationClass = %08lx\n", IrpSp->Parameters.QueryFile.FileInformationClass) );
|
|
DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
|
|
|
|
//
|
|
// Reference our input parameters to make things easier
|
|
//
|
|
|
|
Length = IrpSp->Parameters.QueryFile.Length;
|
|
FileInformationClass = IrpSp->Parameters.QueryFile.FileInformationClass;
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Extract and decode the file object
|
|
//
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
|
|
|
|
try {
|
|
|
|
//
|
|
// Case on the type of open we're dealing with
|
|
//
|
|
|
|
switch (TypeOfOpen) {
|
|
|
|
case UserVolumeOpen:
|
|
|
|
//
|
|
// We cannot query the user volume open.
|
|
//
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
case UserFileOpen:
|
|
case UserDirectoryOpen:
|
|
case UserViewIndexOpen:
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
//
|
|
// We don't allow this operation on with open by file id.
|
|
//
|
|
|
|
if ((Ccb->Lcb == NULL) &&
|
|
(FileInformationClass == FileAlternateNameInformation)) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
} else if ((FileInformationClass == FileAllInformation) ||
|
|
(FileInformationClass == FileNameInformation)) {
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_TRAVERSE_CHECK )) {
|
|
|
|
//
|
|
// If this file was opened by Id by a user without traversal privilege,
|
|
// we can't return any info level that includes a file or path name
|
|
// unless this open is relative to a directory by file id.
|
|
// Look at the file name in the Ccb in that case. We'll return
|
|
// only that portion of the name.
|
|
//
|
|
if (Ccb->FullFileName.MaximumLength == 0) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We'll need to hold the Vcb exclusively through the filename
|
|
// synthesis, since it walks up the directory tree.
|
|
//
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Deliberate fall through to StreamFileOpen case.
|
|
//
|
|
|
|
case StreamFileOpen:
|
|
|
|
//
|
|
// Acquire the Vcb if there is no Ccb. This is for the
|
|
// case where the cache manager is querying the name.
|
|
//
|
|
|
|
if (Ccb == NULL) {
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
}
|
|
|
|
if ((Scb->Header.PagingIoResource != NULL) &&
|
|
|
|
((FileInformationClass == FileAllInformation) ||
|
|
(FileInformationClass == FileStandardInformation) ||
|
|
(FileInformationClass == FileCompressionInformation) ||
|
|
(FileInformationClass == FileNetworkOpenInformation))) {
|
|
|
|
ExAcquireResourceSharedLite( Scb->Header.PagingIoResource, TRUE );
|
|
|
|
FsRtlLockFsRtlHeader( &Scb->Header );
|
|
FsRtlHeaderLocked = TRUE;
|
|
}
|
|
|
|
NtfsAcquireSharedFcb( IrpContext, Fcb, Scb, 0 );
|
|
FcbAcquired = TRUE;
|
|
|
|
//
|
|
// Fail this request if the volume has been dismounted.
|
|
// System files may not have the scbstate flag set - so we test the vcb as well
|
|
// Holding any file's main resource lets us test the vcb since we call acquireallfiles
|
|
// before doing a dismount
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED ) ||
|
|
!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Based on the information class we'll do different
|
|
// actions. Each of hte procedures that we're calling fills
|
|
// up the output buffer, if possible. They will raise the
|
|
// status STATUS_BUFFER_OVERFLOW for an insufficient buffer.
|
|
// This is considered a somewhat unusual case and is handled
|
|
// more cleanly with the exception mechanism rather than
|
|
// testing a return status value for each call.
|
|
//
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileAllInformation:
|
|
|
|
//
|
|
// For the all information class we'll typecast a local
|
|
// pointer to the output buffer and then call the
|
|
// individual routines to fill in the buffer.
|
|
//
|
|
|
|
AllInfo = Buffer;
|
|
Length -= (sizeof(FILE_ACCESS_INFORMATION)
|
|
+ sizeof(FILE_MODE_INFORMATION)
|
|
+ sizeof(FILE_ALIGNMENT_INFORMATION));
|
|
|
|
NtfsQueryBasicInfo( IrpContext, FileObject, Scb, &AllInfo->BasicInformation, &Length );
|
|
NtfsQueryStandardInfo( IrpContext, FileObject, Scb, &AllInfo->StandardInformation, &Length, Ccb );
|
|
NtfsQueryInternalInfo( IrpContext, FileObject, Scb, &AllInfo->InternalInformation, &Length );
|
|
NtfsQueryEaInfo( IrpContext, FileObject, Scb, &AllInfo->EaInformation, &Length );
|
|
NtfsQueryPositionInfo( IrpContext, FileObject, Scb, &AllInfo->PositionInformation, &Length );
|
|
Status =
|
|
NtfsQueryNameInfo( IrpContext, FileObject, Scb, &AllInfo->NameInformation, &Length, Ccb );
|
|
break;
|
|
|
|
case FileBasicInformation:
|
|
|
|
NtfsQueryBasicInfo( IrpContext, FileObject, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileStandardInformation:
|
|
|
|
NtfsQueryStandardInfo( IrpContext, FileObject, Scb, Buffer, &Length, Ccb );
|
|
break;
|
|
|
|
case FileInternalInformation:
|
|
|
|
NtfsQueryInternalInfo( IrpContext, FileObject, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileEaInformation:
|
|
|
|
NtfsQueryEaInfo( IrpContext, FileObject, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileAttributeTagInformation:
|
|
|
|
NtfsQueryAttributeTagInfo( IrpContext, FileObject, Scb, Ccb, Buffer, &Length );
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
|
|
NtfsQueryPositionInfo( IrpContext, FileObject, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileNameInformation:
|
|
|
|
Status = NtfsQueryNameInfo( IrpContext, FileObject, Scb, Buffer, &Length, Ccb );
|
|
break;
|
|
|
|
case FileAlternateNameInformation:
|
|
|
|
Status = NtfsQueryAlternateNameInfo( IrpContext, Scb, Ccb->Lcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileStreamInformation:
|
|
|
|
Status = NtfsQueryStreamsInfo( IrpContext, Fcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileCompressionInformation:
|
|
|
|
Status = NtfsQueryCompressedFileSize( IrpContext, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileNetworkOpenInformation:
|
|
|
|
NtfsQueryNetworkOpenInfo( IrpContext, FileObject, Scb, Buffer, &Length );
|
|
break;
|
|
|
|
default:
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Set the information field to the number of bytes actually filled in
|
|
// and then complete the request
|
|
//
|
|
|
|
Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - Length;
|
|
|
|
//
|
|
// Abort transaction on error by raising.
|
|
//
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCommonQueryInformation );
|
|
|
|
if (FsRtlHeaderLocked) {
|
|
FsRtlUnlockFsRtlHeader( &Scb->Header );
|
|
ExReleaseResourceLite( Scb->Header.PagingIoResource );
|
|
}
|
|
|
|
if (FcbAcquired) { NtfsReleaseFcb( IrpContext, Fcb ); }
|
|
if (VcbAcquired) { NtfsReleaseVcb( IrpContext, Vcb ); }
|
|
|
|
if (!AbnormalTermination()) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonQueryInformation -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsCommonSetInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the common routine for set file information called by both the
|
|
fsd and fsp threads.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp to process
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
BOOLEAN ReleaseScbPaging = FALSE;
|
|
BOOLEAN LazyWriterCallback = FALSE;
|
|
ULONG WaitState;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current Irp stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCommonSetInformation\n") );
|
|
DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
|
|
DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
|
|
DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.SetFile.Length) );
|
|
DebugTrace( 0, Dbg, ("FileInformationClass = %08lx\n", IrpSp->Parameters.SetFile.FileInformationClass) );
|
|
DebugTrace( 0, Dbg, ("FileObject = %08lx\n", IrpSp->Parameters.SetFile.FileObject) );
|
|
DebugTrace( 0, Dbg, ("ReplaceIfExists = %08lx\n", IrpSp->Parameters.SetFile.ReplaceIfExists) );
|
|
DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
|
|
|
|
//
|
|
// Reference our input parameters to make things easier
|
|
//
|
|
|
|
FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
|
|
|
|
//
|
|
// Extract and decode the file object
|
|
//
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
|
|
|
|
//
|
|
// We can reject volume opens immediately.
|
|
//
|
|
|
|
if (TypeOfOpen == UserVolumeOpen ||
|
|
TypeOfOpen == UnopenedFileObject ||
|
|
TypeOfOpen == UserViewIndexOpen ||
|
|
((TypeOfOpen != UserFileOpen) &&
|
|
(FileInformationClass == FileValidDataLengthInformation))) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> STATUS_INVALID_PARAMETER\n") );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_MEDIA_WRITE_PROTECTED );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> STATUS_MEDIA_WRITE_PROTECTED\n") );
|
|
return STATUS_MEDIA_WRITE_PROTECTED;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// The typical path here is for the lazy writer callback. Go ahead and
|
|
// remember this first.
|
|
//
|
|
|
|
if (FileInformationClass == FileEndOfFileInformation) {
|
|
|
|
LazyWriterCallback = IrpSp->Parameters.SetFile.AdvanceOnly;
|
|
}
|
|
|
|
//
|
|
// Perform the oplock check for changes to allocation or EOF if called
|
|
// by the user.
|
|
//
|
|
|
|
if (!LazyWriterCallback &&
|
|
((FileInformationClass == FileEndOfFileInformation) ||
|
|
(FileInformationClass == FileAllocationInformation) ||
|
|
(FileInformationClass == FileValidDataLengthInformation)) &&
|
|
(TypeOfOpen == UserFileOpen) &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
//
|
|
// We check whether we can proceed based on the state of the file oplocks.
|
|
// This call might block this request.
|
|
//
|
|
|
|
Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
|
|
Irp,
|
|
IrpContext,
|
|
NULL,
|
|
NULL );
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// Update the FastIoField.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
}
|
|
|
|
//
|
|
// If this call is for EOF then we need to acquire the Vcb if we may
|
|
// have to perform an update duplicate call. Don't block waiting for
|
|
// the Vcb in the Valid data callback case.
|
|
// We don't want to block the lazy write threads in the clean checkpoint
|
|
// case.
|
|
//
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileEndOfFileInformation:
|
|
|
|
//
|
|
// If this is not a system file then we will need to update duplicate info.
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
WaitState = FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
//
|
|
// Only acquire the Vcb for the Lazy writer if we know the file size in the Fcb
|
|
// is out of date or can compare the Scb with that in the Fcb. An unsafe comparison
|
|
// is OK because if they are changing then someone else can do the work.
|
|
// We also want to update the duplicate information if the total allocated
|
|
// has changed and there are no user handles remaining to perform the update.
|
|
//
|
|
|
|
if (LazyWriterCallback) {
|
|
|
|
if ((FlagOn( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE ) ||
|
|
((Scb->Header.FileSize.QuadPart != Fcb->Info.FileSize) &&
|
|
FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ))) ||
|
|
(FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) &&
|
|
(Scb->CleanupCount == 0) &&
|
|
(Scb->ValidDataToDisk >= Scb->Header.ValidDataLength.QuadPart) &&
|
|
(FlagOn( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE ) ||
|
|
(FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ) &&
|
|
(Scb->TotalAllocated != Fcb->Info.AllocatedLength))))) {
|
|
|
|
//
|
|
// Go ahead and try to acquire the Vcb without waiting.
|
|
//
|
|
|
|
if (NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE )) {
|
|
|
|
VcbAcquired = TRUE;
|
|
|
|
} else {
|
|
|
|
SetFlag( IrpContext->State, WaitState );
|
|
|
|
//
|
|
// If we could not get the Vcb for any reason then return. Let's
|
|
// not block an essential thread waiting for the Vcb. Typically
|
|
// we will only be blocked during a clean checkpoint. The Lazy
|
|
// Writer will periodically come back and retry this call.
|
|
//
|
|
|
|
try_return( Status = STATUS_FILE_LOCK_CONFLICT );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise we always want to wait for the Vcb except if we were called from
|
|
// MM extending a section. We will try to get this without waiting and test
|
|
// if called from MM if unsuccessful.
|
|
//
|
|
|
|
} else {
|
|
|
|
if (NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE )) {
|
|
|
|
VcbAcquired = TRUE;
|
|
|
|
} else if ((Scb->Header.PagingIoResource == NULL) ||
|
|
!NtfsIsExclusiveScbPagingIo( Scb )) {
|
|
|
|
SetFlag( IrpContext->State, WaitState );
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
}
|
|
}
|
|
|
|
SetFlag( IrpContext->State, WaitState );
|
|
}
|
|
|
|
break;
|
|
//
|
|
// Acquire the Vcb shared for changes to allocation or basic
|
|
// information.
|
|
//
|
|
|
|
case FileAllocationInformation:
|
|
case FileBasicInformation:
|
|
case FileDispositionInformation:
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
|
|
break;
|
|
|
|
//
|
|
// If this is a rename or link operation then we need to make sure
|
|
// we have the user's context and acquire the Vcb.
|
|
//
|
|
|
|
case FileRenameInformation:
|
|
case FileLinkInformation:
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_SECURITY )) {
|
|
|
|
IrpContext->Union.SubjectContext = NtfsAllocatePool( PagedPool,
|
|
sizeof( SECURITY_SUBJECT_CONTEXT ));
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_SECURITY );
|
|
|
|
SeCaptureSubjectContext( IrpContext->Union.SubjectContext );
|
|
}
|
|
|
|
// Fall thru
|
|
|
|
//
|
|
// For the two above plus the shortname we might need the Vcb exclusive for either directories
|
|
// or possible deadlocks.
|
|
//
|
|
|
|
case FileShortNameInformation:
|
|
|
|
if (IsDirectory( &Fcb->Info )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
}
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
|
|
} else {
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
}
|
|
|
|
VcbAcquired = TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// The Lazy Writer must still synchronize with Eof to keep the
|
|
// stream sizes from changing. This will be cleaned up when we
|
|
// complete.
|
|
//
|
|
|
|
if (LazyWriterCallback) {
|
|
|
|
//
|
|
// Acquire either the paging io resource shared to serialize with
|
|
// the flush case where the main resource is acquired before IoAtEOF
|
|
//
|
|
|
|
if (Scb->Header.PagingIoResource != NULL) {
|
|
|
|
ExAcquireResourceSharedLite( Scb->Header.PagingIoResource, TRUE );
|
|
ReleaseScbPaging = TRUE;
|
|
}
|
|
|
|
FsRtlLockFsRtlHeader( &Scb->Header );
|
|
IrpContext->CleanupStructure = Scb;
|
|
|
|
//
|
|
// Anyone potentially shrinking/deleting allocation must get the paging I/O
|
|
// resource first. Special cases are the rename path and SetBasicInfo. The
|
|
// rename path to lock the mapped page writer out of this file for deadlock
|
|
// prevention. SetBasicInfo since we may call WriteFileSizes and we
|
|
// don't want to bump up the file size on disk from the value in the Scb
|
|
// if a write to EOF is underway.
|
|
//
|
|
|
|
} else if ((Scb->Header.PagingIoResource != NULL) &&
|
|
((FileInformationClass == FileEndOfFileInformation) ||
|
|
(FileInformationClass == FileAllocationInformation) ||
|
|
(FileInformationClass == FileRenameInformation) ||
|
|
(FileInformationClass == FileBasicInformation) ||
|
|
(FileInformationClass == FileLinkInformation) ||
|
|
(FileInformationClass == FileValidDataLengthInformation))) {
|
|
|
|
NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// Acquire exclusive access to the Fcb, We use exclusive
|
|
// because it is probable that one of the subroutines
|
|
// that we call will need to monkey with file allocation,
|
|
// create/delete extra fcbs. So we're willing to pay the
|
|
// cost of exclusive Fcb access.
|
|
//
|
|
|
|
NtfsAcquireExclusiveFcb( IrpContext, Fcb, Scb, 0 );
|
|
|
|
//
|
|
// Make sure the Scb state test we're about to do is properly synchronized.
|
|
// There's no point in testing the SCB_STATE_VOLUME_DISMOUNTED flag below
|
|
// if the volume can still get dismounted below us during this operation.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ) || NtfsIsSharedScb( Scb ) );
|
|
|
|
//
|
|
// The lazy writer callback is the only caller who can get this far if the
|
|
// volume has been dismounted. We know that there are no user handles or
|
|
// writeable file objects or dirty pages. Make one last check to see
|
|
// if this stream is on a dismounted or locked volume. Note the
|
|
// vcb tests are unsafe
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED ) ||
|
|
FlagOn( Vcb->VcbState, VCB_STATE_LOCK_IN_PROGRESS ) ||
|
|
!(FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ))) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Based on the information class we'll do different
|
|
// actions. We will perform checks, when appropriate
|
|
// to insure that the requested operation is allowed.
|
|
//
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileBasicInformation:
|
|
|
|
Status = NtfsSetBasicInfo( IrpContext, FileObject, Irp, Scb, Ccb );
|
|
break;
|
|
|
|
case FileDispositionInformation:
|
|
|
|
Status = NtfsSetDispositionInfo( IrpContext, FileObject, Irp, Scb, Ccb );
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
|
|
Status = NtfsSetRenameInfo( IrpContext, FileObject, Irp, Vcb, Scb, Ccb, &VcbAcquired );
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
|
|
Status = NtfsSetPositionInfo( IrpContext, FileObject, Irp, Scb );
|
|
break;
|
|
|
|
case FileLinkInformation:
|
|
|
|
Status = NtfsSetLinkInfo( IrpContext, Irp, Vcb, Scb, Ccb, &VcbAcquired );
|
|
break;
|
|
|
|
case FileAllocationInformation:
|
|
|
|
if (TypeOfOpen == UserDirectoryOpen ||
|
|
TypeOfOpen == UserViewIndexOpen) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
Status = NtfsSetAllocationInfo( IrpContext, FileObject, Irp, Scb, Ccb );
|
|
}
|
|
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
|
|
if (TypeOfOpen == UserDirectoryOpen ||
|
|
TypeOfOpen == UserViewIndexOpen) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
Status = NtfsSetEndOfFileInfo( IrpContext, FileObject, Irp, Scb, Ccb, VcbAcquired );
|
|
}
|
|
|
|
break;
|
|
|
|
case FileValidDataLengthInformation:
|
|
|
|
Status = NtfsSetValidDataLengthInfo( IrpContext, Irp, Scb, Ccb );
|
|
break;
|
|
|
|
case FileShortNameInformation:
|
|
|
|
//
|
|
// Disallow setshortname on the root - its meaningless anyway
|
|
//
|
|
|
|
if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
} else {
|
|
Status = NtfsSetShortNameInfo( IrpContext, FileObject, Irp, Vcb, Scb, Ccb );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Abort transaction on error by raising.
|
|
//
|
|
|
|
if (Status != STATUS_PENDING) {
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCommonSetInformation );
|
|
|
|
//
|
|
// Release the paging io resource if acquired shared.
|
|
//
|
|
|
|
if (ReleaseScbPaging) {
|
|
|
|
ExReleaseResourceLite( Scb->Header.PagingIoResource );
|
|
}
|
|
|
|
if (VcbAcquired) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> %08lx\n", Status) );
|
|
}
|
|
|
|
//
|
|
// Complete the request unless it is being done in the oplock
|
|
// package.
|
|
//
|
|
|
|
if (Status != STATUS_PENDING) {
|
|
NtfsCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_BASIC_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query basic information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryBasicInfo...\n") );
|
|
|
|
//
|
|
// Update the length used.
|
|
//
|
|
|
|
*Length -= sizeof( FILE_BASIC_INFORMATION );
|
|
|
|
//
|
|
// Copy over the time information
|
|
//
|
|
|
|
NtfsFillBasicInfo( Buffer, Scb );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryBasicInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryStandardInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_STANDARD_INFORMATION Buffer,
|
|
IN OUT PULONG Length,
|
|
IN PCCB Ccb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query standard information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Ccb - Optionally supplies the ccb for the opened file object.
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryStandardInfo...\n") );
|
|
|
|
//
|
|
// Update the length field.
|
|
//
|
|
|
|
*Length -= sizeof( FILE_STANDARD_INFORMATION );
|
|
|
|
//
|
|
// If the Scb is uninitialized, we initialize it now.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
|
|
(Scb->AttributeTypeCode != $INDEX_ALLOCATION)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
//
|
|
// Call the common routine to fill the output buffer.
|
|
//
|
|
|
|
NtfsFillStandardInfo( Buffer, Scb, Ccb );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryStandardInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryInternalInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_INTERNAL_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query internal information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryInternalInfo...\n") );
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_INTERNAL_INFORMATION) );
|
|
|
|
*Length -= sizeof( FILE_INTERNAL_INFORMATION );
|
|
|
|
//
|
|
// Copy over the entire file reference including the sequence number
|
|
//
|
|
|
|
Buffer->IndexNumber = *(PLARGE_INTEGER)&Scb->Fcb->FileReference;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryInternalInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryEaInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_EA_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query EA information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryEaInfo...\n") );
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_EA_INFORMATION) );
|
|
|
|
*Length -= sizeof( FILE_EA_INFORMATION );
|
|
|
|
//
|
|
// EAs and reparse points cannot both be in a file at the same
|
|
// time. We return different information for each case.
|
|
//
|
|
|
|
if (FlagOn( Scb->Fcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT)) {
|
|
|
|
Buffer->EaSize = 0;
|
|
|
|
} else {
|
|
|
|
Buffer->EaSize = Scb->Fcb->Info.PackedEaSize;
|
|
|
|
//
|
|
// Add 4 bytes for the CbListHeader.
|
|
//
|
|
|
|
if (Buffer->EaSize != 0) {
|
|
|
|
Buffer->EaSize += 4;
|
|
}
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryEaInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryAttributeTagInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PFILE_ATTRIBUTE_TAG_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query of attributes and tag information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB Fcb;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryAttributeTagInfo...\n") );
|
|
|
|
Fcb = Scb->Fcb;
|
|
|
|
//
|
|
// Zero the output buffer and update the length.
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_ATTRIBUTE_TAG_INFORMATION) );
|
|
|
|
*Length -= sizeof( FILE_ATTRIBUTE_TAG_INFORMATION );
|
|
|
|
//
|
|
// Load the file attributes as in NtfsQueryBasicInfo.
|
|
//
|
|
// For the file attribute information if the flags in the attribute are zero then we
|
|
// return the file normal attribute otherwise we return the mask of the set attribute
|
|
// bits. Note that only the valid attribute bits are returned to the user.
|
|
//
|
|
|
|
Buffer->FileAttributes = Fcb->Info.FileAttributes;
|
|
|
|
ClearFlag( Buffer->FileAttributes,
|
|
(~FILE_ATTRIBUTE_VALID_FLAGS |
|
|
FILE_ATTRIBUTE_TEMPORARY |
|
|
FILE_ATTRIBUTE_SPARSE_FILE |
|
|
FILE_ATTRIBUTE_ENCRYPTED) );
|
|
|
|
//
|
|
// Pick up the sparse bit for this stream from the Scb.
|
|
//
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_SPARSE_FILE );
|
|
}
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_ENCRYPTED )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_ENCRYPTED );
|
|
}
|
|
|
|
//
|
|
// If this is the main steam then the compression flag is correct but
|
|
// we need to test if this is a directory.
|
|
//
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
if (IsDirectory( &Fcb->Info ) || IsViewIndex( &Fcb->Info )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
|
|
}
|
|
|
|
//
|
|
// If this is not the main stream on the file then use the stream based
|
|
// compressed bit.
|
|
//
|
|
|
|
} else {
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
|
|
|
|
} else {
|
|
|
|
ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the temporary flag is set, then return it to the caller.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
|
|
}
|
|
|
|
//
|
|
// If there are no flags set then explicitly set the NORMAL flag.
|
|
//
|
|
|
|
if (Buffer->FileAttributes == 0) {
|
|
|
|
Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
|
|
//
|
|
// Load the reparse point tag.
|
|
// As EAs and reparse points cannot both be in a file at the same time, we return
|
|
// the appropriate information for each case.
|
|
//
|
|
|
|
if (FlagOn( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT)) {
|
|
|
|
Buffer->ReparseTag = Fcb->Info.ReparsePointTag;
|
|
|
|
} else {
|
|
|
|
Buffer->ReparseTag = IO_REPARSE_TAG_RESERVED_ZERO;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryAttributeTagInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_POSITION_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query position information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryPositionInfo...\n") );
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_POSITION_INFORMATION) );
|
|
|
|
*Length -= sizeof( FILE_POSITION_INFORMATION );
|
|
|
|
//
|
|
// Get the current position found in the file object.
|
|
//
|
|
|
|
Buffer->CurrentByteOffset = FileObject->CurrentByteOffset;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryPositionInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsQueryNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PULONG Length,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query name information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Ccb - This is the Ccb for this file object. If NULL then this request
|
|
is from the Lazy Writer.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if the whole name would fit into the user buffer,
|
|
STATUS_BUFFER_OVERFLOW otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG BytesToCopy;
|
|
NTSTATUS Status;
|
|
UNICODE_STRING NormalizedName;
|
|
PUNICODE_STRING SourceName;
|
|
ULONG AvailableNameLength;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryNameInfo...\n") );
|
|
|
|
NormalizedName.Buffer = NULL;
|
|
|
|
//
|
|
// Reduce the buffer length by the size of the fixed part of the structure.
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, SIZEOF_FILE_NAME_INFORMATION );
|
|
|
|
*Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
//
|
|
// If the name length in this file object is zero, then we try to
|
|
// construct the name with the Lcb chain. This means we have been
|
|
// called by the system for a lazy write that failed.
|
|
//
|
|
|
|
if (Ccb == NULL) {
|
|
|
|
FILE_REFERENCE FileReference;
|
|
|
|
NtfsSetSegmentNumber( &FileReference, 0, UPCASE_TABLE_NUMBER );
|
|
|
|
//
|
|
// If this is a system file with a known name then just use our constant names.
|
|
//
|
|
|
|
if (NtfsLeqMftRef( &Scb->Fcb->FileReference, &FileReference )) {
|
|
|
|
SourceName =
|
|
(PUNICODE_STRING) &NtfsSystemFiles[ Scb->Fcb->FileReference.SegmentNumberLowPart ];
|
|
|
|
} else {
|
|
|
|
NtfsBuildNormalizedName( IrpContext, Scb->Fcb, FALSE, &NormalizedName );
|
|
SourceName = &NormalizedName;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
|
|
!FlagOn( Ccb->Flags, CCB_FLAG_TRAVERSE_CHECK)) {
|
|
|
|
//
|
|
// If the file was opened by id, the Ccb doesn't
|
|
// have a full file name in it.
|
|
//
|
|
|
|
NtfsBuildNormalizedName( IrpContext, Scb->Fcb, FALSE, &NormalizedName );
|
|
SourceName = &NormalizedName;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Use the name in the Ccb. This may be a relative name for
|
|
// some of the open by ID relative cases.
|
|
//
|
|
|
|
SourceName = &Ccb->FullFileName;
|
|
}
|
|
}
|
|
|
|
Buffer->FileNameLength = SourceName->Length;
|
|
|
|
if ((Scb->AttributeName.Length != 0) &&
|
|
NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
|
|
|
|
Buffer->FileNameLength += sizeof( WCHAR ) + Scb->AttributeName.Length;
|
|
}
|
|
|
|
//
|
|
// Figure out how many bytes we can copy.
|
|
//
|
|
|
|
AvailableNameLength = Buffer->FileNameLength;
|
|
|
|
if (*Length >= Buffer->FileNameLength) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we don't have enough for the entire buffer then make sure we only
|
|
// return full characters (characters are UNICODE).
|
|
//
|
|
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
AvailableNameLength = *Length & 0xfffffffe;
|
|
}
|
|
|
|
//
|
|
// Update the Length
|
|
//
|
|
|
|
*Length -= AvailableNameLength;
|
|
|
|
//
|
|
// Copy over the file name
|
|
//
|
|
|
|
if (SourceName->Length <= AvailableNameLength) {
|
|
|
|
BytesToCopy = SourceName->Length;
|
|
|
|
} else {
|
|
|
|
BytesToCopy = AvailableNameLength;
|
|
}
|
|
|
|
if (BytesToCopy) {
|
|
|
|
RtlCopyMemory( &Buffer->FileName[0],
|
|
SourceName->Buffer,
|
|
BytesToCopy );
|
|
}
|
|
|
|
BytesToCopy = AvailableNameLength - BytesToCopy;
|
|
|
|
if (BytesToCopy) {
|
|
|
|
PWCHAR DestBuffer;
|
|
|
|
DestBuffer = (PWCHAR) Add2Ptr( &Buffer->FileName, SourceName->Length );
|
|
|
|
*DestBuffer = L':';
|
|
DestBuffer += 1;
|
|
|
|
BytesToCopy -= sizeof( WCHAR );
|
|
|
|
if (BytesToCopy) {
|
|
|
|
RtlCopyMemory( DestBuffer,
|
|
Scb->AttributeName.Buffer,
|
|
BytesToCopy );
|
|
}
|
|
}
|
|
|
|
if ((SourceName == &NormalizedName) &&
|
|
(SourceName->Buffer != NULL)) {
|
|
|
|
NtfsFreePool( SourceName->Buffer );
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryNameInfo -> 0x%8lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsQueryAlternateNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN PLCB Lcb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query alternate name information function.
|
|
We will return the alternate name as long as this opener has opened
|
|
a primary link. We don't return the alternate name if the user
|
|
has opened a hard link because there is no reason to expect that
|
|
the primary link has any relationship to a hard link.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Lcb - Supplies the link the user traversed to open this file.
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
**** We need a status code for the case where there is no alternate name
|
|
or the caller isn't allowed to see it.
|
|
|
|
NTSTATUS - STATUS_SUCCESS if the whole name would fit into the user buffer,
|
|
STATUS_OBJECT_NAME_NOT_FOUND if we can't return the name,
|
|
STATUS_BUFFER_OVERFLOW otherwise.
|
|
|
|
**** A code like STATUS_NAME_NOT_FOUND would be good.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG BytesToCopy;
|
|
NTSTATUS Status;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN MoreToGo;
|
|
|
|
UNICODE_STRING AlternateName;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_LCB( Lcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryAlternateNameInfo...\n") );
|
|
|
|
//
|
|
// If the Lcb is not a primary link we can return immediately.
|
|
//
|
|
|
|
if (!FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsQueryAlternateNameInfo: Lcb not a primary link\n") );
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Reduce the buffer length by the size of the fixed part of the structure.
|
|
//
|
|
|
|
if (*Length < SIZEOF_FILE_NAME_INFORMATION ) {
|
|
|
|
*Length = 0;
|
|
NtfsRaiseStatus( IrpContext, STATUS_BUFFER_OVERFLOW, NULL, NULL );
|
|
}
|
|
|
|
RtlZeroMemory( Buffer, SIZEOF_FILE_NAME_INFORMATION );
|
|
|
|
*Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-finally to cleanup the attribut structure if we need it.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// We can special case for the case where the name is in the Lcb.
|
|
//
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS )) {
|
|
|
|
AlternateName = Lcb->ExactCaseLink.LinkName;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We will walk through the file record looking for a file name
|
|
// attribute with the 8.3 bit set. It is not guaranteed to be
|
|
// present.
|
|
//
|
|
|
|
MoreToGo = NtfsLookupAttributeByCode( IrpContext,
|
|
Scb->Fcb,
|
|
&Scb->Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
|
|
while (MoreToGo) {
|
|
|
|
PFILE_NAME FileName;
|
|
|
|
FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
|
|
|
//
|
|
// See if the 8.3 flag is set for this name.
|
|
//
|
|
|
|
if (FlagOn( FileName->Flags, FILE_NAME_DOS )) {
|
|
|
|
AlternateName.Length = (USHORT)(FileName->FileNameLength * sizeof( WCHAR ));
|
|
AlternateName.Buffer = (PWSTR) FileName->FileName;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The last one wasn't it. Let's try again.
|
|
//
|
|
|
|
MoreToGo = NtfsLookupNextAttributeByCode( IrpContext,
|
|
Scb->Fcb,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
}
|
|
|
|
//
|
|
// If we didn't find a match, return to the caller.
|
|
//
|
|
|
|
if (!MoreToGo) {
|
|
|
|
DebugTrace( 0, Dbg, ("NtfsQueryAlternateNameInfo: No Dos link\n") );
|
|
try_return( Status = STATUS_OBJECT_NAME_NOT_FOUND );
|
|
|
|
//
|
|
// **** Get a better status code.
|
|
//
|
|
}
|
|
}
|
|
|
|
//
|
|
// The name is now in alternate name.
|
|
// Figure out how many bytes we can copy.
|
|
//
|
|
|
|
if ( *Length >= (ULONG)AlternateName.Length ) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
BytesToCopy = AlternateName.Length;
|
|
|
|
} else {
|
|
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
|
|
BytesToCopy = *Length;
|
|
}
|
|
|
|
//
|
|
// Copy over the file name
|
|
//
|
|
|
|
RtlCopyMemory( Buffer->FileName, AlternateName.Buffer, BytesToCopy);
|
|
|
|
//
|
|
// Copy the number of bytes (not characters) and update the Length
|
|
//
|
|
|
|
Buffer->FileNameLength = BytesToCopy;
|
|
|
|
*Length -= BytesToCopy;
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryAlternateNameInfo -> 0x%8lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsQueryStreamsInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_STREAM_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will return the attribute name and code name for as
|
|
many attributes in the file as will fit in the user buffer. We return
|
|
a string which can be appended to the end of the file name to
|
|
open the string.
|
|
|
|
For example, for the unnamed data stream we will return the string:
|
|
|
|
"::$DATA"
|
|
|
|
For a user data stream with the name "Authors", we return the string
|
|
|
|
":Authors:$DATA"
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file.
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if all of the names would fit into the user buffer,
|
|
STATUS_BUFFER_OVERFLOW otherwise.
|
|
|
|
**** We need a code indicating that they didn't all fit but
|
|
some of them got in.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
BOOLEAN MoreToGo;
|
|
|
|
PUCHAR UserBuffer;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
PATTRIBUTE_DEFINITION_COLUMNS AttrDefinition;
|
|
UNICODE_STRING AttributeCodeString;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
ATTRIBUTE_TYPE_CODE TypeCode = $DATA;
|
|
|
|
ULONG NextEntry;
|
|
ULONG LastEntry;
|
|
ULONG ThisLength;
|
|
ULONG NameLength;
|
|
ULONG LastQuadAlign;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FCB( Fcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryStreamsInfo...\n") );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
LastEntry = 0;
|
|
NextEntry = 0;
|
|
LastQuadAlign = 0;
|
|
|
|
//
|
|
// Zero the entire buffer.
|
|
//
|
|
|
|
UserBuffer = (PUCHAR) Buffer;
|
|
|
|
RtlZeroMemory( UserBuffer, *Length );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// There should always be at least one attribute.
|
|
//
|
|
|
|
MoreToGo = NtfsLookupAttribute( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
&AttrContext );
|
|
|
|
Attribute = NtfsFoundAttribute( &AttrContext );
|
|
|
|
//
|
|
// Walk through all of the entries, checking if we can return this
|
|
// entry to the user and if it will fit in the buffer.
|
|
//
|
|
|
|
while (MoreToGo) {
|
|
|
|
//
|
|
// If we can return this entry to the user, compute it's size.
|
|
// We only return user defined attributes or data streams
|
|
// unless we are allowing access to all attributes for
|
|
// debugging.
|
|
//
|
|
|
|
if ((Attribute->TypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
(NtfsIsAttributeResident(Attribute) ||
|
|
(Attribute->Form.Nonresident.LowestVcn == 0))) {
|
|
|
|
PWCHAR StreamName;
|
|
|
|
//
|
|
// Lookup the attribute definition for this attribute code.
|
|
//
|
|
|
|
AttrDefinition = NtfsGetAttributeDefinition( Fcb->Vcb,
|
|
Attribute->TypeCode );
|
|
|
|
//
|
|
// Generate a unicode string for the attribute code name.
|
|
//
|
|
|
|
RtlInitUnicodeString( &AttributeCodeString, AttrDefinition->AttributeName );
|
|
|
|
//
|
|
//
|
|
// The size is a combination of the length of the attribute
|
|
// code name and the attribute name plus the separating
|
|
// colons plus the size of the structure. We first compute
|
|
// the name length.
|
|
//
|
|
|
|
NameLength = ((2 + Attribute->NameLength) * sizeof( WCHAR ))
|
|
+ AttributeCodeString.Length;
|
|
|
|
ThisLength = FIELD_OFFSET( FILE_STREAM_INFORMATION, StreamName[0] ) + NameLength;
|
|
|
|
//
|
|
// If the entry doesn't fit, we return buffer overflow.
|
|
//
|
|
// **** This doesn't seem like a good scheme. Maybe we should
|
|
// let the user know how much buffer was needed.
|
|
//
|
|
|
|
if (ThisLength + LastQuadAlign > *Length) {
|
|
|
|
DebugTrace( 0, Dbg, ("Next entry won't fit in the buffer \n") );
|
|
|
|
Status = STATUS_BUFFER_OVERFLOW ;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Now store the stream information into the user's buffer.
|
|
// The name starts with a colon, following by the attribute name
|
|
// and another colon, followed by the attribute code name.
|
|
//
|
|
|
|
if (NtfsIsAttributeResident( Attribute )) {
|
|
|
|
Buffer->StreamSize.QuadPart =
|
|
Attribute->Form.Resident.ValueLength;
|
|
Buffer->StreamAllocationSize.QuadPart =
|
|
QuadAlign( Attribute->Form.Resident.ValueLength );
|
|
|
|
} else {
|
|
|
|
Buffer->StreamSize.QuadPart = Attribute->Form.Nonresident.FileSize;
|
|
Buffer->StreamAllocationSize.QuadPart = Attribute->Form.Nonresident.AllocatedLength;
|
|
}
|
|
|
|
Buffer->StreamNameLength = NameLength;
|
|
|
|
StreamName = (PWCHAR) Buffer->StreamName;
|
|
|
|
*StreamName = L':';
|
|
StreamName += 1;
|
|
|
|
RtlCopyMemory( StreamName,
|
|
Add2Ptr( Attribute, Attribute->NameOffset ),
|
|
Attribute->NameLength * sizeof( WCHAR ));
|
|
|
|
StreamName += Attribute->NameLength;
|
|
|
|
*StreamName = L':';
|
|
StreamName += 1;
|
|
|
|
RtlCopyMemory( StreamName,
|
|
AttributeCodeString.Buffer,
|
|
AttributeCodeString.Length );
|
|
|
|
//
|
|
// Set up the previous next entry offset to point to this entry.
|
|
//
|
|
|
|
*((PULONG)(&UserBuffer[LastEntry])) = NextEntry - LastEntry;
|
|
|
|
//
|
|
// Subtract the number of bytes used from the number of bytes
|
|
// available in the buffer.
|
|
//
|
|
|
|
*Length -= (ThisLength + LastQuadAlign);
|
|
|
|
//
|
|
// Compute the number of bytes needed to quad-align this entry
|
|
// and the offset of the next entry.
|
|
//
|
|
|
|
LastQuadAlign = QuadAlign( ThisLength ) - ThisLength;
|
|
|
|
LastEntry = NextEntry;
|
|
NextEntry += (ThisLength + LastQuadAlign);
|
|
|
|
//
|
|
// Generate a pointer at the next entry offset.
|
|
//
|
|
|
|
Buffer = (PFILE_STREAM_INFORMATION) Add2Ptr( UserBuffer, NextEntry );
|
|
}
|
|
|
|
//
|
|
// Look for the next attribute in the file.
|
|
//
|
|
|
|
MoreToGo = NtfsLookupNextAttribute( IrpContext,
|
|
Fcb,
|
|
&AttrContext );
|
|
|
|
Attribute = NtfsFoundAttribute( &AttrContext );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsQueryStreamsInfo );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryStreamInfo -> 0x%8lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsQueryCompressedFileSize (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_COMPRESSION_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Lookup the attribute and pin it so that we can modify it.
|
|
//
|
|
|
|
//
|
|
// Reduce the buffer length by the size of the fixed part of the structure.
|
|
//
|
|
|
|
if (*Length < sizeof(FILE_COMPRESSION_INFORMATION) ) {
|
|
|
|
*Length = 0;
|
|
NtfsRaiseStatus( IrpContext, STATUS_BUFFER_OVERFLOW, NULL, NULL );
|
|
}
|
|
|
|
if ((Scb->Header.NodeTypeCode == NTFS_NTC_SCB_INDEX) ||
|
|
(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX)) {
|
|
|
|
Buffer->CompressedFileSize = Li0;
|
|
|
|
} else {
|
|
|
|
Buffer->CompressedFileSize.QuadPart = Scb->TotalAllocated;
|
|
}
|
|
|
|
//
|
|
// Do not return more than FileSize.
|
|
//
|
|
|
|
if (Buffer->CompressedFileSize.QuadPart > Scb->Header.FileSize.QuadPart) {
|
|
|
|
Buffer->CompressedFileSize = Scb->Header.FileSize;
|
|
}
|
|
|
|
//
|
|
// Start off saying that the file/directory isn't comressed
|
|
//
|
|
|
|
Buffer->CompressionFormat = 0;
|
|
|
|
//
|
|
// If this is the index allocation Scb and it has not been initialized then
|
|
// lookup the index root and perform the initialization.
|
|
//
|
|
|
|
if ((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
|
|
(Scb->ScbType.Index.BytesPerIndexBuffer == 0)) {
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
//
|
|
// Use a try-finally to perform cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
if (!NtfsLookupAttributeByName( IrpContext,
|
|
Scb->Fcb,
|
|
&Scb->Fcb->FileReference,
|
|
$INDEX_ROOT,
|
|
&Scb->AttributeName,
|
|
NULL,
|
|
FALSE,
|
|
&Context )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|
}
|
|
|
|
NtfsUpdateIndexScbFromAttribute( IrpContext,
|
|
Scb,
|
|
NtfsFoundAttribute( &Context ),
|
|
FALSE );
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return the compression state and the size of the returned data.
|
|
//
|
|
|
|
Buffer->CompressionFormat = (USHORT)(Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
|
|
|
|
if (Buffer->CompressionFormat != 0) {
|
|
Buffer->CompressionFormat += 1;
|
|
Buffer->ClusterShift = (UCHAR)Scb->Vcb->ClusterShift;
|
|
Buffer->CompressionUnitShift = (UCHAR)(Scb->CompressionUnitShift + Buffer->ClusterShift);
|
|
Buffer->ChunkShift = NTFS_CHUNK_SHIFT;
|
|
}
|
|
|
|
*Length -= sizeof(FILE_COMPRESSION_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
NtfsQueryNetworkOpenInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
|
|
IN OUT PULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query network open information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB Fcb;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsQueryNetworkOpenInfo...\n") );
|
|
|
|
Fcb = Scb->Fcb;
|
|
|
|
//
|
|
// If the Scb is uninitialized, we initialize it now.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
|
|
(Scb->AttributeTypeCode != $INDEX_ALLOCATION)) {
|
|
|
|
DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
//
|
|
// Update the length.
|
|
//
|
|
|
|
*Length -= sizeof( FILE_NETWORK_OPEN_INFORMATION );
|
|
|
|
//
|
|
// Copy over the data.
|
|
//
|
|
|
|
NtfsFillNetworkOpenInfo( Buffer, Scb );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
|
|
DebugTrace( -1, Dbg, ("NtfsQueryNetworkOpenInfo -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set basic information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this operation
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PFCB Fcb;
|
|
ULONG UsnReason = 0;
|
|
ULONG NewCcbFlags = 0;
|
|
|
|
PFILE_BASIC_INFORMATION Buffer;
|
|
ULONG PreviousFileAttributes = Scb->Fcb->Info.FileAttributes;
|
|
|
|
BOOLEAN LeaveChangeTime = BooleanFlagOn( Ccb->Flags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME );
|
|
|
|
LONGLONG CurrentTime;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetBasicInfo...\n") );
|
|
|
|
Fcb = Scb->Fcb;
|
|
|
|
//
|
|
// Reference the system buffer containing the user specified basic
|
|
// information record
|
|
//
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Remember the source info flags in the Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
|
|
//
|
|
// If the user is specifying -1 for a field, that means
|
|
// we should leave that field unchanged, even if we might
|
|
// have otherwise set it ourselves. We'll set the
|
|
// Ccb flag saying the user set the field so that we
|
|
// don't do our default updating.
|
|
//
|
|
// We set the field to 0 then so we know not to actually
|
|
// set the field to the user-specified (and in this case,
|
|
// illegal) value.
|
|
//
|
|
|
|
if (Buffer->ChangeTime.QuadPart == -1) {
|
|
|
|
SetFlag( NewCcbFlags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME );
|
|
Buffer->ChangeTime.QuadPart = 0;
|
|
|
|
//
|
|
// This timestamp is special -- sometimes even this very
|
|
// function wants to update the ChangeTime, but if the
|
|
// user is asking us not to, we shouldn't.
|
|
//
|
|
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
|
|
if (Buffer->LastAccessTime.QuadPart == -1) {
|
|
|
|
SetFlag( NewCcbFlags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
|
|
Buffer->LastAccessTime.QuadPart = 0;
|
|
}
|
|
|
|
if (Buffer->LastWriteTime.QuadPart == -1) {
|
|
|
|
SetFlag( NewCcbFlags, CCB_FLAG_USER_SET_LAST_MOD_TIME );
|
|
Buffer->LastWriteTime.QuadPart = 0;
|
|
}
|
|
|
|
if (Buffer->CreationTime.QuadPart == -1) {
|
|
|
|
//
|
|
// We only set the creation time at creation time anyway (how
|
|
// appropriate), so we don't need to set a Ccb flag in this
|
|
// case. In fact, there isn't even a Ccb flag to signify
|
|
// that the user set the creation time.
|
|
//
|
|
|
|
Buffer->CreationTime.QuadPart = 0;
|
|
}
|
|
|
|
//
|
|
// Do a quick check to see there are any illegal time stamps being set.
|
|
// Ntfs supports all values of Nt time as long as the uppermost bit
|
|
// isn't set.
|
|
//
|
|
|
|
if (FlagOn( Buffer->ChangeTime.HighPart, 0x80000000 ) ||
|
|
FlagOn( Buffer->CreationTime.HighPart, 0x80000000 ) ||
|
|
FlagOn( Buffer->LastAccessTime.HighPart, 0x80000000 ) ||
|
|
FlagOn( Buffer->LastWriteTime.HighPart, 0x80000000 )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
NtfsGetCurrentTime( IrpContext, CurrentTime );
|
|
|
|
//
|
|
// Pick up any changes from the fast Io path now while we have the
|
|
// file exclusive.
|
|
//
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
|
|
|
|
//
|
|
// If the user specified a non-zero file attributes field then
|
|
// we need to change the file attributes. This code uses the
|
|
// I/O supplied system buffer to modify the file attributes field
|
|
// before changing its value on the disk.
|
|
//
|
|
|
|
if (Buffer->FileAttributes != 0) {
|
|
|
|
//
|
|
// Check for valid flags being passed in. We fail if this is
|
|
// a directory and the TEMPORARY bit is used. Also fail if this
|
|
// is a file and the DIRECTORY bit is used.
|
|
//
|
|
|
|
if (Scb->AttributeTypeCode == $DATA) {
|
|
|
|
if (FlagOn( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
} else if (IsDirectory( &Fcb->Info )) {
|
|
|
|
if (FlagOn( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clear out the normal bit and the directory bit as well as any unsupported
|
|
// bits.
|
|
//
|
|
|
|
ClearFlag( Buffer->FileAttributes,
|
|
~FILE_ATTRIBUTE_VALID_SET_FLAGS | FILE_ATTRIBUTE_NORMAL );
|
|
|
|
//
|
|
// Update the attributes in the Fcb if this is a change to the file.
|
|
// We want to keep the flags that the user can't set.
|
|
//
|
|
|
|
Fcb->Info.FileAttributes = (Fcb->Info.FileAttributes & ~FILE_ATTRIBUTE_VALID_SET_FLAGS) |
|
|
Buffer->FileAttributes;
|
|
|
|
ASSERTMSG( "conflict with flush",
|
|
NtfsIsSharedFcb( Fcb ) ||
|
|
(Fcb->PagingIoResource != NULL &&
|
|
NtfsIsSharedFcbPagingIo( Fcb )) );
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
|
|
|
|
//
|
|
// If this is the root directory then keep the hidden and system flags.
|
|
//
|
|
|
|
if (Fcb == Fcb->Vcb->RootIndexScb->Fcb) {
|
|
|
|
SetFlag( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN );
|
|
|
|
//
|
|
// Mark the file object temporary flag correctly.
|
|
//
|
|
|
|
} else if (FlagOn(Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY)) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TEMPORARY );
|
|
SetFlag( FileObject->Flags, FO_TEMPORARY_FILE );
|
|
|
|
} else {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_TEMPORARY );
|
|
ClearFlag( FileObject->Flags, FO_TEMPORARY_FILE );
|
|
}
|
|
|
|
if (!LeaveChangeTime) {
|
|
|
|
Fcb->Info.LastChangeTime = CurrentTime;
|
|
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
|
|
//
|
|
// Post a Usn change if the file attribute change.
|
|
//
|
|
|
|
if (PreviousFileAttributes != Fcb->Info.FileAttributes) {
|
|
|
|
UsnReason = USN_REASON_BASIC_INFO_CHANGE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Propagate the new Ccb flags to the Ccb now that we know we won't fail.
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, NewCcbFlags );
|
|
|
|
//
|
|
// If the user specified a non-zero change time then change
|
|
// the change time on the record. Then do the exact same
|
|
// for the last acces time, last write time, and creation time
|
|
//
|
|
|
|
if (Buffer->ChangeTime.QuadPart != 0) {
|
|
|
|
if (Fcb->Info.LastChangeTime != Buffer->ChangeTime.QuadPart) {
|
|
UsnReason = USN_REASON_BASIC_INFO_CHANGE;
|
|
}
|
|
|
|
Fcb->Info.LastChangeTime = Buffer->ChangeTime.QuadPart;
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME );
|
|
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
|
|
if (Buffer->CreationTime.QuadPart != 0) {
|
|
|
|
if (Fcb->Info.CreationTime != Buffer->CreationTime.QuadPart) {
|
|
UsnReason = USN_REASON_BASIC_INFO_CHANGE;
|
|
}
|
|
|
|
Fcb->Info.CreationTime = Buffer->CreationTime.QuadPart;
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
|
|
|
|
if (!LeaveChangeTime) {
|
|
|
|
Fcb->Info.LastChangeTime = CurrentTime;
|
|
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
}
|
|
|
|
if (Buffer->LastAccessTime.QuadPart != 0) {
|
|
|
|
if (Fcb->CurrentLastAccess != Buffer->LastAccessTime.QuadPart) {
|
|
UsnReason = USN_REASON_BASIC_INFO_CHANGE;
|
|
}
|
|
|
|
Fcb->CurrentLastAccess = Fcb->Info.LastAccessTime = Buffer->LastAccessTime.QuadPart;
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
|
|
|
|
if (!LeaveChangeTime) {
|
|
|
|
Fcb->Info.LastChangeTime = CurrentTime;
|
|
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
}
|
|
|
|
if (Buffer->LastWriteTime.QuadPart != 0) {
|
|
|
|
if (Fcb->Info.LastModificationTime != Buffer->LastWriteTime.QuadPart) {
|
|
UsnReason = USN_REASON_BASIC_INFO_CHANGE;
|
|
}
|
|
|
|
Fcb->Info.LastModificationTime = Buffer->LastWriteTime.QuadPart;
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_MOD );
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_MOD_TIME );
|
|
|
|
if (!LeaveChangeTime) {
|
|
|
|
Fcb->Info.LastChangeTime = CurrentTime;
|
|
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
LeaveChangeTime = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now indicate that we should not be updating the standard information attribute anymore
|
|
// on cleanup.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
|
|
|
|
//
|
|
// Check if the index bit changed.
|
|
//
|
|
|
|
if (FlagOn( PreviousFileAttributes ^ Fcb->Info.FileAttributes,
|
|
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED )) {
|
|
|
|
SetFlag( UsnReason, USN_REASON_INDEXABLE_CHANGE );
|
|
}
|
|
|
|
//
|
|
// Post the change to the Usn Journal
|
|
//
|
|
|
|
if (UsnReason != 0) {
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, UsnReason );
|
|
}
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&Scb->Header.ValidDataLength.QuadPart,
|
|
FALSE,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
}
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
|
|
}
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetDispositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set disposition information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this handle
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PLCB Lcb;
|
|
BOOLEAN GenerateOnClose = FALSE;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
HANDLE FileHandle = NULL;
|
|
|
|
PFILE_DISPOSITION_INFORMATION Buffer;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetDispositionInfo...\n") );
|
|
|
|
//
|
|
// First pull the file handle out of the irp
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
FileHandle = IrpSp->Parameters.SetFile.DeleteHandle;
|
|
|
|
//
|
|
// We get the Lcb for this open. If there is no link then we can't
|
|
// set any disposition information if this is a file.
|
|
//
|
|
|
|
Lcb = Ccb->Lcb;
|
|
|
|
if ((Lcb == NULL) &&
|
|
FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetDispositionInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Reference the system buffer containing the user specified disposition
|
|
// information record
|
|
//
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
try {
|
|
|
|
if (Buffer->DeleteFile) {
|
|
|
|
//
|
|
// Check if the file is marked read only
|
|
//
|
|
|
|
if (IsReadOnly( &Scb->Fcb->Info )) {
|
|
|
|
DebugTrace( 0, Dbg, ("File fat flags indicates read only\n") );
|
|
|
|
try_return( Status = STATUS_CANNOT_DELETE );
|
|
}
|
|
|
|
//
|
|
// Make sure there is no process mapping this file as an image
|
|
//
|
|
|
|
if (!MmFlushImageSection( &Scb->NonpagedScb->SegmentObject,
|
|
MmFlushForDelete )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Failed to flush image section\n") );
|
|
|
|
try_return( Status = STATUS_CANNOT_DELETE );
|
|
}
|
|
|
|
//
|
|
// Check that we are not trying to delete one of the special
|
|
// system files.
|
|
//
|
|
|
|
if (FlagOn( Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Scb is one of the special system files\n") );
|
|
|
|
try_return( Status = STATUS_CANNOT_DELETE );
|
|
}
|
|
|
|
//
|
|
// Only do the auditing if we have a user handle. We verify that the FileHandle
|
|
// is still valid and hasn't gone through close. Note we first check the CCB state
|
|
// to see if the cleanup has been issued. If the CCB state is valid then we are
|
|
// guaranteed the handle couldn't have been reused by the object manager even if
|
|
// the user close in another thread has gone through OB. This is because this request
|
|
// is serialized with Ntfs cleanup.
|
|
//
|
|
|
|
if (FileHandle != NULL) {
|
|
|
|
//
|
|
// Check for serialization with Ntfs cleanup first.
|
|
//
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_CLEANUP )) {
|
|
|
|
DebugTrace( 0, Dbg, ("This call issued after cleanup\n") );
|
|
try_return( Status = STATUS_INVALID_HANDLE );
|
|
}
|
|
|
|
Status = ObQueryObjectAuditingByHandle( FileHandle,
|
|
&GenerateOnClose );
|
|
|
|
//
|
|
// Fail the request if the object manager doesn't recognize the handle.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Object manager fails to recognize handle\n") );
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now check that the file is really deleteable according to indexsup
|
|
//
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
BOOLEAN LastLink;
|
|
BOOLEAN NonEmptyIndex = FALSE;
|
|
|
|
//
|
|
// If the link is not deleted, we check if it can be deleted.
|
|
//
|
|
|
|
if (!LcbLinkIsDeleted( Lcb )) {
|
|
|
|
if (NtfsIsLinkDeleteable( IrpContext, Scb->Fcb, &NonEmptyIndex, &LastLink )) {
|
|
|
|
//
|
|
// It is ok to get rid of this guy. All we need to do is
|
|
// mark this Lcb for delete and decrement the link count
|
|
// in the Fcb. If this is a primary link, then we
|
|
// indicate that the primary link has been deleted.
|
|
//
|
|
|
|
SetFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
ASSERTMSG( "Link count should not be 0\n", Scb->Fcb->LinkCount != 0 );
|
|
Scb->Fcb->LinkCount -= 1;
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
|
|
|
|
SetFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
|
|
}
|
|
|
|
//
|
|
// Call into the notify package to close any handles on
|
|
// a directory being deleted.
|
|
//
|
|
|
|
if (IsDirectory( &Scb->Fcb->Info )) {
|
|
|
|
FsRtlNotifyFilterChangeDirectory( Scb->Vcb->NotifySync,
|
|
&Scb->Vcb->DirNotifyList,
|
|
FileObject->FsContext,
|
|
NULL,
|
|
FALSE,
|
|
FALSE,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
|
|
} else if (NonEmptyIndex) {
|
|
|
|
DebugTrace( 0, Dbg, ("Index attribute has entries\n") );
|
|
try_return( Status = STATUS_DIRECTORY_NOT_EMPTY );
|
|
|
|
} else {
|
|
|
|
DebugTrace( 0, Dbg, ("File is not deleteable\n") );
|
|
try_return( Status = STATUS_CANNOT_DELETE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise we are simply removing the attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
}
|
|
|
|
//
|
|
// Indicate in the file object that a delete is pending
|
|
//
|
|
|
|
FileObject->DeletePending = TRUE;
|
|
|
|
//
|
|
// Now do the audit.
|
|
//
|
|
|
|
if ((FileHandle != NULL) && GenerateOnClose) {
|
|
|
|
SeDeleteObjectAuditAlarm( FileObject, FileHandle );
|
|
}
|
|
|
|
} else {
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
if (LcbLinkIsDeleted( Lcb )) {
|
|
|
|
//
|
|
// The user doesn't want to delete the link so clear any delete bits
|
|
// we have laying around
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, ("File is being marked as do not delete on close\n") );
|
|
|
|
ClearFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
Scb->Fcb->LinkCount += 1;
|
|
ASSERTMSG( "Link count should not be 0\n", Scb->Fcb->LinkCount != 0 );
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
|
|
|
|
ClearFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Otherwise we are undeleting an attribute.
|
|
//
|
|
|
|
} else {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
|
|
}
|
|
|
|
FileObject->DeletePending = FALSE;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsSetDispositionInfo );
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetDispositionInfo -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetRenameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PBOOLEAN VcbAcquired
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set rename function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Vcb - Supplies the Vcb for the Volume
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this file object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
PLCB Lcb = Ccb->Lcb;
|
|
PFCB Fcb = Scb->Fcb;
|
|
PSCB ParentScb;
|
|
USHORT FcbLinkCountAdj = 0;
|
|
|
|
BOOLEAN AcquiredParentScb = TRUE;
|
|
BOOLEAN AcquiredObjectIdIndex = FALSE;
|
|
BOOLEAN AcquiredReparsePointIndex = FALSE;
|
|
|
|
PFCB TargetLinkFcb = NULL;
|
|
BOOLEAN ExistingTargetLinkFcb;
|
|
BOOLEAN AcquiredTargetLinkFcb = FALSE;
|
|
USHORT TargetLinkFcbCountAdj = 0;
|
|
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
PFCB FcbWithPagingToRelease = NULL;
|
|
|
|
PFILE_OBJECT TargetFileObject;
|
|
PSCB TargetParentScb;
|
|
|
|
UNICODE_STRING NewLinkName;
|
|
UNICODE_STRING NewFullLinkName;
|
|
PWCHAR NewFullLinkNameBuffer = NULL;
|
|
UCHAR NewLinkNameFlags;
|
|
|
|
PFILE_NAME FileNameAttr = NULL;
|
|
USHORT FileNameAttrLength = 0;
|
|
|
|
UNICODE_STRING PrevLinkName;
|
|
UNICODE_STRING PrevFullLinkName;
|
|
UCHAR PrevLinkNameFlags;
|
|
|
|
UNICODE_STRING SourceFullLinkName;
|
|
USHORT SourceLinkLastNameOffset;
|
|
|
|
BOOLEAN FoundLink;
|
|
PINDEX_ENTRY IndexEntry;
|
|
PBCB IndexEntryBcb = NULL;
|
|
PWCHAR NextChar;
|
|
|
|
BOOLEAN ReportDirNotify = FALSE;
|
|
|
|
ULONG RenameFlags = ACTIVELY_REMOVE_SOURCE_LINK | REMOVE_SOURCE_LINK | ADD_TARGET_LINK;
|
|
|
|
PFCB_USN_RECORD SavedFcbUsnRecord = NULL;
|
|
ULONG SavedUsnReason = 0;
|
|
|
|
NAME_PAIR NamePair;
|
|
NTFS_TUNNELED_DATA TunneledData;
|
|
ULONG TunneledDataSize;
|
|
BOOLEAN HaveTunneledInformation = FALSE;
|
|
PFCB LockedFcb = NULL;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE ();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetRenameInfo...\n") );
|
|
|
|
//
|
|
// See if we are doing a stream rename. The allowed inputs are:
|
|
// No associated file object.
|
|
// Rename Name begins with a colon
|
|
// If so, perform the rename
|
|
//
|
|
|
|
TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
|
|
|
|
if (TargetFileObject == NULL) {
|
|
PFILE_RENAME_INFORMATION FileRename;
|
|
|
|
FileRename = IrpContext->OriginatingIrp->AssociatedIrp.SystemBuffer;
|
|
|
|
if (FileRename->FileNameLength >= sizeof( WCHAR ) &&
|
|
FileRename->FileName[0] == L':') {
|
|
|
|
NewLinkName.Buffer = FileRename->FileName;
|
|
NewLinkName.MaximumLength =
|
|
NewLinkName.Length = (USHORT) FileRename->FileNameLength;
|
|
|
|
Status = NtfsStreamRename( IrpContext, FileObject, Fcb, Scb, Ccb, FileRename->ReplaceIfExists, &NewLinkName );
|
|
DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do a quick check that the caller is allowed to do the rename.
|
|
// The opener must have opened the main data stream by name and this can't be
|
|
// a system file.
|
|
//
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE ) ||
|
|
(Lcb == NULL) ||
|
|
FlagOn(Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// If this link has been deleted, then we don't allow this operation.
|
|
//
|
|
|
|
if (LcbLinkIsDeleted( Lcb )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", STATUS_ACCESS_DENIED) );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Verify that we can wait.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
|
|
|
|
Status = NtfsPostRequest( IrpContext, Irp );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Can't wait\n") );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Remember the source info flags in the Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Initialize the local variables.
|
|
//
|
|
|
|
ParentScb = Lcb->Scb;
|
|
NtfsInitializeNamePair( &NamePair );
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
|
|
(Vcb->NotifyCount != 0)) {
|
|
|
|
ReportDirNotify = TRUE;
|
|
}
|
|
|
|
PrevFullLinkName.Buffer = NULL;
|
|
SourceFullLinkName.Buffer = NULL;
|
|
|
|
//
|
|
// If this is a directory file, we need to examine its descendents.
|
|
// We may not remove a link which may be an ancestor path
|
|
// component of any open file.
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info )) {
|
|
|
|
Status = NtfsCheckTreeForBatchOplocks( IrpContext, Irp, Scb );
|
|
|
|
if (Status != STATUS_SUCCESS) { leave; }
|
|
}
|
|
|
|
//
|
|
// We now assemble the names and in memory-structures for both the
|
|
// source and target links and check if the target link currently
|
|
// exists.
|
|
//
|
|
|
|
NtfsFindTargetElements( IrpContext,
|
|
TargetFileObject,
|
|
ParentScb,
|
|
&TargetParentScb,
|
|
&NewFullLinkName,
|
|
&NewLinkName );
|
|
|
|
//
|
|
// Check that the new name is not invalid.
|
|
//
|
|
|
|
if ((NewLinkName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) ||
|
|
!NtfsIsFileNameValid( &NewLinkName, FALSE )) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Acquire the current parent in order to synchronize removing the current name.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
|
|
//
|
|
// If this Scb does not have a normalized name then provide it with one now.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
&ParentScb->ScbType.Index.NormalizedName );
|
|
}
|
|
|
|
//
|
|
// If this is a directory then make sure it has a normalized name.
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info ) &&
|
|
(Scb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
ParentScb,
|
|
Scb,
|
|
NULL,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Check if we are renaming to the same directory with the exact same name.
|
|
//
|
|
|
|
if (TargetParentScb == ParentScb) {
|
|
|
|
if (NtfsAreNamesEqual( Vcb->UpcaseTable, &NewLinkName, &Lcb->ExactCaseLink.LinkName, FALSE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Renaming to same name and directory\n") );
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Otherwise we want to acquire the target directory.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// We need to do the acquisition carefully since we may only have the Vcb shared.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext,
|
|
TargetParentScb->Fcb,
|
|
TargetParentScb,
|
|
ACQUIRE_DONT_WAIT )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Now snapshot the Scb.
|
|
//
|
|
|
|
if (FlagOn( TargetParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, TargetParentScb );
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, TargetParentScb );
|
|
}
|
|
|
|
SetFlag( RenameFlags, MOVE_TO_NEW_DIR );
|
|
}
|
|
|
|
//
|
|
// We also determine which type of link to
|
|
// create. We create a hard link only unless the source link is
|
|
// a primary link and the user is an IgnoreCase guy.
|
|
//
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ) &&
|
|
FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) {
|
|
|
|
SetFlag( RenameFlags, ADD_PRIMARY_LINK );
|
|
}
|
|
|
|
//
|
|
// Lookup the entry for this filename in the target directory.
|
|
// We look in the Ccb for the type of case match for the target
|
|
// name.
|
|
//
|
|
|
|
FoundLink = NtfsLookupEntry( IrpContext,
|
|
TargetParentScb,
|
|
BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
|
|
&NewLinkName,
|
|
&FileNameAttr,
|
|
&FileNameAttrLength,
|
|
NULL,
|
|
&IndexEntry,
|
|
&IndexEntryBcb,
|
|
NULL );
|
|
|
|
//
|
|
// This call to NtfsLookupEntry may decide to push the root index,
|
|
// in which case we might be holding the Mft now. If there is a
|
|
// transaction, commit it now so we will be able to free the Mft to
|
|
// eliminate a potential deadlock with the ObjectId index when we
|
|
// look up the object id for the rename source to add it to the
|
|
// tunnel cache.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Go through and free any Scb's in the queue of shared
|
|
// Scb's for transactions.
|
|
//
|
|
|
|
if (IrpContext->SharedScb != NULL) {
|
|
|
|
NtfsReleaseSharedResources( IrpContext );
|
|
ASSERT( IrpContext->SharedScb == NULL );
|
|
}
|
|
|
|
//
|
|
// Release the mft, if we acquired it in pushing the root index.
|
|
//
|
|
|
|
NtfsReleaseExclusiveScbIfOwned( IrpContext, Vcb->MftScb );
|
|
}
|
|
|
|
//
|
|
// If we found a matching link, we need to check how we want to operate
|
|
// on the source link and the target link. This means whether we
|
|
// have any work to do, whether we need to remove the target link
|
|
// and whether we need to remove the source link.
|
|
//
|
|
|
|
if (FoundLink) {
|
|
|
|
PFILE_NAME IndexFileName;
|
|
|
|
//
|
|
// Assume we will remove this link.
|
|
//
|
|
|
|
SetFlag( RenameFlags, REMOVE_TARGET_LINK );
|
|
|
|
IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
|
|
|
|
NtfsCheckLinkForRename( Fcb,
|
|
Lcb,
|
|
IndexFileName,
|
|
IndexEntry->FileReference,
|
|
&NewLinkName,
|
|
BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
|
|
&RenameFlags );
|
|
|
|
//
|
|
// Assume we will use the existing name flags on the link found. This
|
|
// will be the case where the file was opened with the 8.3 name and
|
|
// the new name is exactly the long name for the same file.
|
|
//
|
|
|
|
PrevLinkNameFlags =
|
|
NewLinkNameFlags = IndexFileName->Flags;
|
|
|
|
//
|
|
// If we didn't have an exact match, then we need to check if we
|
|
// can remove the found link and then remove it from the disk.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TARGET_LINK )) {
|
|
|
|
//
|
|
// We need to check that the user wanted to remove that link.
|
|
//
|
|
|
|
if (!FlagOn( RenameFlags, TRAVERSE_MATCH ) &&
|
|
!IrpSp->Parameters.SetFile.ReplaceIfExists) {
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// We want to preserve the case and the flags of the matching
|
|
// link found. We also want to preserve the case of the
|
|
// name being created. The following variables currently contain
|
|
// the exact case for the target to remove and the new name to
|
|
// apply.
|
|
//
|
|
// Link to remove - In 'IndexEntry'.
|
|
// The link's flags are also in 'IndexEntry'. We copy
|
|
// these flags to 'PrevLinkNameFlags'
|
|
//
|
|
// New Name - Exact case is stored in 'NewLinkName'
|
|
// - It is also in 'FileNameAttr
|
|
//
|
|
// We modify this so that we can use the FileName attribute
|
|
// structure to create the new link. We copy the linkname being
|
|
// removed into 'PrevLinkName'. The following is the
|
|
// state after the switch.
|
|
//
|
|
// 'FileNameAttr' - contains the name for the link being
|
|
// created.
|
|
//
|
|
// 'PrevLinkFileName' - Contains the link name for the link being
|
|
// removed.
|
|
//
|
|
// 'PrevLinkFileNameFlags' - Contains the name flags for the link
|
|
// being removed.
|
|
//
|
|
|
|
//
|
|
// Allocate a buffer for the name being removed. It should be
|
|
// large enough for the entire directory name.
|
|
//
|
|
|
|
PrevFullLinkName.MaximumLength = TargetParentScb->ScbType.Index.NormalizedName.Length +
|
|
sizeof( WCHAR ) +
|
|
(IndexFileName->FileNameLength * sizeof( WCHAR ));
|
|
|
|
PrevFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
|
|
PrevFullLinkName.MaximumLength );
|
|
|
|
RtlCopyMemory( PrevFullLinkName.Buffer,
|
|
TargetParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
TargetParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
NextChar = Add2Ptr( PrevFullLinkName.Buffer,
|
|
TargetParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
if (TargetParentScb != Vcb->RootIndexScb) {
|
|
|
|
*NextChar = L'\\';
|
|
NextChar += 1;
|
|
}
|
|
|
|
RtlCopyMemory( NextChar,
|
|
IndexFileName->FileName,
|
|
IndexFileName->FileNameLength * sizeof( WCHAR ));
|
|
|
|
//
|
|
// Copy the name found in the Index Entry to 'PrevLinkName'
|
|
//
|
|
|
|
PrevLinkName.Buffer = NextChar;
|
|
PrevLinkName.MaximumLength =
|
|
PrevLinkName.Length = IndexFileName->FileNameLength * sizeof( WCHAR );
|
|
|
|
//
|
|
// Update the full name length with the final component.
|
|
//
|
|
|
|
PrevFullLinkName.Length = (USHORT) PtrOffset( PrevFullLinkName.Buffer, NextChar ) + PrevLinkName.Length;
|
|
|
|
//
|
|
// We only need this check if the link is for a different file.
|
|
//
|
|
|
|
if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
//
|
|
// We check if there is an existing Fcb for the target link.
|
|
// If there is, the unclean count better be 0.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
TargetLinkFcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
IndexEntry->FileReference,
|
|
FALSE,
|
|
BooleanFlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ),
|
|
&ExistingTargetLinkFcb );
|
|
|
|
//
|
|
// Before we go on, make sure we aren't about to rename over a system file.
|
|
//
|
|
|
|
if (FlagOn( TargetLinkFcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If there is no paging resource, then try to create one
|
|
// by first locking the fcb then make sure there is
|
|
// still no paging resource before adding one.
|
|
// Once PagingIoResource is allocated, it will not go away.
|
|
// That's why it is safe to peek at it before locking it
|
|
// as long as pointer value is atomic.
|
|
//
|
|
|
|
if (TargetLinkFcb->PagingIoResource == NULL) {
|
|
|
|
//
|
|
// Increase the reference count so that the fcb can't go away.
|
|
// This need to be done while holding onto the fcb table.
|
|
//
|
|
|
|
TargetLinkFcb->ReferenceCount += 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Add a paging resource to the target - this is not supplied if its created
|
|
// from scratch. We need this (acquired in the proper order) for the delete
|
|
// to work correctly if there are any data streams. It's not going to harm a
|
|
// directory del and because of the teardown in the finally clause its difficult
|
|
// to retry again without looping.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, TargetLinkFcb );
|
|
LockedFcb = TargetLinkFcb;
|
|
|
|
if (TargetLinkFcb->PagingIoResource == NULL) {
|
|
|
|
TargetLinkFcb->PagingIoResource = NtfsAllocateEresource();
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, LockedFcb );
|
|
LockedFcb = NULL;
|
|
|
|
//
|
|
// Now decrease the reference count while holding the fcb table
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
TargetLinkFcb->ReferenceCount -= 1;
|
|
}
|
|
|
|
//
|
|
// We need to acquire this file carefully in the event that we don't hold
|
|
// the Vcb exclusively.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
if (!NtfsAcquirePagingResourceExclusive( IrpContext, TargetLinkFcb, FALSE )) {
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
FcbWithPagingToRelease = TargetLinkFcb;
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, ACQUIRE_DONT_WAIT )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
} else {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Acquire the paging Io resource for this file before the main
|
|
// resource in case we need to delete.
|
|
//
|
|
|
|
FcbWithPagingToRelease = TargetLinkFcb;
|
|
|
|
NtfsAcquirePagingResourceExclusive( IrpContext, FcbWithPagingToRelease, TRUE );
|
|
NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, 0 );
|
|
}
|
|
|
|
AcquiredTargetLinkFcb = TRUE;
|
|
|
|
//
|
|
// If the Fcb Info field needs to be initialized, we do so now.
|
|
// We read this information from the disk as the duplicate information
|
|
// in the index entry is not guaranteed to be correct.
|
|
//
|
|
|
|
if (!FlagOn( TargetLinkFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
TargetLinkFcb,
|
|
NULL );
|
|
NtfsConditionallyFixupQuota( IrpContext, TargetLinkFcb );
|
|
|
|
//
|
|
// Commit any fixups and release the quota index so we don't deadlock
|
|
// acquiring the objectid when doing any tunnelling below
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
NtfsReleaseSharedResources( IrpContext );
|
|
ASSERTMSG( "Ntfs: we should not own the mftscb\n", !NtfsIsSharedScb( Vcb->MftScb ) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We are adding a link to the source file which already
|
|
// exists as a link to a different file in the target directory.
|
|
//
|
|
// We need to check whether we permitted to delete this
|
|
// link. If not then it is possible that the problem is
|
|
// an existing batch oplock on the file. In that case
|
|
// we want to delete the batch oplock and try this again.
|
|
//
|
|
|
|
Status = NtfsCheckFileForDelete( IrpContext,
|
|
TargetParentScb,
|
|
TargetLinkFcb,
|
|
ExistingTargetLinkFcb,
|
|
IndexEntry );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
PSCB NextScb = NULL;
|
|
|
|
//
|
|
// We are going to either fail this request or pass
|
|
// this on to the oplock package. Test if there is
|
|
// a batch oplock on any streams on this file.
|
|
//
|
|
|
|
while ((NextScb = NtfsGetNextChildScb( TargetLinkFcb,
|
|
NextScb )) != NULL) {
|
|
|
|
if ((NextScb->AttributeTypeCode == $DATA) &&
|
|
(NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
|
|
FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
|
|
|
|
if (*VcbAcquired) {
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
*VcbAcquired = FALSE;
|
|
}
|
|
|
|
Status = FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
|
|
Irp,
|
|
IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsPrePostIrp );
|
|
break;
|
|
}
|
|
}
|
|
|
|
leave;
|
|
}
|
|
|
|
NtfsCleanupLinkForRemoval( TargetLinkFcb, TargetParentScb, ExistingTargetLinkFcb );
|
|
|
|
//
|
|
// DeleteFile might need to get the reparse index to remove a reparse
|
|
// point. We may need the object id index later to deal with the
|
|
// tunnel cache. Let's acquire them in the right order now.
|
|
//
|
|
|
|
if (Vcb->ObjectIdTableScb != NULL) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
AcquiredObjectIdIndex = TRUE;
|
|
}
|
|
|
|
if (HasReparsePoint( &TargetLinkFcb->Info ) &&
|
|
(Vcb->ReparsePointTableScb != NULL)) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ReparsePointTableScb );
|
|
AcquiredReparsePointIndex = TRUE;
|
|
}
|
|
|
|
|
|
if (TargetLinkFcb->LinkCount == 1) {
|
|
|
|
PFCB TempFcb;
|
|
|
|
//
|
|
// Fixup the IrpContext CleanupStructure so deletefile logs correctly
|
|
//
|
|
|
|
TempFcb = (PFCB) IrpContext->CleanupStructure;
|
|
IrpContext->CleanupStructure = FcbWithPagingToRelease;
|
|
|
|
ASSERT( (NULL == TempFcb) || (NTFS_NTC_FCB == SafeNodeType( TempFcb )) );
|
|
|
|
FcbWithPagingToRelease = TempFcb;
|
|
|
|
NtfsDeleteFile( IrpContext,
|
|
TargetLinkFcb,
|
|
TargetParentScb,
|
|
&AcquiredParentScb,
|
|
NULL,
|
|
NULL );
|
|
|
|
FcbWithPagingToRelease = IrpContext->CleanupStructure;
|
|
IrpContext->CleanupStructure = TempFcb;
|
|
|
|
//
|
|
// Make sure to force the close record out to disk.
|
|
//
|
|
|
|
TargetLinkFcbCountAdj += 1;
|
|
|
|
} else {
|
|
NtfsPostUsnChange( IrpContext, TargetLinkFcb, USN_REASON_HARD_LINK_CHANGE | USN_REASON_CLOSE );
|
|
NtfsRemoveLink( IrpContext,
|
|
TargetLinkFcb,
|
|
TargetParentScb,
|
|
PrevLinkName,
|
|
NULL,
|
|
NULL );
|
|
|
|
ClearFlag( TargetLinkFcb->FcbState, FCB_STATE_VALID_USN_NAME );
|
|
|
|
TargetLinkFcbCountAdj += 1;
|
|
NtfsUpdateFcb( TargetLinkFcb, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
}
|
|
|
|
//
|
|
// The target link is for the same file as the source link. No security
|
|
// checks need to be done. Go ahead and remove it.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_RENAME_OLD_NAME );
|
|
|
|
TargetLinkFcb = Fcb;
|
|
NtfsRemoveLink( IrpContext,
|
|
Fcb,
|
|
TargetParentScb,
|
|
PrevLinkName,
|
|
NULL,
|
|
NULL );
|
|
|
|
FcbLinkCountAdj += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
//
|
|
// Post the Usn record for the old name. Don't write it until after
|
|
// we check if we need to remove an object ID due to tunnelling.
|
|
// Otherwise we might deadlock between the journal/mft resources
|
|
// and the object id resources.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_RENAME_OLD_NAME );
|
|
|
|
//
|
|
// See if we need to remove the current link.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
|
|
|
|
//
|
|
// Now we want to remove the source link from the file. We need to
|
|
// remember if we deleted a two part primary link.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK )) {
|
|
|
|
TunneledData.HasObjectId = FALSE;
|
|
NtfsRemoveLink( IrpContext,
|
|
Fcb,
|
|
ParentScb,
|
|
Lcb->ExactCaseLink.LinkName,
|
|
&NamePair,
|
|
&TunneledData );
|
|
|
|
//
|
|
// Remember the full name for the original filename and some
|
|
// other information to pass to the dirnotify package.
|
|
//
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
if (!IsDirectory( &Fcb->Info ) &&
|
|
!FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) {
|
|
|
|
//
|
|
// Tunnel property information for file links
|
|
//
|
|
|
|
NtfsGetTunneledData( IrpContext,
|
|
Fcb,
|
|
&TunneledData );
|
|
|
|
FsRtlAddToTunnelCache( &Vcb->Tunnel,
|
|
*(PULONGLONG)&ParentScb->Fcb->FileReference,
|
|
&NamePair.Short,
|
|
&NamePair.Long,
|
|
BooleanFlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS ),
|
|
sizeof( NTFS_TUNNELED_DATA ),
|
|
&TunneledData );
|
|
}
|
|
}
|
|
|
|
FcbLinkCountAdj += 1;
|
|
}
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
SourceFullLinkName.Buffer = NtfsAllocatePool( PagedPool, Ccb->FullFileName.Length );
|
|
|
|
RtlCopyMemory( SourceFullLinkName.Buffer,
|
|
Ccb->FullFileName.Buffer,
|
|
Ccb->FullFileName.Length );
|
|
|
|
SourceFullLinkName.MaximumLength = SourceFullLinkName.Length = Ccb->FullFileName.Length;
|
|
SourceLinkLastNameOffset = Ccb->LastFileNameOffset;
|
|
}
|
|
}
|
|
|
|
//
|
|
// See if we need to add the target link.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, ADD_TARGET_LINK )) {
|
|
|
|
//
|
|
// Check that we have permission to add a file to this directory.
|
|
//
|
|
|
|
NtfsCheckIndexForAddOrDelete( IrpContext,
|
|
TargetParentScb->Fcb,
|
|
(IsDirectory( &Fcb->Info ) ?
|
|
FILE_ADD_SUBDIRECTORY :
|
|
FILE_ADD_FILE),
|
|
Ccb->AccessFlags >> 2 );
|
|
|
|
//
|
|
// Grunge the tunnel cache for property restoration
|
|
//
|
|
|
|
if (!IsDirectory( &Fcb->Info ) &&
|
|
!FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) {
|
|
|
|
NtfsResetNamePair( &NamePair );
|
|
TunneledDataSize = sizeof( NTFS_TUNNELED_DATA );
|
|
|
|
if (FsRtlFindInTunnelCache( &Vcb->Tunnel,
|
|
*(PULONGLONG)&TargetParentScb->Fcb->FileReference,
|
|
&NewLinkName,
|
|
&NamePair.Short,
|
|
&NamePair.Long,
|
|
&TunneledDataSize,
|
|
&TunneledData)) {
|
|
|
|
ASSERT( TunneledDataSize == sizeof( NTFS_TUNNELED_DATA ));
|
|
HaveTunneledInformation = TRUE;
|
|
|
|
//
|
|
// Preacquire the objectid index which we need for tunnelling if its
|
|
// not already owned before adding the link which may acquire the mft
|
|
// if it has to allocate a record or acquire the quota index if it moves
|
|
// a data attribute around while adding the new filename
|
|
//
|
|
|
|
if ((!AcquiredObjectIdIndex && (Vcb->ObjectIdTableScb != NULL))) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
AcquiredObjectIdIndex = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now want to add the new link into the target directory.
|
|
// We create a hard link only if the source name was a hard link
|
|
// or this is a case-sensitive open. This means that we can
|
|
// replace a primary link pair with a hard link only.
|
|
//
|
|
|
|
NtfsAddLink( IrpContext,
|
|
BooleanFlagOn( RenameFlags, ADD_PRIMARY_LINK ),
|
|
TargetParentScb,
|
|
Fcb,
|
|
FileNameAttr,
|
|
NULL,
|
|
&NewLinkNameFlags,
|
|
NULL,
|
|
HaveTunneledInformation ? &NamePair : NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Restore timestamps on tunneled files
|
|
//
|
|
|
|
if (HaveTunneledInformation) {
|
|
|
|
NtfsSetTunneledData( IrpContext,
|
|
Fcb,
|
|
&TunneledData );
|
|
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
|
|
SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
//
|
|
// If we have tunneled information then copy the correct case of the
|
|
// name into the new link pointer.
|
|
//
|
|
|
|
if (NewLinkNameFlags == FILE_NAME_DOS) {
|
|
|
|
RtlCopyMemory( NewLinkName.Buffer,
|
|
NamePair.Short.Buffer,
|
|
NewLinkName.Length );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the flags field in the target file name. We will use this
|
|
// below if we are updating the normalized name.
|
|
//
|
|
|
|
FileNameAttr->Flags = NewLinkNameFlags;
|
|
|
|
if (ParentScb != TargetParentScb) {
|
|
|
|
NtfsUpdateFcb( TargetParentScb->Fcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
}
|
|
|
|
//
|
|
// If we need a full buffer for the new name for notify and don't already
|
|
// have one then construct the full name now. This will only happen if
|
|
// we are renaming within the same directory.
|
|
//
|
|
|
|
if (ReportDirNotify &&
|
|
(NewFullLinkName.Buffer == NULL)) {
|
|
|
|
NewFullLinkName.MaximumLength = Ccb->LastFileNameOffset + NewLinkName.Length;
|
|
|
|
NewFullLinkNameBuffer = NtfsAllocatePool( PagedPool,
|
|
NewFullLinkName.MaximumLength );
|
|
|
|
RtlCopyMemory( NewFullLinkNameBuffer,
|
|
Ccb->FullFileName.Buffer,
|
|
Ccb->LastFileNameOffset );
|
|
|
|
RtlCopyMemory( Add2Ptr( NewFullLinkNameBuffer, Ccb->LastFileNameOffset ),
|
|
NewLinkName.Buffer,
|
|
NewLinkName.Length );
|
|
|
|
NewFullLinkName.Buffer = NewFullLinkNameBuffer;
|
|
NewFullLinkName.Length = NewFullLinkName.MaximumLength;
|
|
}
|
|
|
|
FcbLinkCountAdj -= 1;
|
|
}
|
|
|
|
//
|
|
// Now write the Usn record for the old name if it exists. Since this call
|
|
// needs to acquire the usn journal and/or mft, we need to do this after the
|
|
// NtfsSetTunneledData call, since that may acquire the object id index.
|
|
//
|
|
|
|
if (IrpContext->Usn.CurrentUsnFcb != NULL) {
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
}
|
|
|
|
//
|
|
// We need to update the names in the Lcb for this file as well as any subdirectories
|
|
// or files. We will do this in two passes. The first pass is just to reserve enough
|
|
// space in all of the file objects and Lcb's. We update the names in the second pass.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TARGET_LINK )) {
|
|
|
|
SetFlag( RenameFlags, REMOVE_TRAVERSE_LINK );
|
|
|
|
} else {
|
|
|
|
SetFlag( RenameFlags, REUSE_TRAVERSE_LINK );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is a directory and we added a target link it means that the
|
|
// normalized name has changed. Make sure the buffer in the Scb will hold
|
|
// the larger name.
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info ) && FlagOn( RenameFlags, ADD_TARGET_LINK )) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
TargetParentScb,
|
|
Scb,
|
|
FileNameAttr,
|
|
TRUE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Now post a rename change on the Fcb. We delete the old Usn record first,
|
|
// since it has the wrong name. No need to get the mutex since we have the
|
|
// file exclusive.
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord != NULL) {
|
|
|
|
SavedFcbUsnRecord = Fcb->FcbUsnRecord;
|
|
SavedUsnReason = SavedFcbUsnRecord->UsnRecord.Reason;
|
|
if (SavedFcbUsnRecord->ModifiedOpenFilesLinks.Flink != NULL) {
|
|
NtfsLockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|
RemoveEntryList( &SavedFcbUsnRecord->ModifiedOpenFilesLinks );
|
|
|
|
if (SavedFcbUsnRecord->TimeOutLinks.Flink != NULL) {
|
|
|
|
RemoveEntryList( &SavedFcbUsnRecord->TimeOutLinks );
|
|
SavedFcbUsnRecord->ModifiedOpenFilesLinks.Flink = NULL;
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|
}
|
|
Fcb->FcbUsnRecord = NULL;
|
|
|
|
//
|
|
// Note - Fcb is unlocked immediately below in the finally clause.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Post the rename to the Usn Journal. We wait until the end, in order to
|
|
// reduce resource contention on the UsnJournal, in the event that we already
|
|
// posted a change when we deleted the target file.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext,
|
|
Scb,
|
|
(SavedUsnReason & ~USN_REASON_RENAME_OLD_NAME) | USN_REASON_RENAME_NEW_NAME );
|
|
|
|
//
|
|
// Now, if anything at all is posted to the Usn Journal, we write it now
|
|
// so we don't get a recursive failure later on
|
|
//
|
|
|
|
if (IrpContext->Usn.CurrentUsnFcb != NULL) {
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Cleanup/Raise on any recursive failures before modifying in memory structures
|
|
//
|
|
|
|
NtfsCleanupTransaction( IrpContext, Status, FALSE );
|
|
|
|
//
|
|
// We have now modified the on-disk structures. We now need to
|
|
// modify the in-memory structures. This includes the Fcb and Lcb's
|
|
// for any links we superseded, and the source Fcb and it's Lcb's.
|
|
//
|
|
// We will do this in two passes. The first pass will guarantee that all of the
|
|
// name buffers will be large enough for the names. The second pass will store the
|
|
// names into the buffers.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, MOVE_TO_NEW_DIR )) {
|
|
|
|
NtfsMoveLinkToNewDir( IrpContext,
|
|
&NewFullLinkName,
|
|
&NewLinkName,
|
|
NewLinkNameFlags,
|
|
TargetParentScb,
|
|
Fcb,
|
|
Lcb,
|
|
RenameFlags,
|
|
&PrevLinkName,
|
|
PrevLinkNameFlags );
|
|
|
|
//
|
|
// Otherwise we will rename in the current directory. We need to remember
|
|
// if we have merged with an existing link on this file.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsRenameLinkInDir( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
Lcb,
|
|
&NewLinkName,
|
|
NewLinkNameFlags,
|
|
RenameFlags,
|
|
&PrevLinkName,
|
|
PrevLinkNameFlags );
|
|
}
|
|
|
|
//
|
|
// At this point a commit was done in the NtfsRenameMoveLink above
|
|
// which dropped any resources associated with the USN Journal and we
|
|
// should not do any more transactional work
|
|
//
|
|
|
|
ASSERT( IrpContext->TransactionId == 0 );
|
|
|
|
//
|
|
// Nothing should fail from this point forward.
|
|
//
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_NO_FAILURES_EXPECTED );
|
|
|
|
//
|
|
// Update the normalized name - the buffer should already be big enough
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info ) && FlagOn( RenameFlags, ADD_TARGET_LINK )) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
TargetParentScb,
|
|
Scb,
|
|
FileNameAttr,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Now look at the link we superseded. If we deleted the file then go through and
|
|
// mark everything as deleted.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TARGET_LINK | TRAVERSE_MATCH ) == REMOVE_TARGET_LINK) {
|
|
|
|
NtfsUpdateFcbFromLinkRemoval( IrpContext,
|
|
TargetParentScb,
|
|
TargetLinkFcb,
|
|
PrevLinkName,
|
|
PrevLinkNameFlags );
|
|
}
|
|
|
|
//
|
|
// Change the time stamps in the parent if we modified the links in this directory.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
|
|
|
|
NtfsUpdateFcb( ParentScb->Fcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
}
|
|
|
|
//
|
|
// We always set the last change time on the file we renamed unless
|
|
// the caller explicitly set this.
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
|
|
|
|
//
|
|
// Don't set the archive bit on a directory. Otherwise we break existing
|
|
// apps that don't expect to see this flag.
|
|
//
|
|
|
|
if (!IsDirectory( &Fcb->Info )) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_SET_ARCHIVE );
|
|
}
|
|
|
|
//
|
|
// Report the changes to the affected directories. We defer reporting
|
|
// until now so that all of the on disk changes have been made.
|
|
// We have already preserved the original file name for any changes
|
|
// associated with it.
|
|
//
|
|
// Note that we may have to make a call to notify that we are removing
|
|
// a target if there is only a case change. This could make for
|
|
// a third notify call.
|
|
//
|
|
// Now that we have the new name we need to decide whether to report
|
|
// this as a change in the file or adding a file to a new directory.
|
|
//
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
ULONG FilterMatch = 0;
|
|
ULONG Action;
|
|
|
|
//
|
|
// If we are deleting a target link in order to make a case change then
|
|
// report that.
|
|
//
|
|
|
|
if ((PrevFullLinkName.Buffer != NULL) &&
|
|
FlagOn( RenameFlags,
|
|
OVERWRITE_SOURCE_LINK | REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == REMOVE_TARGET_LINK) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&PrevFullLinkName,
|
|
PrevFullLinkName.Length - PrevLinkName.Length,
|
|
NULL,
|
|
((TargetParentScb->ScbType.Index.NormalizedName.Length != 0) ?
|
|
&TargetParentScb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
(IsDirectory( &TargetLinkFcb->Info ) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME :
|
|
FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_REMOVED,
|
|
TargetParentScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// If we stored the original name then we report the changes
|
|
// associated with it.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&SourceFullLinkName,
|
|
SourceLinkLastNameOffset,
|
|
NULL,
|
|
((ParentScb->ScbType.Index.NormalizedName.Length != 0) ?
|
|
&ParentScb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
(IsDirectory( &Fcb->Info ) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME :
|
|
FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
((FlagOn( RenameFlags, MOVE_TO_NEW_DIR ) ||
|
|
!FlagOn( RenameFlags, ADD_TARGET_LINK ) ||
|
|
(FlagOn( RenameFlags, REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == (REMOVE_TARGET_LINK | EXACT_CASE_MATCH))) ?
|
|
FILE_ACTION_REMOVED :
|
|
FILE_ACTION_RENAMED_OLD_NAME),
|
|
ParentScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Check if a new name will appear in the directory.
|
|
//
|
|
|
|
if (!FoundLink ||
|
|
(FlagOn( RenameFlags, OVERWRITE_SOURCE_LINK | EXACT_CASE_MATCH) == OVERWRITE_SOURCE_LINK) ||
|
|
(FlagOn( RenameFlags, REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == REMOVE_TARGET_LINK)) {
|
|
|
|
FilterMatch = IsDirectory( &Fcb->Info)
|
|
? FILE_NOTIFY_CHANGE_DIR_NAME
|
|
: FILE_NOTIFY_CHANGE_FILE_NAME;
|
|
|
|
//
|
|
// If we moved to a new directory, remember the
|
|
// action was a create operation.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, MOVE_TO_NEW_DIR )) {
|
|
|
|
Action = FILE_ACTION_ADDED;
|
|
|
|
} else {
|
|
|
|
Action = FILE_ACTION_RENAMED_NEW_NAME;
|
|
}
|
|
|
|
//
|
|
// There was an entry with the same case. If this isn't the
|
|
// same file then we report a change to all the file attributes.
|
|
//
|
|
|
|
} else if (FlagOn( RenameFlags, REMOVE_TARGET_LINK | TRAVERSE_MATCH ) == REMOVE_TARGET_LINK) {
|
|
|
|
FilterMatch = (FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE |
|
|
FILE_NOTIFY_CHANGE_LAST_WRITE |
|
|
FILE_NOTIFY_CHANGE_LAST_ACCESS |
|
|
FILE_NOTIFY_CHANGE_CREATION |
|
|
FILE_NOTIFY_CHANGE_SECURITY |
|
|
FILE_NOTIFY_CHANGE_EA);
|
|
|
|
//
|
|
// The file name isn't changing, only the properties of the
|
|
// file.
|
|
//
|
|
|
|
Action = FILE_ACTION_MODIFIED;
|
|
}
|
|
|
|
if (FilterMatch != 0) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&NewFullLinkName,
|
|
NewFullLinkName.Length - NewLinkName.Length,
|
|
NULL,
|
|
((TargetParentScb->ScbType.Index.NormalizedName.Length != 0) ?
|
|
&TargetParentScb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
FilterMatch,
|
|
Action,
|
|
TargetParentScb->Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now adjust the link counts on the different files.
|
|
//
|
|
|
|
if (TargetLinkFcb != NULL) {
|
|
TargetLinkFcb->LinkCount -= TargetLinkFcbCountAdj;
|
|
TargetLinkFcb->TotalLinks -= TargetLinkFcbCountAdj;
|
|
}
|
|
|
|
Fcb->TotalLinks -= FcbLinkCountAdj;
|
|
Fcb->LinkCount -= FcbLinkCountAdj;
|
|
|
|
//
|
|
// If we have a recursive failure at this point we will unwind the transaction
|
|
// in the fsd and leave the in memory stuff changed
|
|
//
|
|
|
|
ASSERT( !NT_SUCCESS( Status ) || NT_SUCCESS( IrpContext->ExceptionStatus ));
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsSetRenameInfo );
|
|
|
|
if (LockedFcb != NULL) {
|
|
NtfsUnlockFcb( IrpContext, LockedFcb );
|
|
}
|
|
|
|
//
|
|
// See if we have a SavedFcbUsnRecord.
|
|
//
|
|
|
|
if (SavedFcbUsnRecord != NULL) {
|
|
|
|
//
|
|
// Conceivably we failed to reallcoate the record when we tried to post
|
|
// the rename. If so, we will simply restore it here. (Note the rename
|
|
// back to the old name will occur anyway.)
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord == NULL) {
|
|
Fcb->FcbUsnRecord = SavedFcbUsnRecord;
|
|
|
|
//
|
|
// Else just free the pool.
|
|
//
|
|
|
|
} else {
|
|
NtfsFreePool( SavedFcbUsnRecord );
|
|
}
|
|
}
|
|
|
|
//
|
|
// release objectid and reparse explicitly so we can call Teardown structures and wait to go up chain
|
|
//
|
|
|
|
if (AcquiredObjectIdIndex) { NtfsReleaseScb( IrpContext, Vcb->ObjectIdTableScb ); }
|
|
if (AcquiredReparsePointIndex) { NtfsReleaseScb( IrpContext, Vcb->ReparsePointTableScb ); }
|
|
|
|
//
|
|
// Release the PagingToResource first while holding onto the FcbTable otherwise
|
|
// the Fcb can go away and freeing the paging resource first
|
|
//
|
|
|
|
if (FcbWithPagingToRelease != NULL) { NtfsReleasePagingResource( IrpContext, FcbWithPagingToRelease ); }
|
|
if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); }
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
//
|
|
// If we allocated any buffers for the notify operations deallocate them now.
|
|
//
|
|
|
|
if (NewFullLinkNameBuffer != NULL) { NtfsFreePool( NewFullLinkNameBuffer ); }
|
|
if (PrevFullLinkName.Buffer != NULL) { NtfsFreePool( PrevFullLinkName.Buffer ); }
|
|
if (SourceFullLinkName.Buffer != NULL) {
|
|
|
|
NtfsFreePool( SourceFullLinkName.Buffer );
|
|
}
|
|
|
|
//
|
|
// If we allocated a file name attribute, we deallocate it now.
|
|
//
|
|
|
|
if (FileNameAttr != NULL) { NtfsFreePool( FileNameAttr ); }
|
|
|
|
//
|
|
// If we allocated a buffer for the tunneled names, deallocate them now.
|
|
//
|
|
|
|
if (NamePair.Long.Buffer != NamePair.LongBuffer) {
|
|
|
|
NtfsFreePool( NamePair.Long.Buffer );
|
|
}
|
|
|
|
//
|
|
// Some cleanup only occurs if this request has not been posted to
|
|
// the oplock package
|
|
|
|
if (Status != STATUS_PENDING) {
|
|
|
|
if (AcquiredTargetLinkFcb) {
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
TargetLinkFcb,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetLinkInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN OUT PBOOLEAN VcbAcquired
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set link function. It will create a new link for a
|
|
file.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Vcb - Supplies the Vcb for the Volume
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this file object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
PLCB Lcb = Ccb->Lcb;
|
|
PFCB Fcb = Scb->Fcb;
|
|
PSCB ParentScb = NULL;
|
|
SHORT LinkCountAdj = 0;
|
|
|
|
BOOLEAN AcquiredParentScb = TRUE;
|
|
BOOLEAN AcquiredObjectIdIndex = FALSE;
|
|
BOOLEAN AcquiredPreviousFcb = FALSE;
|
|
PFCB LockedFcb = NULL;
|
|
|
|
UNICODE_STRING NewLinkName;
|
|
UNICODE_STRING NewFullLinkName;
|
|
PWCHAR NewFullLinkNameBuffer = NULL;
|
|
PFILE_NAME NewLinkNameAttr = NULL;
|
|
USHORT NewLinkNameAttrLength = 0;
|
|
UCHAR NewLinkNameFlags;
|
|
|
|
PSCB TargetParentScb;
|
|
PFILE_OBJECT TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
|
|
|
|
BOOLEAN FoundPrevLink;
|
|
UNICODE_STRING PrevLinkName;
|
|
UNICODE_STRING PrevFullLinkName;
|
|
UCHAR PrevLinkNameFlags;
|
|
USHORT PrevFcbLinkCountAdj = 0;
|
|
BOOLEAN ExistingPrevFcb = FALSE;
|
|
PFCB PreviousFcb = NULL;
|
|
|
|
ULONG RenameFlags = 0;
|
|
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
PFCB FcbWithPagingResourceToRelease = NULL;
|
|
|
|
BOOLEAN ReportDirNotify = FALSE;
|
|
PWCHAR NextChar;
|
|
|
|
PINDEX_ENTRY IndexEntry;
|
|
PBCB IndexEntryBcb = NULL;
|
|
|
|
PISECURITY_DESCRIPTOR SecurityDescriptor;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetLinkInfo...\n") );
|
|
|
|
PrevFullLinkName.Buffer = NULL;
|
|
|
|
//
|
|
// If we are not opening the entire file, we can't set link info.
|
|
//
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We also fail this if we are attempting to create a link on a directory.
|
|
// This will prevent cycles from being created.
|
|
//
|
|
|
|
if (FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", STATUS_FILE_IS_A_DIRECTORY) );
|
|
return STATUS_FILE_IS_A_DIRECTORY;
|
|
}
|
|
|
|
//
|
|
// We can't add a link without having a parent directory. Either we want to use the same
|
|
// parent or our caller supplied a parent.
|
|
//
|
|
|
|
if (Lcb == NULL) {
|
|
|
|
//
|
|
// If the current file has been opened by FileId and there are no
|
|
// remaining links not marked for delete then don't allow this
|
|
// operation. This is because we defer the delete of the last link
|
|
// until all of the OpenByID handles are closed. We don't have any
|
|
// easy way to remember that there is a link to delete after
|
|
// the open through the link is closed.
|
|
//
|
|
// The OPEN_BY_FILE_ID flag indicates that we used an open by Id somewhere
|
|
// in the open path. This operation is OK if this user opened through
|
|
// a link, that's why we will only do this test if there is no Lcb.
|
|
//
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
|
|
(Fcb->LinkCount == 0)) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// If there is no target file object, then we can't add a link.
|
|
//
|
|
|
|
if (TargetFileObject == NULL) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: No target file object -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
} else {
|
|
|
|
ParentScb = Lcb->Scb;
|
|
|
|
//
|
|
// If this link has been deleted, then we don't allow this operation.
|
|
//
|
|
|
|
if (LcbLinkIsDeleted( Lcb )) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if we are allowed to perform this link operation. We can't if this
|
|
// is a system file or the user hasn't opened the entire file. We
|
|
// don't need to check for the root explicitly since it is one of
|
|
// the system files.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Verify that we can wait.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
|
|
|
|
Status = NtfsPostRequest( IrpContext, Irp );
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Can't wait\n") );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Check if we will want to report this via the dir notify package.
|
|
//
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
|
|
(Ccb->FullFileName.Buffer[0] == L'\\') &&
|
|
(Vcb->NotifyCount != 0)) {
|
|
|
|
ReportDirNotify = TRUE;
|
|
}
|
|
|
|
//
|
|
// Remember the source info flags in the Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Post the change to the Usn Journal (on errors change is backed out)
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_HARD_LINK_CHANGE );
|
|
|
|
//
|
|
// We now assemble the names and in memory-structures for both the
|
|
// source and target links and check if the target link currently
|
|
// exists.
|
|
//
|
|
|
|
NtfsFindTargetElements( IrpContext,
|
|
TargetFileObject,
|
|
ParentScb,
|
|
&TargetParentScb,
|
|
&NewFullLinkName,
|
|
&NewLinkName );
|
|
|
|
//
|
|
// Check that the new name is not invalid.
|
|
//
|
|
|
|
if ((NewLinkName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) ||
|
|
!NtfsIsFileNameValid( &NewLinkName, FALSE )) {
|
|
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
leave;
|
|
}
|
|
|
|
if (TargetParentScb == ParentScb) {
|
|
|
|
//
|
|
// Acquire the target parent in order to synchronize adding a link.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
|
|
//
|
|
// Check if we are creating a link to the same directory with the
|
|
// exact same name.
|
|
//
|
|
|
|
if (NtfsAreNamesEqual( Vcb->UpcaseTable,
|
|
&NewLinkName,
|
|
&Lcb->ExactCaseLink.LinkName,
|
|
FALSE )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Creating link to same name and directory\n") );
|
|
Status = STATUS_SUCCESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Make sure the normalized name is in this Scb.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
&ParentScb->ScbType.Index.NormalizedName );
|
|
}
|
|
|
|
//
|
|
// Otherwise we remember that we are creating this link in a new directory.
|
|
//
|
|
|
|
} else {
|
|
|
|
SetFlag( RenameFlags, CREATE_IN_NEW_DIR );
|
|
|
|
//
|
|
// We know that we need to acquire the target directory so we can
|
|
// add and remove links. We want to carefully acquire the Scb in the
|
|
// event we only have the Vcb shared.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext,
|
|
TargetParentScb->Fcb,
|
|
TargetParentScb,
|
|
ACQUIRE_DONT_WAIT )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Now snapshot the Scb.
|
|
//
|
|
|
|
if (FlagOn( TargetParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, TargetParentScb );
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, TargetParentScb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are exceeding the maximum link count on this file then return
|
|
// an error. There isn't a descriptive error code to use at this time.
|
|
//
|
|
|
|
if (Fcb->TotalLinks >= NTFS_MAX_LINK_COUNT) {
|
|
|
|
Status = STATUS_TOO_MANY_LINKS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Lookup the entry for this filename in the target directory.
|
|
// We look in the Ccb for the type of case match for the target
|
|
// name.
|
|
//
|
|
|
|
FoundPrevLink = NtfsLookupEntry( IrpContext,
|
|
TargetParentScb,
|
|
BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
|
|
&NewLinkName,
|
|
&NewLinkNameAttr,
|
|
&NewLinkNameAttrLength,
|
|
NULL,
|
|
&IndexEntry,
|
|
&IndexEntryBcb,
|
|
NULL );
|
|
|
|
//
|
|
// If we found a matching link, we need to check how we want to operate
|
|
// on the source link and the target link. This means whether we
|
|
// have any work to do, whether we need to remove the target link
|
|
// and whether we need to remove the source link.
|
|
//
|
|
|
|
if (FoundPrevLink) {
|
|
|
|
PFILE_NAME IndexFileName;
|
|
|
|
IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
|
|
|
|
//
|
|
// If the file references match, we are trying to create a
|
|
// link where one already exists.
|
|
//
|
|
|
|
if (NtfsCheckLinkForNewLink( Fcb,
|
|
IndexFileName,
|
|
IndexEntry->FileReference,
|
|
&NewLinkName,
|
|
&RenameFlags )) {
|
|
|
|
//
|
|
// There is no work to do.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// We need to check that the user wanted to remove that link.
|
|
//
|
|
|
|
if (!IrpSp->Parameters.SetFile.ReplaceIfExists) {
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// We want to preserve the case and the flags of the matching
|
|
// target link. We also want to preserve the case of the
|
|
// name being created. The following variables currently contain
|
|
// the exact case for the target to remove and the new name to
|
|
// apply.
|
|
//
|
|
// Link to remove - In 'IndexEntry'.
|
|
// The links flags are also in 'IndexEntry'. We copy
|
|
// these flags to 'PrevLinkNameFlags'
|
|
//
|
|
// New Name - Exact case is stored in 'NewLinkName'
|
|
// - Exact case is also stored in 'NewLinkNameAttr'
|
|
//
|
|
// We modify this so that we can use the FileName attribute
|
|
// structure to create the new link. We copy the linkname being
|
|
// removed into 'PrevLinkName'. The following is the
|
|
// state after the switch.
|
|
//
|
|
// 'NewLinkNameAttr' - contains the name for the link being
|
|
// created.
|
|
//
|
|
// 'PrevLinkName' - Contains the link name for the link being
|
|
// removed.
|
|
//
|
|
// 'PrevLinkNameFlags' - Contains the name flags for the link
|
|
// being removed.
|
|
//
|
|
|
|
//
|
|
// Remember the file name flags for the match being made.
|
|
//
|
|
|
|
PrevLinkNameFlags = IndexFileName->Flags;
|
|
|
|
//
|
|
// If we are report this via dir notify then build the full name.
|
|
// Otherwise just remember the last name.
|
|
//
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
PrevFullLinkName.MaximumLength =
|
|
PrevFullLinkName.Length = (ParentScb->ScbType.Index.NormalizedName.Length +
|
|
sizeof( WCHAR ) +
|
|
NewLinkName.Length);
|
|
|
|
PrevFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
|
|
PrevFullLinkName.MaximumLength );
|
|
|
|
RtlCopyMemory( PrevFullLinkName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
NextChar = Add2Ptr( PrevFullLinkName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length != sizeof( WCHAR )) {
|
|
|
|
|
|
*NextChar = L'\\';
|
|
NextChar += 1;
|
|
|
|
} else {
|
|
|
|
PrevFullLinkName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
PrevLinkName.Buffer = NextChar;
|
|
|
|
} else {
|
|
|
|
PrevFullLinkName.Buffer =
|
|
PrevLinkName.Buffer = NtfsAllocatePool( PagedPool, NewLinkName.Length );
|
|
|
|
PrevFullLinkName.Length = PrevLinkName.MaximumLength = NewLinkName.Length;
|
|
}
|
|
|
|
//
|
|
// Copy the name found in the Index Entry to 'PrevLinkName'
|
|
//
|
|
|
|
PrevLinkName.Length =
|
|
PrevLinkName.MaximumLength = NewLinkName.Length;
|
|
|
|
RtlCopyMemory( PrevLinkName.Buffer,
|
|
IndexFileName->FileName,
|
|
NewLinkName.Length );
|
|
|
|
//
|
|
// We only need this check if the existing link is for a different file.
|
|
//
|
|
|
|
if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
//
|
|
// We check if there is an existing Fcb for the target link.
|
|
// If there is, the unclean count better be 0.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
PreviousFcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
IndexEntry->FileReference,
|
|
FALSE,
|
|
BooleanFlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ),
|
|
&ExistingPrevFcb );
|
|
|
|
//
|
|
// Before we go on, make sure we aren't about to rename over a system file.
|
|
//
|
|
|
|
if (FlagOn( PreviousFcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Add a paging resource to the target - this is not supplied if its created
|
|
// from scratch. We need this (acquired in the proper order) for the delete
|
|
// to work correctly if there are any data streams. It's not going to harm a
|
|
// directory del and because of the teardown in the finally clause its difficult
|
|
// to retry again without looping
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, PreviousFcb );
|
|
LockedFcb = PreviousFcb;
|
|
if (PreviousFcb->PagingIoResource == NULL) {
|
|
PreviousFcb->PagingIoResource = NtfsAllocateEresource();
|
|
}
|
|
NtfsUnlockFcb( IrpContext, LockedFcb );
|
|
LockedFcb = NULL;
|
|
|
|
//
|
|
// We need to acquire this file carefully in the event that we don't hold
|
|
// the Vcb exclusively.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX )) {
|
|
|
|
if (!NtfsAcquirePagingResourceExclusive( IrpContext, PreviousFcb, FALSE )) {
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
FcbWithPagingResourceToRelease = PreviousFcb;
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, ACQUIRE_DONT_WAIT )) {
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ACQUIRE_EX );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
} else {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Acquire the paging Io resource for this file before the main
|
|
// resource in case we need to delete.
|
|
//
|
|
|
|
FcbWithPagingResourceToRelease = PreviousFcb;
|
|
|
|
NtfsAcquirePagingResourceExclusive( IrpContext, PreviousFcb, TRUE );
|
|
NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, 0 );
|
|
}
|
|
|
|
AcquiredPreviousFcb = TRUE;
|
|
|
|
//
|
|
// If the Fcb Info field needs to be initialized, we do so now.
|
|
// We read this information from the disk as the duplicate information
|
|
// in the index entry is not guaranteed to be correct.
|
|
//
|
|
|
|
if (!FlagOn( PreviousFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
|
|
|
|
NtfsUpdateFcbInfoFromDisk( IrpContext,
|
|
TRUE,
|
|
PreviousFcb,
|
|
NULL );
|
|
//
|
|
// If we need to acquire the object id index later in order
|
|
// to set or lookup information for the tunnel cache, we
|
|
// risk a deadlock if the quota index is still held. Given
|
|
// that superceding renames where the target Fcb isn't
|
|
// already open are a fairly rare case, we can tolerate the
|
|
// potential inefficiency of preacquiring the object id
|
|
// index now.
|
|
//
|
|
|
|
if (Vcb->ObjectIdTableScb != NULL) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ObjectIdTableScb );
|
|
AcquiredObjectIdIndex = TRUE;
|
|
}
|
|
|
|
NtfsConditionallyFixupQuota( IrpContext, PreviousFcb );
|
|
}
|
|
|
|
//
|
|
// We are adding a link to the source file which already
|
|
// exists as a link to a different file in the target directory.
|
|
//
|
|
// We need to check whether we permitted to delete this
|
|
// link. If not then it is possible that the problem is
|
|
// an existing batch oplock on the file. In that case
|
|
// we want to delete the batch oplock and try this again.
|
|
//
|
|
|
|
Status = NtfsCheckFileForDelete( IrpContext,
|
|
TargetParentScb,
|
|
PreviousFcb,
|
|
ExistingPrevFcb,
|
|
IndexEntry );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
PSCB NextScb = NULL;
|
|
|
|
//
|
|
// We are going to either fail this request or pass
|
|
// this on to the oplock package. Test if there is
|
|
// a batch oplock on any streams on this file.
|
|
//
|
|
|
|
while ((NextScb = NtfsGetNextChildScb( PreviousFcb,
|
|
NextScb )) != NULL) {
|
|
|
|
if ((NextScb->AttributeTypeCode == $DATA) &&
|
|
(NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
|
|
FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
|
|
|
|
//
|
|
// Go ahead and perform any necessary cleanup now.
|
|
// Once we call the oplock package below we lose
|
|
// control of the IrpContext.
|
|
//
|
|
|
|
if (*VcbAcquired) {
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
*VcbAcquired = FALSE;
|
|
}
|
|
|
|
Status = FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
|
|
Irp,
|
|
IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsPrePostIrp );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// We are adding a link to the source file which already
|
|
// exists as a link to a different file in the target directory.
|
|
//
|
|
|
|
NtfsCleanupLinkForRemoval( PreviousFcb,
|
|
TargetParentScb,
|
|
ExistingPrevFcb );
|
|
|
|
//
|
|
// If the link count on this file is 1, then delete the file. Otherwise just
|
|
// delete the link.
|
|
//
|
|
|
|
if (PreviousFcb->LinkCount == 1) {
|
|
|
|
PVOID TempFcb;
|
|
|
|
TempFcb = (PFCB)IrpContext->CleanupStructure;
|
|
IrpContext->CleanupStructure = FcbWithPagingResourceToRelease;
|
|
FcbWithPagingResourceToRelease = TempFcb;
|
|
|
|
ASSERT( (NULL == TempFcb) || (NTFS_NTC_FCB == SafeNodeType( TempFcb )) );
|
|
|
|
NtfsDeleteFile( IrpContext,
|
|
PreviousFcb,
|
|
TargetParentScb,
|
|
&AcquiredParentScb,
|
|
NULL,
|
|
NULL );
|
|
|
|
FcbWithPagingResourceToRelease = IrpContext->CleanupStructure;
|
|
IrpContext->CleanupStructure = TempFcb;
|
|
|
|
//
|
|
// Make sure to force the close record out to disk.
|
|
//
|
|
|
|
PrevFcbLinkCountAdj += 1;
|
|
|
|
} else {
|
|
|
|
NtfsPostUsnChange( IrpContext, PreviousFcb, USN_REASON_HARD_LINK_CHANGE | USN_REASON_CLOSE );
|
|
NtfsRemoveLink( IrpContext,
|
|
PreviousFcb,
|
|
TargetParentScb,
|
|
PrevLinkName,
|
|
NULL,
|
|
NULL );
|
|
|
|
ClearFlag( PreviousFcb->FcbState, FCB_STATE_VALID_USN_NAME );
|
|
|
|
PrevFcbLinkCountAdj += 1;
|
|
NtfsUpdateFcb( PreviousFcb, FCB_INFO_CHANGED_LAST_CHANGE );
|
|
}
|
|
|
|
//
|
|
// Otherwise we need to remove this link as our caller wants to replace it
|
|
// with a different case.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsRemoveLink( IrpContext,
|
|
Fcb,
|
|
TargetParentScb,
|
|
PrevLinkName,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Make sure we find the name again when posting another change.
|
|
//
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_VALID_USN_NAME );
|
|
|
|
PreviousFcb = Fcb;
|
|
LinkCountAdj += 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we have the full name of the target if we will be reporting
|
|
// this.
|
|
//
|
|
|
|
if (ReportDirNotify && (NewFullLinkName.Buffer == NULL)) {
|
|
|
|
NewFullLinkName.MaximumLength =
|
|
NewFullLinkName.Length = (ParentScb->ScbType.Index.NormalizedName.Length +
|
|
sizeof( WCHAR ) +
|
|
NewLinkName.Length);
|
|
|
|
NewFullLinkNameBuffer =
|
|
NewFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
|
|
NewFullLinkName.MaximumLength );
|
|
|
|
RtlCopyMemory( NewFullLinkName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
NextChar = Add2Ptr( NewFullLinkName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length != sizeof( WCHAR )) {
|
|
|
|
*NextChar = L'\\';
|
|
NextChar += 1;
|
|
|
|
} else {
|
|
|
|
NewFullLinkName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
RtlCopyMemory( NextChar,
|
|
NewLinkName.Buffer,
|
|
NewLinkName.Length );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
//
|
|
// Check that we have permission to add a file to this directory.
|
|
//
|
|
|
|
NtfsCheckIndexForAddOrDelete( IrpContext,
|
|
TargetParentScb->Fcb,
|
|
FILE_ADD_FILE,
|
|
Ccb->AccessFlags >> 2 );
|
|
|
|
//
|
|
// We always set the last change time on the file we renamed unless
|
|
// the caller explicitly set this.
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
|
|
|
|
//
|
|
// We now want to add the new link into the target directory.
|
|
// We never create a primary link through the link operation although
|
|
// we can remove one.
|
|
//
|
|
|
|
NtfsAddLink( IrpContext,
|
|
FALSE,
|
|
TargetParentScb,
|
|
Fcb,
|
|
NewLinkNameAttr,
|
|
NULL,
|
|
&NewLinkNameFlags,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
|
|
LinkCountAdj -= 1;
|
|
NtfsUpdateFcb( TargetParentScb->Fcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
|
|
//
|
|
// Now we want to update the Fcb for the link we renamed. If we moved it
|
|
// to a new directory we need to move all the Lcb's associated with
|
|
// the previous link.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
NtfsReplaceLinkInDir( IrpContext,
|
|
TargetParentScb,
|
|
Fcb,
|
|
&NewLinkName,
|
|
NewLinkNameFlags,
|
|
&PrevLinkName,
|
|
PrevLinkNameFlags );
|
|
}
|
|
|
|
//
|
|
// We have now modified the on-disk structures. We now need to
|
|
// modify the in-memory structures. This includes the Fcb and Lcb's
|
|
// for any links we superseded, and the source Fcb and it's Lcb's.
|
|
//
|
|
// We start by looking at the link we superseded. We know the
|
|
// the target directory, link name and flags, and the file the
|
|
// link was connected to.
|
|
//
|
|
|
|
if (FoundPrevLink && !FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
NtfsUpdateFcbFromLinkRemoval( IrpContext,
|
|
TargetParentScb,
|
|
PreviousFcb,
|
|
PrevLinkName,
|
|
PrevLinkNameFlags );
|
|
}
|
|
|
|
//
|
|
// We have three cases to report for changes in the target directory..
|
|
//
|
|
// 1. If we overwrote an existing link to a different file, we
|
|
// report this as a modified file.
|
|
//
|
|
// 2. If we moved a link to a new directory, then we added a file.
|
|
//
|
|
// 3. If we renamed a link in in the same directory, then we report
|
|
// that there is a new name.
|
|
//
|
|
// We currently combine cases 2 and 3.
|
|
//
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
ULONG FilterMatch = 0;
|
|
ULONG FileAction;
|
|
|
|
//
|
|
// If we removed an entry and it wasn't an exact case match, then
|
|
// report the entry which was removed.
|
|
//
|
|
|
|
if (!FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
|
|
|
|
if (FoundPrevLink) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&PrevFullLinkName,
|
|
PrevFullLinkName.Length - PrevLinkName.Length,
|
|
NULL,
|
|
&TargetParentScb->ScbType.Index.NormalizedName,
|
|
(IsDirectory( &PreviousFcb->Info ) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME :
|
|
FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_REMOVED,
|
|
TargetParentScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// We will be adding an entry.
|
|
//
|
|
|
|
FilterMatch = FILE_NOTIFY_CHANGE_FILE_NAME;
|
|
FileAction = FILE_ACTION_ADDED;
|
|
|
|
//
|
|
// If this was not a traverse match then report that all the file
|
|
// properties changed.
|
|
//
|
|
|
|
} else if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
FilterMatch |= (FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE |
|
|
FILE_NOTIFY_CHANGE_LAST_WRITE |
|
|
FILE_NOTIFY_CHANGE_LAST_ACCESS |
|
|
FILE_NOTIFY_CHANGE_CREATION |
|
|
FILE_NOTIFY_CHANGE_SECURITY |
|
|
FILE_NOTIFY_CHANGE_EA);
|
|
|
|
FileAction = FILE_ACTION_MODIFIED;
|
|
}
|
|
|
|
if (FilterMatch != 0) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&NewFullLinkName,
|
|
NewFullLinkName.Length - NewLinkName.Length,
|
|
NULL,
|
|
&TargetParentScb->ScbType.Index.NormalizedName,
|
|
FilterMatch,
|
|
FileAction,
|
|
TargetParentScb->Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Checkpoint the transaction before we make the changes below. If there are Usn
|
|
// records then writing them could raise.
|
|
//
|
|
|
|
if (IrpContext->Usn.CurrentUsnFcb != NULL) {
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Adjust the link counts on the files.
|
|
//
|
|
|
|
Fcb->TotalLinks = (SHORT) Fcb->TotalLinks - LinkCountAdj;
|
|
Fcb->LinkCount = (SHORT) Fcb->LinkCount - LinkCountAdj;
|
|
|
|
//
|
|
// We can now adjust the total link count on the previous Fcb.
|
|
//
|
|
|
|
if (PreviousFcb != NULL) {
|
|
|
|
PreviousFcb->TotalLinks -= PrevFcbLinkCountAdj;
|
|
PreviousFcb->LinkCount -= PrevFcbLinkCountAdj;
|
|
|
|
if (PreviousFcb->LinkCount == 0) {
|
|
|
|
//
|
|
// Release the quota control block. This does not have to be done
|
|
// here however, it allows us to free up the quota control block
|
|
// before the fcb is removed from the table. This keeps the assert
|
|
// about quota table empty from triggering in
|
|
// NtfsClearAndVerifyQuotaIndex.
|
|
//
|
|
|
|
if (NtfsPerformQuotaOperation( PreviousFcb )) {
|
|
NtfsDereferenceQuotaControlBlock( Vcb,
|
|
&PreviousFcb->QuotaControl );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do an audit record for the link creation if necc.
|
|
// Check if we need to load the security descriptor for the file
|
|
//
|
|
|
|
if (Fcb->SharedSecurity == NULL) {
|
|
NtfsLoadSecurityDescriptor( IrpContext, Fcb );
|
|
}
|
|
|
|
SecurityDescriptor = (PISECURITY_DESCRIPTOR) Fcb->SharedSecurity->SecurityDescriptor;
|
|
|
|
if (SeAuditingHardLinkEventsWithContext( TRUE, SecurityDescriptor, NULL )) {
|
|
|
|
UNICODE_STRING GeneratedName;
|
|
PUNICODE_STRING OldFullLinkName;
|
|
UNICODE_STRING DeviceAndOldLinkName;
|
|
UNICODE_STRING DeviceAndNewLinkName;
|
|
USHORT Length;
|
|
|
|
GeneratedName.Buffer = NULL;
|
|
DeviceAndOldLinkName.Buffer = NULL;
|
|
DeviceAndNewLinkName.Buffer = NULL;
|
|
|
|
try {
|
|
|
|
//
|
|
// Generate current filename
|
|
//
|
|
|
|
if (Ccb->FullFileName.Length != 0 ) {
|
|
OldFullLinkName = &Ccb->FullFileName;
|
|
|
|
} else {
|
|
|
|
NtfsBuildNormalizedName( IrpContext, Scb->Fcb, FALSE, &GeneratedName );
|
|
OldFullLinkName = &GeneratedName;
|
|
}
|
|
|
|
//
|
|
// Create the full device and file name strings
|
|
//
|
|
|
|
Length = Vcb->DeviceName.Length + OldFullLinkName->Length;
|
|
DeviceAndOldLinkName.Buffer = NtfsAllocatePool( PagedPool, Length );
|
|
DeviceAndOldLinkName.Length = DeviceAndOldLinkName.MaximumLength = Length;
|
|
RtlCopyMemory( DeviceAndOldLinkName.Buffer, Vcb->DeviceName.Buffer, Vcb->DeviceName.Length );
|
|
RtlCopyMemory( Add2Ptr( DeviceAndOldLinkName.Buffer, Vcb->DeviceName.Length ), OldFullLinkName->Buffer, OldFullLinkName->Length );
|
|
|
|
Length = Vcb->DeviceName.Length + TargetParentScb->ScbType.Index.NormalizedName.Length + sizeof( WCHAR ) + NewLinkName.Length;
|
|
DeviceAndNewLinkName.Buffer = NtfsAllocatePool( PagedPool, Length );
|
|
DeviceAndNewLinkName.Length = DeviceAndNewLinkName.MaximumLength = Length;
|
|
RtlCopyMemory( DeviceAndNewLinkName.Buffer, Vcb->DeviceName.Buffer, Vcb->DeviceName.Length );
|
|
RtlCopyMemory( Add2Ptr( DeviceAndNewLinkName.Buffer, Vcb->DeviceName.Length ),
|
|
TargetParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
TargetParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
NextChar = Add2Ptr( DeviceAndNewLinkName.Buffer,
|
|
Vcb->DeviceName.Length + TargetParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
if (TargetParentScb->ScbType.Index.NormalizedName.Length != sizeof( WCHAR )) {
|
|
*NextChar = L'\\';
|
|
NextChar += 1;
|
|
} else {
|
|
DeviceAndNewLinkName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
RtlCopyMemory( NextChar, NewLinkName.Buffer, NewLinkName.Length );
|
|
|
|
SeAuditHardLinkCreation( &DeviceAndOldLinkName,
|
|
&DeviceAndNewLinkName,
|
|
TRUE );
|
|
} finally {
|
|
|
|
if (GeneratedName.Buffer != NULL) {
|
|
NtfsFreePool( GeneratedName.Buffer );
|
|
}
|
|
if (DeviceAndNewLinkName.Buffer != NULL) {
|
|
NtfsFreePool( DeviceAndNewLinkName.Buffer );
|
|
}
|
|
if (DeviceAndOldLinkName.Buffer != NULL) {
|
|
NtfsFreePool( DeviceAndOldLinkName.Buffer );
|
|
}
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsSetLinkInfo );
|
|
|
|
if (LockedFcb != NULL) {
|
|
NtfsUnlockFcb( IrpContext, LockedFcb );
|
|
}
|
|
|
|
//
|
|
// release objectid and reparse explicitly so we can call Teardown structures and wait to go up chain
|
|
//
|
|
|
|
if (AcquiredObjectIdIndex) { NtfsReleaseScb( IrpContext, Vcb->ObjectIdTableScb ); }
|
|
if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); }
|
|
|
|
//
|
|
// If we allocated any buffers for name storage then deallocate them now.
|
|
//
|
|
|
|
if (PrevFullLinkName.Buffer != NULL) { NtfsFreePool( PrevFullLinkName.Buffer ); }
|
|
if (NewFullLinkNameBuffer != NULL) { NtfsFreePool( NewFullLinkNameBuffer ); }
|
|
|
|
//
|
|
// Release any paging io resource acquired.
|
|
//
|
|
|
|
if (FcbWithPagingResourceToRelease != NULL) { ExReleaseResourceLite( FcbWithPagingResourceToRelease->PagingIoResource ); }
|
|
|
|
//
|
|
// If we allocated a file name attribute, we deallocate it now.
|
|
//
|
|
|
|
if (NewLinkNameAttr != NULL) { NtfsFreePool( NewLinkNameAttr ); }
|
|
|
|
//
|
|
// If we have acquired the Fcb for a removed link and it didn't previously
|
|
// exist, call our teardown routine.
|
|
//
|
|
|
|
if ((Status != STATUS_PENDING) && AcquiredPreviousFcb) {
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
PreviousFcb,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetShortNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set shortname function. We first check that the short name
|
|
passed to us is valid for the context established by the system, i.e. check length and
|
|
whether extended characters are allowed. We will use the same test Ntfs uses in the
|
|
create path to determine whether to generate a short name. If the name is valid then
|
|
check whether it is legal to put this short name on the link used to open the file.
|
|
It is legal if the existing link is either a long, long/short or a short name. It is
|
|
also legal if this is any link AND there isn't a specialized link (long, long/short, short)
|
|
on this file. The final check is that this new link can't be a case insensitive match with
|
|
any other link in the directory except for the existing short name on the file.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Vcb - Vcb for the volume
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this file object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb = Ccb->Lcb;
|
|
PFCB Fcb = Scb->Fcb;
|
|
PSCB ParentScb;
|
|
PLCB ShortNameLcb = NULL;
|
|
PLCB LongNameLcb = NULL;
|
|
|
|
UNICODE_STRING FullShortName;
|
|
UNICODE_STRING ShortName;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
UNICODE_STRING OldShortName;
|
|
UNICODE_STRING FullOldShortName;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
BOOLEAN ExistingShortName = FALSE;
|
|
|
|
PFILE_NAME FoundFileName;
|
|
|
|
BOOLEAN FoundLink;
|
|
PFILE_NAME ShortNameAttr = NULL;
|
|
USHORT ShortNameAttrLength = 0;
|
|
|
|
LOGICAL ReportDirNotify = FALSE;
|
|
|
|
PINDEX_ENTRY IndexEntry;
|
|
PBCB IndexEntryBcb = NULL;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE ();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetShortNameInfo...\n") );
|
|
|
|
OldShortName.Buffer = NULL;
|
|
FullOldShortName.Buffer = NULL;
|
|
FullShortName.Buffer = NULL;
|
|
|
|
//
|
|
// Do a quick check that the caller is allowed to do the rename.
|
|
// The opener must have opened the main data stream by name and this can't be
|
|
// a system file.
|
|
//
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE ) ||
|
|
(Lcb == NULL) ||
|
|
FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// The caller also must have restore privilege + plus some kind of write access to set the short name
|
|
//
|
|
|
|
if (!FlagOn( Ccb->AccessFlags, RESTORE_ACCESS ) ||
|
|
!FlagOn( Ccb->AccessFlags, WRITE_DATA_ACCESS | WRITE_ATTRIBUTES_ACCESS )) {
|
|
|
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|
}
|
|
|
|
//
|
|
// This operation only applies to case-insensitive handles.
|
|
//
|
|
|
|
if (FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Case sensitive handle\n") );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Validate the new short name. It must be a valid Ntfs name and satisfy
|
|
// the current requirement for a short name. The short name must be a full number of
|
|
// unicode characters and a valid short name for the current system.
|
|
//
|
|
|
|
ShortName.MaximumLength =
|
|
ShortName.Length = (USHORT) ((PFILE_NAME_INFORMATION) IrpContext->OriginatingIrp->AssociatedIrp.SystemBuffer)->FileNameLength;
|
|
ShortName.Buffer = (PWSTR) &((PFILE_NAME_INFORMATION) IrpContext->OriginatingIrp->AssociatedIrp.SystemBuffer)->FileName;
|
|
|
|
if ((ShortName.Length == 0) ||
|
|
FlagOn( ShortName.Length, 1 ) ||
|
|
!NtfsIsFatNameValid( &ShortName, FALSE )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Invalid name\n") );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Make sure the name is upcased.
|
|
//
|
|
|
|
NtfsUpcaseName( Vcb->UpcaseTable, Vcb->UpcaseTableSize, &ShortName );
|
|
|
|
//
|
|
// If this link has been deleted, then we don't allow this operation.
|
|
//
|
|
|
|
if (LcbLinkIsDeleted( Lcb )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Exit -> %08lx\n", STATUS_ACCESS_DENIED) );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Verify that we can wait.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
|
|
|
|
Status = NtfsPostRequest( IrpContext, Irp );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Can't wait\n") );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If this is a directory file, we need to examine its descendents.
|
|
// We may not remove a link which may be an ancestor path
|
|
// component of any open file.
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info )) {
|
|
|
|
Status = NtfsCheckTreeForBatchOplocks( IrpContext, Irp, Scb );
|
|
|
|
//
|
|
// Get out if there are any blocking batch oplocks.
|
|
//
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find the Parent Scb.
|
|
//
|
|
|
|
ParentScb = Lcb->Scb;
|
|
|
|
//
|
|
// Acquire the parent and make sure it has a normalized name. Also make sure the current
|
|
// Fcb has a normalized name if it is a directory.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, ParentScb );
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
|
|
NtfsBuildNormalizedName( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
&ParentScb->ScbType.Index.NormalizedName );
|
|
}
|
|
|
|
if (IsDirectory( &Fcb->Info ) &&
|
|
(Scb->ScbType.Index.NormalizedName.Length == 0)) {
|
|
|
|
NtfsUpdateNormalizedName( IrpContext,
|
|
ParentScb,
|
|
Scb,
|
|
NULL,
|
|
FALSE,
|
|
FALSE );
|
|
}
|
|
|
|
if (Vcb->NotifyCount != 0) {
|
|
|
|
ReportDirNotify = TRUE;
|
|
}
|
|
|
|
//
|
|
// Check if the current Lcb is either part of or all of Ntfs/Dos name pair, if so then
|
|
// our life is much easier. Otherwise look through the filename attributes to verify
|
|
// there isn't already an Ntfs/Dos name.
|
|
//
|
|
|
|
if (!FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_NTFS | FILE_NAME_DOS )) {
|
|
|
|
//
|
|
// Initialize the attribute enumeration context.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
if (!NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttrContext )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
|
|
//
|
|
// Now keep looking until we find a match.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
FoundFileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
|
|
|
//
|
|
// If we find any with the Ntfs/Dos flags set then get out.
|
|
//
|
|
|
|
if (FlagOn( FoundFileName->Flags, FILE_NAME_NTFS | FILE_NAME_DOS )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_OBJECT_NAME_COLLISION, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Get the next filename attribute.
|
|
//
|
|
|
|
if (!NtfsLookupNextAttributeByCode( IrpContext,
|
|
Fcb,
|
|
$FILE_NAME,
|
|
&AttrContext )) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We know the link in our hand will become the long name Lcb.
|
|
//
|
|
|
|
LongNameLcb = Lcb;
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
CleanupAttrContext = FALSE;
|
|
|
|
//
|
|
// Find the appropriate long and short name Lcbs if they are present. We
|
|
// need to update them if present.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// The Lcb has at least one flag set. If both aren't set then there
|
|
// is a separate short name.
|
|
//
|
|
|
|
if (Lcb->FileNameAttr->Flags != (FILE_NAME_NTFS | FILE_NAME_DOS)) {
|
|
|
|
ExistingShortName = TRUE;
|
|
}
|
|
|
|
//
|
|
// If a long name flag is set then we have the long name Lcb.
|
|
//
|
|
|
|
if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_NTFS )) {
|
|
|
|
LongNameLcb = Lcb;
|
|
}
|
|
|
|
//
|
|
// Find out if there are any Lcb's for these links in memory.
|
|
// If not there then we don't need to update them.
|
|
//
|
|
|
|
ShortNameLcb = NtfsLookupLcbByFlags( Fcb, FILE_NAME_DOS );
|
|
|
|
if (LongNameLcb == NULL) {
|
|
|
|
LongNameLcb = NtfsLookupLcbByFlags( Fcb, FILE_NAME_NTFS );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Verify that we don't have a case insensitive match in the directory UNLESS it is for
|
|
// the short name on the link we are adding this entry to. Our checks above already
|
|
// verified that any short name we find will match some component of the link we
|
|
// were called with.
|
|
//
|
|
|
|
FoundLink = NtfsLookupEntry( IrpContext,
|
|
ParentScb,
|
|
TRUE,
|
|
&ShortName,
|
|
&ShortNameAttr,
|
|
&ShortNameAttrLength,
|
|
NULL,
|
|
&IndexEntry,
|
|
&IndexEntryBcb,
|
|
NULL );
|
|
|
|
//
|
|
// If we found a link then there is nothing to do. Either its the same file in
|
|
// which case we noop or we have a name collision
|
|
//
|
|
|
|
if (FoundLink) {
|
|
|
|
if (NtfsEqualMftRef( &IndexEntry->FileReference, &Scb->Fcb->FileReference )) {
|
|
leave;
|
|
} else {
|
|
NtfsRaiseStatus( IrpContext, STATUS_OBJECT_NAME_COLLISION, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the short name DOS bit is set.
|
|
//
|
|
|
|
ShortNameAttr->Flags = FILE_NAME_DOS;
|
|
|
|
//
|
|
// Grow the short name Lcb buffers if present.
|
|
//
|
|
|
|
if (ShortNameLcb != NULL) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
ShortNameLcb,
|
|
&ShortName,
|
|
FILE_NAME_DOS,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// We now have the appropriate Lcb's for the name switch and know that their buffers
|
|
// are the appropriate size. Proceed now to make the changes on disk and update the
|
|
// appropriate in-memory structures. Start with any on-disk changes which may need to
|
|
// be rolled back.
|
|
//
|
|
|
|
//
|
|
// Convert the corresponding long name to an Ntfs-only long name if necessary.
|
|
//
|
|
|
|
if (LongNameLcb != NULL) {
|
|
|
|
//
|
|
// It's possible that we don't need to update the flags.
|
|
//
|
|
|
|
if (LongNameLcb->FileNameAttr->Flags != FILE_NAME_NTFS) {
|
|
|
|
NtfsUpdateFileNameFlags( IrpContext,
|
|
Fcb,
|
|
ParentScb,
|
|
FILE_NAME_NTFS,
|
|
LongNameLcb->FileNameAttr );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the LongNameLcb is NULL then our caller must have opened
|
|
// through the short name Lcb. Since there must be a corresponding
|
|
// NTFS only name we don't need to update the flags.
|
|
//
|
|
|
|
ASSERT( Lcb->FileNameAttr->Flags == FILE_NAME_DOS );
|
|
ExistingShortName = TRUE;
|
|
}
|
|
|
|
//
|
|
// Remove the existing short name if necessary.
|
|
//
|
|
|
|
if (ExistingShortName) {
|
|
|
|
NtfsRemoveLinkViaFlags( IrpContext,
|
|
Fcb,
|
|
ParentScb,
|
|
FILE_NAME_DOS,
|
|
NULL,
|
|
&OldShortName );
|
|
|
|
//
|
|
// Now allocate a full name for the dir notify.
|
|
//
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
//
|
|
// Figure out the length of the name.
|
|
//
|
|
|
|
FullOldShortName.Length = OldShortName.Length + ParentScb->ScbType.Index.NormalizedName.Length;
|
|
|
|
if (ParentScb != Vcb->RootIndexScb) {
|
|
|
|
FullOldShortName.Length += sizeof( WCHAR );
|
|
}
|
|
|
|
FullOldShortName.MaximumLength = FullOldShortName.Length;
|
|
FullOldShortName.Buffer = NtfsAllocatePool( PagedPool, FullOldShortName.Length );
|
|
|
|
//
|
|
// Copy in the full name. Note we always copy in the '\' separator but will automatically
|
|
// overwrite in the case where it wasn't needed.
|
|
//
|
|
|
|
RtlCopyMemory( FullOldShortName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
*(FullOldShortName.Buffer + (ParentScb->ScbType.Index.NormalizedName.Length / sizeof( WCHAR ))) = L'\\';
|
|
|
|
RtlCopyMemory( Add2Ptr( FullOldShortName.Buffer, FullOldShortName.Length - OldShortName.Length ),
|
|
OldShortName.Buffer,
|
|
OldShortName.Length );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the correct dup info into the attribute.
|
|
//
|
|
|
|
RtlCopyMemory( &ShortNameAttr->Info,
|
|
&Fcb->Info,
|
|
sizeof( DUPLICATED_INFORMATION ));
|
|
|
|
//
|
|
// Put it in the file record.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
NtfsCreateAttributeWithValue( IrpContext,
|
|
Fcb,
|
|
$FILE_NAME,
|
|
NULL,
|
|
ShortNameAttr,
|
|
NtfsFileNameSize( ShortNameAttr ),
|
|
0,
|
|
&ParentScb->Fcb->FileReference,
|
|
TRUE,
|
|
&AttrContext );
|
|
|
|
//
|
|
// Now put it in the index entry.
|
|
//
|
|
|
|
NtfsAddIndexEntry( IrpContext,
|
|
ParentScb,
|
|
ShortNameAttr,
|
|
NtfsFileNameSize( ShortNameAttr ),
|
|
&Fcb->FileReference,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Now allocate a full name for the dir notify.
|
|
//
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
//
|
|
// Figure out the length of the name.
|
|
//
|
|
|
|
FullShortName.Length = ShortName.Length + ParentScb->ScbType.Index.NormalizedName.Length;
|
|
|
|
if (ParentScb != Vcb->RootIndexScb) {
|
|
|
|
FullShortName.Length += sizeof( WCHAR );
|
|
}
|
|
|
|
FullShortName.MaximumLength = FullShortName.Length;
|
|
FullShortName.Buffer = NtfsAllocatePool( PagedPool, FullShortName.Length );
|
|
|
|
//
|
|
// Copy in the full name. Note we always copy in the '\' separator but will automatically
|
|
// overwrite in the case where it wasn't needed.
|
|
//
|
|
|
|
RtlCopyMemory( FullShortName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
*(FullShortName.Buffer + (ParentScb->ScbType.Index.NormalizedName.Length / sizeof( WCHAR ))) = L'\\';
|
|
|
|
RtlCopyMemory( Add2Ptr( FullShortName.Buffer, FullShortName.Length - ShortName.Length ),
|
|
ShortName.Buffer,
|
|
ShortName.Length );
|
|
}
|
|
|
|
//
|
|
// Write the usn journal entry now if active. We want to write this log record
|
|
// before updating the in-memory data structures.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext,
|
|
Fcb,
|
|
USN_REASON_HARD_LINK_CHANGE );
|
|
|
|
//
|
|
// The on-disk changes are complete. Checkpoint the transaction now so we don't have to
|
|
// roll back any in-memory structures if we get a LOG_FILE_FULL when writing to the Usn journal.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Update the existing long and short names Lcb with the new names and flags if necessary.
|
|
//
|
|
|
|
if (LongNameLcb != NULL) {
|
|
|
|
LongNameLcb->FileNameAttr->Flags = FILE_NAME_NTFS;
|
|
}
|
|
|
|
if (ShortNameLcb != NULL) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
ShortNameLcb,
|
|
&ShortName,
|
|
FILE_NAME_DOS,
|
|
FALSE );
|
|
}
|
|
|
|
if (ReportDirNotify) {
|
|
|
|
//
|
|
// Generate the DirNotify event for the old short name if necessary.
|
|
//
|
|
|
|
if (ExistingShortName) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&FullOldShortName,
|
|
FullOldShortName.Length - OldShortName.Length,
|
|
NULL,
|
|
NULL,
|
|
IsDirectory( &Fcb->Info ) ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
|
|
FILE_ACTION_RENAMED_OLD_NAME,
|
|
ParentScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Generate the DirNotify event for the new short name.
|
|
//
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Vcb,
|
|
&FullShortName,
|
|
FullShortName.Length - ShortName.Length,
|
|
NULL,
|
|
NULL,
|
|
IsDirectory( &Fcb->Info ) ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,
|
|
FILE_ACTION_RENAMED_NEW_NAME,
|
|
ParentScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Change the time stamps in the parent if we modified the links in this directory.
|
|
//
|
|
|
|
NtfsUpdateFcb( ParentScb->Fcb,
|
|
(FCB_INFO_CHANGED_LAST_CHANGE |
|
|
FCB_INFO_CHANGED_LAST_MOD |
|
|
FCB_INFO_UPDATE_LAST_ACCESS) );
|
|
|
|
//
|
|
// Update the last change time and archive bit. No archive bit change for
|
|
// directories.
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
|
|
|
|
//
|
|
// Don't set the archive bit on a directory. Otherwise we break existing
|
|
// apps that don't expect to see this flag.
|
|
//
|
|
|
|
if (!IsDirectory( &Fcb->Info )) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_SET_ARCHIVE );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsSetShortNameInfo );
|
|
|
|
//
|
|
// Cleanup the allocations and contexts used.
|
|
//
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
if (ShortNameAttr != NULL) {
|
|
|
|
NtfsFreePool( ShortNameAttr );
|
|
}
|
|
|
|
if (OldShortName.Buffer != NULL) {
|
|
|
|
NtfsFreePool( OldShortName.Buffer );
|
|
}
|
|
|
|
if (FullOldShortName.Buffer != NULL) {
|
|
|
|
NtfsFreePool( FullOldShortName.Buffer );
|
|
}
|
|
|
|
if (FullShortName.Buffer != NULL) {
|
|
|
|
NtfsFreePool( FullShortName.Buffer );
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &IndexEntryBcb );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetShortNameInfo: Exit -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set position information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PFILE_POSITION_INFORMATION Buffer;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetPositionInfo...\n") );
|
|
|
|
//
|
|
// Reference the system buffer containing the user specified position
|
|
// information record
|
|
//
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
FileObject->CurrentByteOffset = Buffer->CurrentByteOffset;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetPositionInfo -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsPrepareToShrinkFileSize (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
LONGLONG NewFileSize
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Page in the last page of the file so we don't deadlock behind another thread
|
|
trying to access it. (CcSetFileSizes will do a purge that will try to zero
|
|
the cachemap directly when we shrink a file)
|
|
|
|
Note: this requires droping and regaining the main resource to not deadlock
|
|
and must be done before a transaction has started
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
NewFileSize - The new size the file will shrink to
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
{
|
|
IO_STATUS_BLOCK Iosb;
|
|
ULONG Buffer;
|
|
|
|
if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)&NewFileSize )) {
|
|
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
|
|
if ((Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL) &&
|
|
((NewFileSize % PAGE_SIZE) != 0)) {
|
|
|
|
if (NULL == Scb->FileObject) {
|
|
NtfsCreateInternalAttributeStream( IrpContext,
|
|
Scb,
|
|
FALSE,
|
|
&NtfsInternalUseFile[PREPARETOSHRINKFILESIZE_FILE_NUMBER] );
|
|
}
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ) );
|
|
NtfsReleaseScb( IrpContext, Scb );
|
|
|
|
NewFileSize = NewFileSize & ~(PAGE_SIZE - 1);
|
|
if (!CcCopyRead( Scb->FileObject,
|
|
(PLARGE_INTEGER)&NewFileSize,
|
|
1,
|
|
BooleanFlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT ),
|
|
&Buffer,
|
|
&Iosb )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Scb );
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetAllocationInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set allocation information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - This is the Scb for the open operation. May not be present if
|
|
this is a Mm call.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PFCB Fcb = Scb->Fcb;
|
|
BOOLEAN NonResidentPath = FALSE;
|
|
BOOLEAN FileIsCached = FALSE;
|
|
BOOLEAN ClearCheckSizeFlag = FALSE;
|
|
|
|
LONGLONG NewAllocationSize;
|
|
LONGLONG PrevAllocationSize;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetAllocationInfo...\n") );
|
|
|
|
//
|
|
// Are we serialized correctly? In NtfsCommonSetInformation above, we get
|
|
// paging shared for a lazy writer callback, but we should never end up in
|
|
// here from a lazy writer callback.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScbPagingIo( Scb ) );
|
|
|
|
//
|
|
// If this attribute has been 'deleted' then we we can return immediately
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo: Attribute is already deleted\n") );
|
|
|
|
return Status;
|
|
}
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
if (Ccb != NULL) {
|
|
|
|
//
|
|
// Remember the source info flags in the Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
}
|
|
|
|
//
|
|
// Save the current state of the Scb.
|
|
//
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// Get the new allocation size.
|
|
//
|
|
|
|
NewAllocationSize = ((PFILE_ALLOCATION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->AllocationSize.QuadPart;
|
|
PrevAllocationSize = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
//
|
|
// Check for a valid input value for the file size.
|
|
//
|
|
|
|
ASSERT( NewAllocationSize >= 0 );
|
|
|
|
if ((ULONGLONG)NewAllocationSize > MAXFILESIZE) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo: Invalid allocation size\n") );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Do work to prepare for shrinking file if necc.
|
|
//
|
|
|
|
if (NewAllocationSize < Scb->Header.FileSize.QuadPart) {
|
|
|
|
//
|
|
// Paging IO should never shrink the file.
|
|
//
|
|
|
|
ASSERT( !FlagOn( Irp->Flags, IRP_PAGING_IO ) );
|
|
|
|
Status = NtfsPrepareToShrinkFileSize( IrpContext, FileObject, Scb, NewAllocationSize );
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Use a try-finally so we can update the on disk time-stamps.
|
|
//
|
|
|
|
try {
|
|
|
|
#ifdef SYSCACHE
|
|
//
|
|
// Let's remember this.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_SYSCACHE_FILE )) {
|
|
|
|
PSYSCACHE_EVENT SyscacheEvent;
|
|
|
|
SyscacheEvent = NtfsAllocatePool( PagedPool, sizeof( SYSCACHE_EVENT ) );
|
|
|
|
SyscacheEvent->EventTypeCode = SYSCACHE_SET_ALLOCATION_SIZE;
|
|
SyscacheEvent->Data1 = NewAllocationSize;
|
|
SyscacheEvent->Data2 = 0L;
|
|
|
|
InsertTailList( &Scb->ScbType.Data.SyscacheEventList, &SyscacheEvent->EventList );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// It is extremely expensive to make this call on a file that is not
|
|
// cached, and Ntfs has suffered stack overflows in addition to massive
|
|
// time and disk I/O expense (CcZero data on user mapped files!). Therefore,
|
|
// if no one has the file cached, we cache it here to make this call cheaper.
|
|
//
|
|
// Don't create the stream file if called from FsRtlSetFileSize (which sets
|
|
// IRP_PAGING_IO) because mm is in the process of creating a section.
|
|
//
|
|
|
|
|
|
if ((NewAllocationSize != Scb->Header.AllocationSize.QuadPart) &&
|
|
(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
FileIsCached = CcIsFileCached( FileObject );
|
|
|
|
if (!FileIsCached &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( Irp->Flags, IRP_PAGING_IO )) {
|
|
|
|
NtfsCreateInternalAttributeStream( IrpContext,
|
|
Scb,
|
|
FALSE,
|
|
&NtfsInternalUseFile[SETALLOCATIONINFO_FILE_NUMBER] );
|
|
FileIsCached = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the caller is extending the allocation of resident attribute then
|
|
// we will force it to become non-resident. This solves the problem of
|
|
// trying to keep the allocation and file sizes in sync with only one
|
|
// number to use in the attribute header.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
NtfsLookupAttributeForScb( IrpContext,
|
|
Scb,
|
|
NULL,
|
|
&AttrContext );
|
|
|
|
//
|
|
// Convert if extending.
|
|
//
|
|
|
|
if (NewAllocationSize > Scb->Header.AllocationSize.QuadPart) {
|
|
|
|
NtfsConvertToNonresident( IrpContext,
|
|
Fcb,
|
|
NtfsFoundAttribute( &AttrContext ),
|
|
(BOOLEAN) (!FileIsCached),
|
|
&AttrContext );
|
|
|
|
NonResidentPath = TRUE;
|
|
|
|
//
|
|
// Otherwise the allocation is shrinking or staying the same.
|
|
//
|
|
|
|
} else {
|
|
|
|
NewAllocationSize = QuadAlign( (ULONG) NewAllocationSize );
|
|
|
|
//
|
|
// If the allocation size doesn't change, we are done.
|
|
//
|
|
|
|
if ((ULONG) NewAllocationSize == Scb->Header.AllocationSize.LowPart) {
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
|
|
//
|
|
// We are sometimes called by MM during a create section, so
|
|
// for right now the best way we have of detecting a create
|
|
// section is IRP_PAGING_IO being set, as in FsRtlSetFileSizes.
|
|
//
|
|
|
|
NtfsChangeAttributeValue( IrpContext,
|
|
Fcb,
|
|
(ULONG) NewAllocationSize,
|
|
NULL,
|
|
0,
|
|
TRUE,
|
|
FALSE,
|
|
(BOOLEAN) (!FileIsCached),
|
|
FALSE,
|
|
&AttrContext );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
CleanupAttrContext = FALSE;
|
|
|
|
//
|
|
// Post this to the Usn journal if we are shrinking the data.
|
|
//
|
|
|
|
if (NewAllocationSize < Scb->Header.FileSize.QuadPart) {
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_DATA_TRUNCATION );
|
|
}
|
|
|
|
//
|
|
// Now update the sizes in the Scb.
|
|
//
|
|
|
|
Scb->Header.AllocationSize.LowPart =
|
|
Scb->Header.FileSize.LowPart =
|
|
Scb->Header.ValidDataLength.LowPart = (ULONG) NewAllocationSize;
|
|
|
|
Scb->TotalAllocated = NewAllocationSize;
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_ALLOC, 0, 0, NewAllocationSize );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} else {
|
|
|
|
NonResidentPath = TRUE;
|
|
}
|
|
|
|
//
|
|
// We now test if we need to modify the non-resident allocation. We will
|
|
// do this in two cases. Either we're converting from resident in
|
|
// two steps or the attribute was initially non-resident.
|
|
//
|
|
|
|
if (NonResidentPath) {
|
|
|
|
NewAllocationSize = LlClustersFromBytes( Scb->Vcb, NewAllocationSize );
|
|
NewAllocationSize = LlBytesFromClusters( Scb->Vcb, NewAllocationSize );
|
|
|
|
|
|
DebugTrace( 0, Dbg, ("NewAllocationSize -> %016I64x\n", NewAllocationSize) );
|
|
|
|
//
|
|
// Now if the file allocation is being increased then we need to only add allocation
|
|
// to the attribute
|
|
//
|
|
|
|
if (Scb->Header.AllocationSize.QuadPart < NewAllocationSize) {
|
|
|
|
//
|
|
// Add either the true disk allocation or add a hole for a sparse
|
|
// file.
|
|
//
|
|
|
|
if (!FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
//
|
|
// If there is a compression unit then we could be in the process of
|
|
// decompressing. Allocate precisely in this case because we don't
|
|
// want to leave any holes. Specifically the user may have truncated
|
|
// the file and is now regenerating it yet the clear compression operation
|
|
// has already passed this point in the file (and dropped all resources).
|
|
// No one will go back to cleanup the allocation if we leave a hole now.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) &&
|
|
(Scb->CompressionUnit != 0)) {
|
|
|
|
ASSERT( FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ));
|
|
|
|
NewAllocationSize = BlockAlign( NewAllocationSize, (LONG)Scb->CompressionUnit );
|
|
}
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ),
|
|
LlClustersFromBytes( Scb->Vcb, NewAllocationSize - Scb->Header.AllocationSize.QuadPart ),
|
|
FALSE,
|
|
NULL );
|
|
|
|
} else {
|
|
|
|
NtfsAddSparseAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
Scb->Header.AllocationSize.QuadPart,
|
|
NewAllocationSize - Scb->Header.AllocationSize.QuadPart );
|
|
}
|
|
|
|
//
|
|
// Set the truncate on close flag.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
|
|
//
|
|
// Otherwise delete the allocation as requested.
|
|
//
|
|
|
|
} else if (Scb->Header.AllocationSize.QuadPart > NewAllocationSize) {
|
|
|
|
//
|
|
// Check on possible cleanup if the file will shrink.
|
|
//
|
|
|
|
if (NewAllocationSize < Scb->Header.FileSize.QuadPart) {
|
|
|
|
//
|
|
// If we will shrink FileSize, then write the UsnJournal.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_DATA_TRUNCATION );
|
|
|
|
Scb->Header.FileSize.QuadPart = NewAllocationSize;
|
|
|
|
//
|
|
// Do we need to shrink any of the valid data length values.
|
|
//
|
|
|
|
if (NewAllocationSize < Scb->Header.ValidDataLength.QuadPart) {
|
|
|
|
Scb->Header.ValidDataLength.QuadPart = NewAllocationSize;
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_ALLOC, 0, 0, NewAllocationSize );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (NewAllocationSize < Scb->ValidDataToDisk) {
|
|
|
|
Scb->ValidDataToDisk = NewAllocationSize;
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDD_CHANGE, SCE_FLAG_SET_ALLOC, 0, 0, NewAllocationSize );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
LlClustersFromBytes( Scb->Vcb, NewAllocationSize ),
|
|
MAXLONGLONG,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
}
|
|
|
|
//
|
|
// If this is the paging file then guarantee that the Mcb is fully loaded.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
NtfsPreloadAllocation( IrpContext,
|
|
Scb,
|
|
0,
|
|
LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ));
|
|
}
|
|
}
|
|
|
|
try_exit:
|
|
|
|
if (PrevAllocationSize != Scb->Header.AllocationSize.QuadPart) {
|
|
|
|
//
|
|
// Mark this file object as modified and with a size change in order to capture
|
|
// all of the changes to the Fcb.
|
|
//
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_SIZE_CHANGED );
|
|
ClearCheckSizeFlag = TRUE;
|
|
}
|
|
|
|
//
|
|
// Always set the file as modified to force a time stamp change.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Ccb )) {
|
|
|
|
SetFlag( Ccb->Flags,
|
|
(CCB_FLAG_UPDATE_LAST_MODIFY |
|
|
CCB_FLAG_UPDATE_LAST_CHANGE |
|
|
CCB_FLAG_SET_ARCHIVE) );
|
|
|
|
} else {
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
|
|
}
|
|
|
|
//
|
|
// Now capture any file size changes in this file object back to the Fcb.
|
|
//
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
|
|
|
|
//
|
|
// Update the standard information if required.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
}
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
//
|
|
// We know we wrote out any changes to the file size above so clear the
|
|
// flag in the Scb to check the attribute size. This will save us from doing
|
|
// this unnecessarily at cleanup.
|
|
//
|
|
|
|
if (ClearCheckSizeFlag) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
}
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Update duplicated information.
|
|
//
|
|
|
|
NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
|
|
|
|
//
|
|
// Update the cache manager if needed.
|
|
//
|
|
|
|
if (CcIsFileCached( FileObject )) {
|
|
//
|
|
// We want to checkpoint the transaction if there is one active.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_CC_SET_SIZE, SCE_FLAG_SET_ALLOC, *((PULONG)Add2Ptr( FileObject->SectionObjectPointer->SharedCacheMap, 0x6c)), Scb->Header.ValidDataLength.QuadPart, Scb->Header.FileSize.QuadPart );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Truncate either stream that is cached.
|
|
// Cachemap better exist or we will skip notifying cc and not potentially.
|
|
// purge the data section
|
|
//
|
|
|
|
ASSERT( FileObject->SectionObjectPointer->SharedCacheMap != NULL );
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
|
|
//
|
|
// Clear out the write mask on truncates to zero.
|
|
//
|
|
|
|
#ifdef SYSCACHE
|
|
if ((Scb->Header.FileSize.QuadPart == 0) && FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE) &&
|
|
(Scb->ScbType.Data.WriteMask != NULL)) {
|
|
RtlZeroMemory(Scb->ScbType.Data.WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Now cleanup the stream we created if there are no more user
|
|
// handles.
|
|
//
|
|
|
|
if ((Scb->CleanupCount == 0) && (Scb->FileObject != NULL)) {
|
|
NtfsDeleteInternalAttributeStream( Scb, FALSE, FALSE );
|
|
}
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsSetAllocation );
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo -> %08lx\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetEndOfFileInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb OPTIONAL,
|
|
IN BOOLEAN VcbAcquired
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set end of file information function.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Supplies the Ccb for this operation. Will always be present if the
|
|
Vcb is acquired. Otherwise we must test for it.
|
|
|
|
AcquiredVcb - Indicates if this request has acquired the Vcb, meaning
|
|
do we have duplicate information to update.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PFCB Fcb = Scb->Fcb;
|
|
BOOLEAN NonResidentPath = TRUE;
|
|
BOOLEAN FileSizeChanged = FALSE;
|
|
BOOLEAN FileIsCached = FALSE;
|
|
|
|
LONGLONG NewFileSize;
|
|
LONGLONG NewValidDataLength;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FILE_OBJECT( FileObject );
|
|
ASSERT_IRP( Irp );
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsSetEndOfFileInfo...\n") );
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
//
|
|
// Get the new file size and whether this is coming from the lazy writer.
|
|
//
|
|
|
|
NewFileSize = ((PFILE_END_OF_FILE_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart;
|
|
|
|
//
|
|
// If this attribute has been 'deleted' then return immediately.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsEndOfFileInfo: No work to do\n") );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Save the current state of the Scb.
|
|
//
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// If we are called from the cache manager then we want to update the valid data
|
|
// length if necessary and also perform an update duplicate call if the Vcb
|
|
// is held.
|
|
//
|
|
|
|
if (IoGetCurrentIrpStackLocation(Irp)->Parameters.SetFile.AdvanceOnly) {
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb ) && (Scb->CleanupCount == 0)) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_WRITE, SCE_FLAG_SET_EOF, Scb->Header.ValidDataLength.QuadPart, Scb->ValidDataToDisk, NewFileSize );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// We only have work to do if the file is nonresident.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
//
|
|
// Assume this is the lazy writer and set NewValidDataLength to
|
|
// NewFileSize (NtfsWriteFileSizes never goes beyond what's in the
|
|
// Fcb).
|
|
//
|
|
|
|
NewValidDataLength = NewFileSize;
|
|
NewFileSize = Scb->Header.FileSize.QuadPart;
|
|
|
|
//
|
|
// If this file has a compressed stream, then we have to possibly
|
|
// reduce NewValidDataLength according to dirty data in the opposite
|
|
// stream (compressed or uncompressed) from which we were called.
|
|
//
|
|
|
|
#ifdef COMPRESS_ON_WIRE
|
|
if (Scb->NonpagedScb->SegmentObjectC.SharedCacheMap != NULL) {
|
|
|
|
LARGE_INTEGER FlushedValidData;
|
|
PSECTION_OBJECT_POINTERS SegmentObject = &Scb->NonpagedScb->SegmentObject;
|
|
|
|
//
|
|
// Assume the other stream is not cached.
|
|
//
|
|
|
|
FlushedValidData.QuadPart = NewValidDataLength;
|
|
|
|
//
|
|
// If we were called for the compressed stream, then get flushed number
|
|
// for the normal stream.
|
|
//
|
|
|
|
if (FileObject->SectionObjectPointer != SegmentObject) {
|
|
if (SegmentObject->SharedCacheMap != NULL) {
|
|
FlushedValidData = CcGetFlushedValidData( SegmentObject, FALSE );
|
|
}
|
|
|
|
//
|
|
// Else if we were called for the normal stream, get the flushed number
|
|
// for the compressed stream.
|
|
//
|
|
|
|
} else {
|
|
FlushedValidData = CcGetFlushedValidData( &Scb->NonpagedScb->SegmentObjectC, FALSE );
|
|
}
|
|
|
|
if (NewValidDataLength > FlushedValidData.QuadPart) {
|
|
NewValidDataLength = FlushedValidData.QuadPart;
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// NtfsWriteFileSizes will trim the new vdl down to filesize if necc. for on disk updates
|
|
// so we only need to explicitly trim it ourselfs for cases when its really growing
|
|
// but cc thinks its gone farther than it really has
|
|
// E.g in the activevacb case when its replaced cc considers the whole page dirty and
|
|
// advances valid data goal to the end of the page
|
|
//
|
|
// 3 pts protect us here - cc always trims valid data goal when we shrink so any
|
|
// callbacks indicate real data from this size file
|
|
// We inform cc of the new vdl on all unbuffered writes so eventually he will
|
|
// call us back to update for new disk sizes
|
|
// if mm and cc are active in a file we will let mm
|
|
// flush all pages beyond vdl. For the boundary page
|
|
// cc can flush it but we will move vdl fwd at that time as well
|
|
//
|
|
|
|
if ((Scb->Header.ValidDataLength.QuadPart < NewFileSize) &&
|
|
(NewValidDataLength > Scb->Header.ValidDataLength.QuadPart)) {
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_EOF, NewValidDataLength, 0, Scb->Header.ValidDataLength.QuadPart );
|
|
}
|
|
#endif
|
|
|
|
NewValidDataLength = Scb->Header.ValidDataLength.QuadPart;
|
|
} // endif advancing VDL
|
|
|
|
//
|
|
// Always call writefilesizes in case on disk VDL is less than the
|
|
// in memory one
|
|
//
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&NewValidDataLength,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// If we acquired the Vcb then do the update duplicate if necessary.
|
|
//
|
|
|
|
if (VcbAcquired) {
|
|
|
|
//
|
|
// Now capture any file size changes in this file object back to the Fcb.
|
|
//
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
|
|
|
|
//
|
|
// Update the standard information if required.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
}
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Update duplicated information.
|
|
//
|
|
|
|
NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
|
|
}
|
|
|
|
//
|
|
// We know the file size for this Scb is now correct on disk.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
} else {
|
|
|
|
if (Ccb != NULL) {
|
|
|
|
//
|
|
// Remember the source info flags in the Ccb.
|
|
//
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
}
|
|
|
|
//
|
|
// Check for a valid input value for the file size.
|
|
//
|
|
|
|
if ((ULONGLONG)NewFileSize > MAXFILESIZE) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo: Invalid file size -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Do work to prepare for shrinking file if necc.
|
|
//
|
|
|
|
if (NewFileSize < Scb->Header.FileSize.QuadPart) {
|
|
|
|
Status = NtfsPrepareToShrinkFileSize( IrpContext, FileObject, Scb, NewFileSize );
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if we really are changing the file size.
|
|
//
|
|
|
|
if (Scb->Header.FileSize.QuadPart != NewFileSize) {
|
|
|
|
FileSizeChanged = TRUE;
|
|
|
|
//
|
|
// Post the FileSize change to the Usn Journal
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext,
|
|
Scb,
|
|
((NewFileSize > Scb->Header.FileSize.QuadPart) ?
|
|
USN_REASON_DATA_EXTEND :
|
|
USN_REASON_DATA_TRUNCATION) );
|
|
}
|
|
|
|
//
|
|
// It is extremely expensive to make this call on a file that is not
|
|
// cached, and Ntfs has suffered stack overflows in addition to massive
|
|
// time and disk I/O expense (CcZero data on user mapped files!). Therefore,
|
|
// if no one has the file cached, we cache it here to make this call cheaper.
|
|
//
|
|
// Don't create the stream file if called from FsRtlSetFileSize (which sets
|
|
// IRP_PAGING_IO) because mm is in the process of creating a section.
|
|
//
|
|
|
|
if (FileSizeChanged &&
|
|
(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
|
|
|
|
FileIsCached = CcIsFileCached( FileObject );
|
|
|
|
if (!FileIsCached &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
!FlagOn( Irp->Flags, IRP_PAGING_IO )) {
|
|
|
|
NtfsCreateInternalAttributeStream( IrpContext,
|
|
Scb,
|
|
FALSE,
|
|
&NtfsInternalUseFile[SETENDOFFILEINFO_FILE_NUMBER] );
|
|
FileIsCached = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is a resident attribute we will try to keep it resident.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
|
|
if (FileSizeChanged) {
|
|
|
|
//
|
|
// If the new file size is larger than a file record then convert
|
|
// to non-resident and use the non-resident code below. Otherwise
|
|
// call ChangeAttributeValue which may also convert to nonresident.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
try {
|
|
|
|
NtfsLookupAttributeForScb( IrpContext,
|
|
Scb,
|
|
NULL,
|
|
&AttrContext );
|
|
|
|
//
|
|
// If we are growing out of the file record then force the non-resident
|
|
// path. We especially need this for sparse files to make sure it
|
|
// stays either fully allocated or fully deallocated. QuadAlign the new
|
|
// size to handle the close boundary cases.
|
|
//
|
|
|
|
FileRecord = NtfsContainingFileRecord( &AttrContext );
|
|
|
|
ASSERT( FileRecord->FirstFreeByte > Scb->Header.FileSize.LowPart );
|
|
|
|
if ((FileRecord->FirstFreeByte - Scb->Header.FileSize.QuadPart + QuadAlign( NewFileSize )) >=
|
|
Scb->Vcb->BytesPerFileRecordSegment) {
|
|
|
|
NtfsConvertToNonresident( IrpContext,
|
|
Fcb,
|
|
NtfsFoundAttribute( &AttrContext ),
|
|
(BOOLEAN) (!FileIsCached),
|
|
&AttrContext );
|
|
|
|
} else {
|
|
|
|
ULONG AttributeOffset;
|
|
|
|
//
|
|
// We are sometimes called by MM during a create section, so
|
|
// for right now the best way we have of detecting a create
|
|
// section is IRP_PAGING_IO being set, as in FsRtlSetFileSizes.
|
|
//
|
|
|
|
if ((ULONG) NewFileSize > Scb->Header.FileSize.LowPart) {
|
|
|
|
AttributeOffset = Scb->Header.ValidDataLength.LowPart;
|
|
|
|
} else {
|
|
|
|
AttributeOffset = (ULONG) NewFileSize;
|
|
}
|
|
|
|
NtfsChangeAttributeValue( IrpContext,
|
|
Fcb,
|
|
AttributeOffset,
|
|
NULL,
|
|
(ULONG) NewFileSize - AttributeOffset,
|
|
TRUE,
|
|
FALSE,
|
|
(BOOLEAN) (!FileIsCached),
|
|
FALSE,
|
|
&AttrContext );
|
|
|
|
Scb->Header.FileSize.QuadPart = NewFileSize;
|
|
|
|
//
|
|
// If the file went non-resident, then the allocation size in
|
|
// the Scb is correct. Otherwise we quad-align the new file size.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.FileSize.LowPart );
|
|
Scb->Header.ValidDataLength.QuadPart = NewFileSize;
|
|
Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_EOF, 0, 0, NewFileSize );
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
NonResidentPath = FALSE;
|
|
}
|
|
|
|
} finally {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
} else {
|
|
|
|
NonResidentPath = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now test if we need to modify the non-resident Eof. We will
|
|
// do this in two cases. Either we're converting from resident in
|
|
// two steps or the attribute was initially non-resident. We can ignore
|
|
// this step if not changing the file size.
|
|
//
|
|
|
|
if (NonResidentPath) {
|
|
|
|
//
|
|
// Now determine where the new file size lines up with the
|
|
// current file layout. The two cases we need to consider are
|
|
// where the new file size is less than the current file size and
|
|
// valid data length, in which case we need to shrink them.
|
|
// Or we new file size is greater than the current allocation,
|
|
// in which case we need to extend the allocation to match the
|
|
// new file size.
|
|
//
|
|
|
|
if (NewFileSize > Scb->Header.AllocationSize.QuadPart) {
|
|
|
|
DebugTrace( 0, Dbg, ("Adding allocation to file\n") );
|
|
|
|
//
|
|
// Add either the true disk allocation or add a hole for a sparse
|
|
// file.
|
|
//
|
|
|
|
if (!FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
LONGLONG NewAllocationSize = NewFileSize;
|
|
|
|
//
|
|
// If there is a compression unit then we could be in the process of
|
|
// decompressing. Allocate precisely in this case because we don't
|
|
// want to leave any holes. Specifically the user may have truncated
|
|
// the file and is now regenerating it yet the clear compression operation
|
|
// has already passed this point in the file (and dropped all resources).
|
|
// No one will go back to cleanup the allocation if we leave a hole now.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) &&
|
|
(Scb->CompressionUnit != 0)) {
|
|
|
|
ASSERT( FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ));
|
|
NewAllocationSize += Scb->CompressionUnit - 1;
|
|
((PLARGE_INTEGER) &NewAllocationSize)->LowPart &= ~(Scb->CompressionUnit - 1);
|
|
}
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ),
|
|
LlClustersFromBytes(Scb->Vcb, (NewAllocationSize - Scb->Header.AllocationSize.QuadPart)),
|
|
FALSE,
|
|
NULL );
|
|
|
|
} else {
|
|
|
|
NtfsAddSparseAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
Scb->Header.AllocationSize.QuadPart,
|
|
NewFileSize - Scb->Header.AllocationSize.QuadPart );
|
|
}
|
|
|
|
} else {
|
|
|
|
LONGLONG DeletePoint;
|
|
|
|
//
|
|
// If this is a sparse file we actually want to leave a hole between
|
|
// the end of the file and the allocation size.
|
|
//
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE ) &&
|
|
(NewFileSize < Scb->Header.FileSize.QuadPart) &&
|
|
((DeletePoint = NewFileSize + Scb->CompressionUnit - 1) < Scb->Header.AllocationSize.QuadPart)) {
|
|
|
|
((PLARGE_INTEGER) &DeletePoint)->LowPart &= ~(Scb->CompressionUnit - 1);
|
|
|
|
ASSERT( DeletePoint < Scb->Header.AllocationSize.QuadPart );
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
FileObject,
|
|
Scb,
|
|
LlClustersFromBytesTruncate( Scb->Vcb, DeletePoint ),
|
|
LlClustersFromBytesTruncate( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ) - 1,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
|
|
}
|
|
|
|
NewValidDataLength = Scb->Header.ValidDataLength.QuadPart;
|
|
|
|
//
|
|
// If this is a paging file, let the whole thing be valid
|
|
// so that we don't end up zeroing pages! Also, make sure
|
|
// we really write this into the file.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
|
|
VCN AllocatedVcns;
|
|
|
|
AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
|
|
|
|
Scb->Header.ValidDataLength.QuadPart =
|
|
NewValidDataLength = NewFileSize;
|
|
|
|
//
|
|
// If this is the paging file then guarantee that the Mcb is fully loaded.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
|
|
}
|
|
|
|
if (NewFileSize < NewValidDataLength) {
|
|
|
|
Scb->Header.ValidDataLength.QuadPart =
|
|
NewValidDataLength = NewFileSize;
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_EOF, 0, 0, NewFileSize );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) &&
|
|
(NewFileSize < Scb->ValidDataToDisk)) {
|
|
|
|
Scb->ValidDataToDisk = NewFileSize;
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDD_CHANGE, SCE_FLAG_SET_EOF, 0, 0, NewFileSize );
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
Scb->Header.FileSize.QuadPart = NewFileSize;
|
|
|
|
//
|
|
// Call our common routine to modify the file sizes. We are now
|
|
// done with NewFileSize and NewValidDataLength, and we have
|
|
// PagingIo + main exclusive (so no one can be working on this Scb).
|
|
// NtfsWriteFileSizes uses the sizes in the Scb, and this is the
|
|
// one place where in Ntfs where we wish to use a different value
|
|
// for ValidDataLength. Therefore, we save the current ValidData
|
|
// and plug it with our desired value and restore on return.
|
|
//
|
|
|
|
ASSERT( NewFileSize == Scb->Header.FileSize.QuadPart );
|
|
ASSERT( NewValidDataLength == Scb->Header.ValidDataLength.QuadPart );
|
|
NtfsVerifySizes( &Scb->Header );
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&Scb->Header.ValidDataLength.QuadPart,
|
|
BooleanFlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ),
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// If the file size changed then mark this file object as having changed the size.
|
|
//
|
|
|
|
if (FileSizeChanged) {
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_SIZE_CHANGED );
|
|
}
|
|
|
|
//
|
|
// Always mark the data stream as modified.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Ccb )) {
|
|
|
|
SetFlag( Ccb->Flags,
|
|
(CCB_FLAG_UPDATE_LAST_MODIFY |
|
|
CCB_FLAG_UPDATE_LAST_CHANGE |
|
|
CCB_FLAG_SET_ARCHIVE) );
|
|
|
|
} else {
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
|
|
}
|
|
|
|
//
|
|
// Now capture any file size changes in this file object back to the Fcb.
|
|
//
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, VcbAcquired );
|
|
|
|
//
|
|
// Update the standard information if required.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
}
|
|
|
|
//
|
|
// We know we wrote out any changes to the file size above so clear the
|
|
// flag in the Scb to check the attribute size. This will save us from doing
|
|
// this unnecessarily at cleanup.
|
|
//
|
|
|
|
if (FileSizeChanged) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
}
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Update duplicated information.
|
|
//
|
|
|
|
if (VcbAcquired) {
|
|
|
|
NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
|
|
}
|
|
|
|
if (CcIsFileCached( FileObject )) {
|
|
|
|
//
|
|
// We want to checkpoint the transaction if there is one active.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_CC_SET_SIZE, SCE_FLAG_SET_EOF, *((PULONG)Add2Ptr( FileObject->SectionObjectPointer->SharedCacheMap, 0x6c)), Scb->Header.ValidDataLength.QuadPart, Scb->Header.FileSize.QuadPart );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Cache map should still exist or we won't purge the data section
|
|
//
|
|
|
|
ASSERT( FileObject->SectionObjectPointer->SharedCacheMap != NULL );
|
|
NtfsSetBothCacheSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
|
|
//
|
|
// Clear out the write mask on truncates to zero.
|
|
//
|
|
|
|
#ifdef SYSCACHE
|
|
if ((Scb->Header.FileSize.QuadPart == 0) && FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE) &&
|
|
(Scb->ScbType.Data.WriteMask != NULL)) {
|
|
RtlZeroMemory(Scb->ScbType.Data.WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Now cleanup the stream we created if there are no more user
|
|
// handles.
|
|
//
|
|
|
|
if ((Scb->CleanupCount == 0) && (Scb->FileObject != NULL)) {
|
|
NtfsDeleteInternalAttributeStream( Scb, FALSE, FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsSetValidDataLengthInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set valid data length information function.
|
|
Notes: we interact with CC but do not initiate caching ourselves. This is
|
|
only possible if the file is not mapped so we can do purges on the section.
|
|
|
|
Also the filetype check that restricts this to fileopens only is done in the
|
|
CommonSetInformation call.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Scb - Supplies the Scb for the file/directory being modified
|
|
|
|
Ccb - Ccb attached to the file. Contains cached privileges of opener
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The status of the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG NewValidDataLength;
|
|
LONGLONG NewFileSize;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// User must have manage volume privilege to explicitly tweak the VDL
|
|
//
|
|
|
|
if (!FlagOn( Ccb->AccessFlags, MANAGE_VOLUME_ACCESS)) {
|
|
return STATUS_PRIVILEGE_NOT_HELD;
|
|
}
|
|
|
|
//
|
|
// We don't support this call for compressed or sparse files
|
|
//
|
|
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK | ATTRIBUTE_FLAG_SPARSE)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
NewValidDataLength = ((PFILE_VALID_DATA_LENGTH_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->ValidDataLength.QuadPart;
|
|
NewFileSize = Scb->Header.FileSize.QuadPart;
|
|
|
|
//
|
|
// VDL can only move forward
|
|
//
|
|
|
|
if ((NewValidDataLength < Scb->Header.ValidDataLength.QuadPart) ||
|
|
(NewValidDataLength > NewFileSize) ||
|
|
(NewValidDataLength < 0)) {
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We only have work to do if the file is nonresident.
|
|
//
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
//
|
|
// We can't change the VDL without being able to purge. This should stay
|
|
// constant since we own everything exclusive
|
|
//
|
|
|
|
if (!MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, &Li0 )) {
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// Flush old data out and purge the cache so we can see new data
|
|
//
|
|
|
|
NtfsFlushAndPurgeScb( IrpContext, Scb, NULL );
|
|
|
|
//
|
|
// update the scb
|
|
//
|
|
|
|
Scb->Header.ValidDataLength.QuadPart = NewValidDataLength;
|
|
if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
|
|
Scb->ValidDataToDisk = NewValidDataLength;
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (ScbIsBeingLogged( Scb )) {
|
|
FsRtlLogSyscacheEvent( Scb, SCE_VDL_CHANGE, SCE_FLAG_SET_VDL, 0, 0, NewValidDataLength );
|
|
}
|
|
#endif
|
|
|
|
ASSERT( IrpContext->CleanupStructure != NULL );
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&NewValidDataLength,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
//
|
|
// Now capture any file size changes in this file object back to the Fcb.
|
|
//
|
|
|
|
NtfsUpdateScbFromFileObject( IrpContext, IrpSp->FileObject, Scb, FALSE );
|
|
|
|
//
|
|
// Inform CC of the new values
|
|
//
|
|
|
|
NtfsSetBothCacheSizes( IrpSp->FileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
|
|
//
|
|
// We know the file size for this Scb is now correct on disk.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
//
|
|
// Post a usn record
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Scb, USN_REASON_DATA_OVERWRITE );
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCheckScbForLinkRemoval (
|
|
IN PSCB Scb,
|
|
OUT PSCB *BatchOplockScb,
|
|
OUT PULONG BatchOplockCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to check if a link to an open Scb may be
|
|
removed for rename. We walk through all the children and
|
|
verify that they have no user opens.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb whose children are to be examined.
|
|
|
|
BatchOplockScb - Address to store Scb which may have a batch oplock.
|
|
|
|
BatchOplockCount - Number of files which have batch oplocks on this
|
|
pass through the directory tree.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS if the link can be removed,
|
|
STATUS_ACCESS_DENIED otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSCB NextScb;
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCheckScbForLinkRemoval: Entered\n") );
|
|
|
|
//
|
|
// Initialize the batch oplock state.
|
|
//
|
|
|
|
*BatchOplockCount = 0;
|
|
*BatchOplockScb = NULL;
|
|
|
|
//
|
|
// If this is a directory file and we are removing a link,
|
|
// we need to examine its descendents. We may not remove a link which
|
|
// may be an ancestor path component of any open file.
|
|
//
|
|
|
|
//
|
|
// First look for any descendents with a non-zero unclean count.
|
|
//
|
|
|
|
NextScb = Scb;
|
|
|
|
while ((NextScb = NtfsGetNextScb( NextScb, Scb )) != NULL) {
|
|
|
|
//
|
|
// Stop if there are open handles. If there is a batch oplock on
|
|
// this file then we will try to break the batch oplock. In this
|
|
// pass we will just count the number of files with batch oplocks
|
|
// and remember the first one we encounter.
|
|
//
|
|
// Skip over the Scb's with a zero cleanup count as we would otherwise
|
|
// fail this if we encounter them.
|
|
//
|
|
|
|
if (NextScb->CleanupCount != 0) {
|
|
|
|
if ((NextScb->AttributeTypeCode == $DATA) &&
|
|
(NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
|
|
FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
|
|
|
|
*BatchOplockCount += 1;
|
|
|
|
if (*BatchOplockScb == NULL) {
|
|
|
|
*BatchOplockScb = NextScb;
|
|
Status = STATUS_PENDING;
|
|
}
|
|
|
|
} else {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
DebugTrace( 0, Dbg, ("NtfsCheckScbForLinkRemoval: Directory to rename has open children\n") );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//
|
|
// We know there are no opens below this point. We will remove any prefix
|
|
// entries later.
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckScbForLinkRemoval: Exit -> %08lx\n") );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsFindTargetElements (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT TargetFileObject,
|
|
IN PSCB ParentScb,
|
|
OUT PSCB *TargetParentScb,
|
|
OUT PUNICODE_STRING FullTargetFileName,
|
|
OUT PUNICODE_STRING TargetFileName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines the target directory for the rename and the
|
|
target link name. If these is a target file object, we use that to
|
|
find the target. Otherwise the target is the same directory as the
|
|
source.
|
|
|
|
Arguments:
|
|
|
|
TargetFileObject - This is the file object which describes the target
|
|
for the link operation.
|
|
|
|
ParentScb - This is current directory for the link.
|
|
|
|
TargetParentScb - This is the location to store the parent of the target.
|
|
|
|
FullTargetFileName - This is a pointer to a unicode string which will point
|
|
to the name from the root. We clear this if there is no full name
|
|
available.
|
|
|
|
TargetFileName - This is a pointer to a unicode string which will point to
|
|
the target name on exit.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if there is no work to do, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsFindTargetElements: Entered\n") );
|
|
|
|
//
|
|
// We need to find the target parent directory, target file and target
|
|
// name for the new link. These three pieces of information allow
|
|
// us to see if the link already exists.
|
|
//
|
|
// Check if we have a file object for the target.
|
|
//
|
|
|
|
if (TargetFileObject != NULL) {
|
|
|
|
PVCB TargetVcb;
|
|
PFCB TargetFcb;
|
|
PCCB TargetCcb;
|
|
|
|
USHORT PreviousLength;
|
|
USHORT LastFileNameOffset;
|
|
|
|
//
|
|
// The target directory is given by the TargetFileObject.
|
|
// The name for the link is contained in the TargetFileObject.
|
|
//
|
|
// The target must be a user directory and must be on the
|
|
// current Vcb.
|
|
//
|
|
|
|
if ((NtfsDecodeFileObject( IrpContext,
|
|
TargetFileObject,
|
|
&TargetVcb,
|
|
&TargetFcb,
|
|
TargetParentScb,
|
|
&TargetCcb,
|
|
TRUE ) != UserDirectoryOpen) ||
|
|
|
|
((ParentScb != NULL) &&
|
|
(TargetVcb != ParentScb->Vcb))) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFindTargetElements: Target file object is invalid\n") );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Temporarily set the file name to point to the full buffer.
|
|
//
|
|
|
|
LastFileNameOffset = PreviousLength = TargetFileObject->FileName.Length;
|
|
|
|
TargetFileObject->FileName.Length = TargetFileObject->FileName.MaximumLength;
|
|
|
|
*FullTargetFileName = TargetFileObject->FileName;
|
|
|
|
//
|
|
// If the first character at the final component is a backslash, move the
|
|
// offset ahead by 2.
|
|
//
|
|
|
|
if (TargetFileObject->FileName.Buffer[LastFileNameOffset / sizeof( WCHAR )] == L'\\') {
|
|
|
|
LastFileNameOffset += sizeof( WCHAR );
|
|
}
|
|
|
|
NtfsBuildLastFileName( IrpContext,
|
|
TargetFileObject,
|
|
LastFileNameOffset,
|
|
TargetFileName );
|
|
|
|
//
|
|
// Restore the file object length.
|
|
//
|
|
|
|
TargetFileObject->FileName.Length = PreviousLength;
|
|
|
|
//
|
|
// Otherwise the rename occurs in the current directory. The directory
|
|
// is the parent of this Fcb, the name is stored in a Rename buffer.
|
|
//
|
|
|
|
} else {
|
|
|
|
PFILE_RENAME_INFORMATION Buffer;
|
|
|
|
Buffer = IrpContext->OriginatingIrp->AssociatedIrp.SystemBuffer;
|
|
|
|
*TargetParentScb = ParentScb;
|
|
|
|
TargetFileName->MaximumLength =
|
|
TargetFileName->Length = (USHORT)Buffer->FileNameLength;
|
|
TargetFileName->Buffer = (PWSTR) &Buffer->FileName;
|
|
|
|
FullTargetFileName->Length =
|
|
FullTargetFileName->MaximumLength = 0;
|
|
FullTargetFileName->Buffer = NULL;
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFindTargetElements: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsCheckLinkForNewLink (
|
|
IN PFCB Fcb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
OUT PULONG LinkFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the source and target directories and files.
|
|
It determines whether the target link needs to be removed and
|
|
whether the target link spans the same parent and file as the
|
|
source link. This routine may determine that there
|
|
is absolutely no work remaining for this link operation. This is true
|
|
if the desired link already exists.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the link which is being renamed.
|
|
|
|
FileNameAttr - This is the file name attribute for the matching link
|
|
on the disk.
|
|
|
|
FileReference - This is the file reference for the matching link found.
|
|
|
|
NewLinkName - This is the name to use for the rename.
|
|
|
|
LinkFlags - Address of flags field to store whether the source link and target
|
|
link traverse the same directory and file.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if there is no work to do, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN NoWorkToDo = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCheckLinkForNewLink: Entered\n") );
|
|
|
|
//
|
|
// Check if the file references match.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &FileReference, &Fcb->FileReference )) {
|
|
|
|
SetFlag( *LinkFlags, TRAVERSE_MATCH );
|
|
}
|
|
|
|
//
|
|
// We need to determine if we have an exact match for the link names.
|
|
//
|
|
|
|
if (RtlEqualMemory( FileNameAttr->FileName,
|
|
NewLinkName->Buffer,
|
|
NewLinkName->Length )) {
|
|
|
|
SetFlag( *LinkFlags, EXACT_CASE_MATCH );
|
|
}
|
|
|
|
//
|
|
// We now have to decide whether we will be removing the target link.
|
|
// The following conditions must hold for us to preserve the target link.
|
|
//
|
|
// 1 - The target link connects the same directory to the same file.
|
|
//
|
|
// 2 - The names are an exact case match.
|
|
//
|
|
|
|
if (FlagOn( *LinkFlags, TRAVERSE_MATCH | EXACT_CASE_MATCH ) == (TRAVERSE_MATCH | EXACT_CASE_MATCH)) {
|
|
|
|
NoWorkToDo = TRUE;
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckLinkForNewLink: Exit\n") );
|
|
|
|
return NoWorkToDo;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsCheckLinkForRename (
|
|
IN PFCB Fcb,
|
|
IN PLCB Lcb,
|
|
IN PFILE_NAME FileNameAttr,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN PUNICODE_STRING TargetFileName,
|
|
IN BOOLEAN IgnoreCase,
|
|
IN OUT PULONG RenameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the source and target directories and files.
|
|
It determines whether the target link needs to be removed and
|
|
whether the target link spans the same parent and file as the
|
|
source link. We also determine if the new link name is an exact case
|
|
match for the existing link name. The booleans indicating which links
|
|
to remove or add have already been initialized to the default values.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the link which is being renamed.
|
|
|
|
Lcb - This is the link being renamed.
|
|
|
|
FileNameAttr - This is the file name attribute for the matching link
|
|
on the disk.
|
|
|
|
FileReference - This is the file reference for the matching link found.
|
|
|
|
TargetFileName - This is the name to use for the rename.
|
|
|
|
IgnoreCase - Indicates if the user is case sensitive.
|
|
|
|
RenameFlags - Flag field which indicates which updates to perform.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCheckLinkForRename: Entered\n") );
|
|
|
|
//
|
|
// Check if the file references match.
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &FileReference, &Fcb->FileReference )) {
|
|
|
|
SetFlag( *RenameFlags, TRAVERSE_MATCH );
|
|
}
|
|
|
|
//
|
|
// We need to determine if we have an exact match between the desired name
|
|
// and the current name for the link. We already know the length are the same.
|
|
//
|
|
|
|
if (RtlEqualMemory( FileNameAttr->FileName,
|
|
TargetFileName->Buffer,
|
|
TargetFileName->Length )) {
|
|
|
|
SetFlag( *RenameFlags, EXACT_CASE_MATCH );
|
|
}
|
|
|
|
//
|
|
// If this is a traverse match (meaning the desired link and the link
|
|
// being replaced connect the same directory to the same file) we check
|
|
// if we can leave the link on the file.
|
|
//
|
|
// At the end of the rename, there must be an Ntfs name or hard link
|
|
// which matches the target name exactly.
|
|
//
|
|
|
|
if (FlagOn( *RenameFlags, TRAVERSE_MATCH )) {
|
|
|
|
//
|
|
// If we are in the same directory and are renaming between Ntfs and Dos
|
|
// links then don't remove the link twice.
|
|
//
|
|
|
|
if (!FlagOn( *RenameFlags, MOVE_TO_NEW_DIR )) {
|
|
|
|
//
|
|
// If We are renaming from between primary links then don't remove the
|
|
// source. It is removed with the target.
|
|
//
|
|
|
|
if ((Lcb->FileNameAttr->Flags != 0) && (FileNameAttr->Flags != 0)) {
|
|
|
|
ClearFlag( *RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK );
|
|
SetFlag( *RenameFlags, OVERWRITE_SOURCE_LINK );
|
|
|
|
//
|
|
// If this is an exact case match then don't remove the source at all.
|
|
//
|
|
|
|
if (FlagOn( *RenameFlags, EXACT_CASE_MATCH )) {
|
|
|
|
ClearFlag( *RenameFlags, REMOVE_SOURCE_LINK );
|
|
}
|
|
|
|
//
|
|
// If we are changing the case of a link only, then don't remove the link twice.
|
|
//
|
|
|
|
} else if (RtlEqualMemory( Lcb->ExactCaseLink.LinkName.Buffer,
|
|
FileNameAttr->FileName,
|
|
Lcb->ExactCaseLink.LinkName.Length )) {
|
|
|
|
SetFlag( *RenameFlags, OVERWRITE_SOURCE_LINK );
|
|
ClearFlag( *RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the names match exactly we can reuse the links if we don't have a
|
|
// conflict with the name flags.
|
|
//
|
|
|
|
if (FlagOn( *RenameFlags, EXACT_CASE_MATCH ) &&
|
|
(FlagOn( *RenameFlags, OVERWRITE_SOURCE_LINK ) ||
|
|
!IgnoreCase ||
|
|
!FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ))) {
|
|
|
|
//
|
|
// Otherwise we are renaming hard links or this is a Posix opener.
|
|
//
|
|
|
|
ClearFlag( *RenameFlags, REMOVE_TARGET_LINK | ADD_TARGET_LINK );
|
|
}
|
|
}
|
|
|
|
//
|
|
// The non-traverse case is already initialized.
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCheckLinkForRename: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsCleanupLinkForRemoval (
|
|
IN PFCB PreviousFcb,
|
|
IN PSCB ParentScb,
|
|
IN BOOLEAN ExistingFcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the cleanup on a file/link which is the target
|
|
of either a rename or set link operation.
|
|
|
|
Arguments:
|
|
|
|
PreviousFcb - Address to store the Fcb for the file whose link is
|
|
being removed.
|
|
|
|
ParentScb - This is the parent for the link being removed.
|
|
|
|
ExistingFcb - Address to store whether this Fcb already existed.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCleanupLinkForRemoval: Entered\n") );
|
|
|
|
//
|
|
// If the Fcb existed, we remove all of the prefix entries for it which
|
|
// belong to the parent we are given.
|
|
//
|
|
|
|
if (ExistingFcb) {
|
|
|
|
PLIST_ENTRY Links;
|
|
PLCB ThisLcb;
|
|
|
|
for (Links = PreviousFcb->LcbQueue.Flink;
|
|
Links != &PreviousFcb->LcbQueue;
|
|
Links = Links->Flink ) {
|
|
|
|
ThisLcb = CONTAINING_RECORD( Links,
|
|
LCB,
|
|
FcbLinks );
|
|
|
|
if (ThisLcb->Scb == ParentScb) {
|
|
|
|
ASSERT( NtfsIsExclusiveScb( ThisLcb->Scb ) );
|
|
NtfsRemovePrefix( ThisLcb );
|
|
}
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( ThisLcb );
|
|
|
|
} // End for each Lcb of Fcb
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCleanupLinkForRemoval: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsUpdateFcbFromLinkRemoval (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN UNICODE_STRING FileName,
|
|
IN UCHAR FileNameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to update the in-memory part of a link which
|
|
has been removed from a file. We find the Lcb's for the links and
|
|
mark them as deleted and removed.
|
|
|
|
Arguments:
|
|
|
|
ParentScb - Scb for the directory the was removed from.
|
|
|
|
ParentScb - This is the Scb for the new directory.
|
|
|
|
Fcb - The Fcb for the file whose link is being renamed.
|
|
|
|
FileName - File name for link being removed.
|
|
|
|
FileNameFlags - File name flags for link being removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb;
|
|
PLCB SplitPrimaryLcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsUpdateFcbFromLinkRemoval: Entered\n") );
|
|
|
|
SplitPrimaryLcb = NULL;
|
|
|
|
//
|
|
// Find the Lcb for the link which was removed.
|
|
//
|
|
|
|
Lcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
FileName,
|
|
FileNameFlags,
|
|
NULL );
|
|
|
|
//
|
|
// If this is a split primary, we need to find the name flags for
|
|
// the Lcb.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( Lcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( Lcb ));
|
|
}
|
|
|
|
//
|
|
// Mark any Lcb's we have as deleted and removed.
|
|
//
|
|
|
|
SetFlag( Lcb->LcbState, (LCB_STATE_DELETE_ON_CLOSE | LCB_STATE_LINK_IS_GONE) );
|
|
|
|
if (SplitPrimaryLcb) {
|
|
|
|
SetFlag( SplitPrimaryLcb->LcbState,
|
|
(LCB_STATE_DELETE_ON_CLOSE | LCB_STATE_LINK_IS_GONE) );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsUpdateFcbFromLinkRemoval: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsReplaceLinkInDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR FileNameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create the in-memory part of a link in a new
|
|
directory.
|
|
|
|
Arguments:
|
|
|
|
ParentScb - Scb for the directory the link is being created in.
|
|
|
|
Fcb - The Fcb for the file whose link is being created.
|
|
|
|
NewLinkName - Name for the new component.
|
|
|
|
FileNameFlags - These are the flags to use for the new link.
|
|
|
|
PrevLinkName - File name for link being removed.
|
|
|
|
PrevLinkNameFlags - File name flags for link being removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB TraverseLcb;
|
|
PLCB SplitPrimaryLcb = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateLinkInNewDir: Entered\n") );
|
|
|
|
SplitPrimaryLcb = NULL;
|
|
|
|
//
|
|
// Build the name for the traverse link and call strucsup to
|
|
// give us an Lcb.
|
|
//
|
|
|
|
TraverseLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
*PrevLinkName,
|
|
PrevLinkNameFlags,
|
|
NULL );
|
|
|
|
//
|
|
// If this is a split primary, we need to find the name flags for
|
|
// the Lcb.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( TraverseLcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
|
|
}
|
|
|
|
//
|
|
// We now need only to rename and combine any existing Lcb's.
|
|
//
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
TraverseLcb,
|
|
NewLinkName,
|
|
FileNameFlags,
|
|
FALSE );
|
|
|
|
if (SplitPrimaryLcb != NULL) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
SplitPrimaryLcb,
|
|
NewLinkName,
|
|
FileNameFlags,
|
|
FALSE );
|
|
|
|
NtfsCombineLcbs( IrpContext,
|
|
TraverseLcb,
|
|
SplitPrimaryLcb );
|
|
|
|
NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateLinkInNewDir: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsMoveLinkToNewDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PUNICODE_STRING NewFullLinkName,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR NewLinkNameFlags,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN OUT PLCB Lcb,
|
|
IN ULONG RenameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to move the in-memory part of a link to a new
|
|
directory. We move the link involved and its primary link partner if
|
|
it exists.
|
|
|
|
LCBs are not rolled back so after its called - no exceptions can occur that would
|
|
cause rollback. Because of this we checkpoint the current transaction before the 2nd
|
|
phase of modification when all allocations have already been done.
|
|
|
|
Arguments:
|
|
|
|
NewFullLinkName - This is the full name for the new link from the root.
|
|
|
|
NewLinkName - This is the last component name only.
|
|
|
|
NewLinkNameFlags - These are the flags to use for the new link.
|
|
|
|
ParentScb - This is the Scb for the new directory.
|
|
|
|
Fcb - The Fcb for the file whose link is being renamed.
|
|
|
|
Lcb - This is the Lcb which is the base of the rename.
|
|
|
|
RenameFlags - Flag field indicating the type of operations to perform
|
|
on file name links.
|
|
|
|
PrevLinkName - File name for link being removed. Only meaningful here
|
|
if this is a traverse match and there are remaining Lcbs for the
|
|
previous link.
|
|
|
|
PrevLinkNameFlags - File name flags for link being removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB TraverseLcb = NULL;
|
|
PLCB SplitPrimaryLcb = NULL;
|
|
BOOLEAN SplitSourceLcb = FALSE;
|
|
|
|
UNICODE_STRING TargetDirectoryName;
|
|
UNICODE_STRING SplitLinkName;
|
|
|
|
UCHAR SplitLinkNameFlags = NewLinkNameFlags;
|
|
BOOLEAN Found;
|
|
|
|
PFILE_NAME FileName;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
|
|
ULONG Pass;
|
|
BOOLEAN CheckBufferOnly;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsMoveLinkToNewDir: Entered\n") );
|
|
|
|
//
|
|
// Use a try-finally to perform cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Construct the unicode string for the parent directory.
|
|
//
|
|
|
|
TargetDirectoryName = *NewFullLinkName;
|
|
TargetDirectoryName.Length -= NewLinkName->Length;
|
|
|
|
if (TargetDirectoryName.Length > sizeof( WCHAR )) {
|
|
|
|
TargetDirectoryName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
// If the link being moved is a split primary link, we need to find
|
|
// its other half.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( Lcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( Lcb ));
|
|
SplitSourceLcb = TRUE;
|
|
|
|
//
|
|
// If we found an existing Lcb we have to update its name as well. We may be
|
|
// able to use the new name used for the Lcb passed in. However we must check
|
|
// that we don't overwrite a DOS name with an NTFS only name.
|
|
//
|
|
|
|
if (SplitPrimaryLcb &&
|
|
(SplitPrimaryLcb->FileNameAttr->Flags == FILE_NAME_DOS) &&
|
|
(NewLinkNameFlags == FILE_NAME_NTFS)) {
|
|
|
|
//
|
|
// Lookup the dos only name on disk.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
//
|
|
// Walk through the names for this entry. There better
|
|
// be one which is not a DOS-only name.
|
|
//
|
|
|
|
Found = NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
|
|
while (Found) {
|
|
|
|
FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
|
|
|
if (FileName->Flags == FILE_NAME_DOS) { break; }
|
|
|
|
Found = NtfsLookupNextAttributeByCode( IrpContext,
|
|
Fcb,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
}
|
|
|
|
//
|
|
// We should have found the entry.
|
|
//
|
|
|
|
if (!Found) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
|
|
//
|
|
// Now build the component name.
|
|
//
|
|
|
|
SplitLinkName.Buffer = FileName->FileName;
|
|
SplitLinkName.MaximumLength =
|
|
SplitLinkName.Length = FileName->FileNameLength * sizeof( WCHAR );
|
|
SplitLinkNameFlags = FILE_NAME_DOS;
|
|
|
|
} else {
|
|
|
|
SplitLinkName = *NewLinkName;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we removed or reused a traverse link, we need to check if there is
|
|
// an Lcb for it.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK | REUSE_TRAVERSE_LINK )) {
|
|
|
|
//
|
|
// Build the name for the traverse link and call strucsup to
|
|
// give us an Lcb.
|
|
//
|
|
|
|
if (FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
|
|
|
|
TraverseLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
*NewLinkName,
|
|
PrevLinkNameFlags,
|
|
NULL );
|
|
|
|
} else {
|
|
|
|
TraverseLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
*PrevLinkName,
|
|
PrevLinkNameFlags,
|
|
NULL );
|
|
}
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK )) {
|
|
|
|
//
|
|
// If this is a split primary, we need to find the name flags for
|
|
// the Lcb.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( TraverseLcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now move and combine the Lcbs. We will do this in two passes. One will allocate buffers
|
|
// of sufficient size. The other will store the names in.
|
|
//
|
|
|
|
Pass = 0;
|
|
CheckBufferOnly = TRUE;
|
|
do {
|
|
|
|
//
|
|
// Start with the Lcb used for the rename.
|
|
//
|
|
|
|
NtfsMoveLcb( IrpContext,
|
|
Lcb,
|
|
ParentScb,
|
|
Fcb,
|
|
&TargetDirectoryName,
|
|
NewLinkName,
|
|
NewLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
//
|
|
// Next do the split primary if from the source file or the target.
|
|
//
|
|
|
|
if (SplitPrimaryLcb && SplitSourceLcb) {
|
|
|
|
NtfsMoveLcb( IrpContext,
|
|
SplitPrimaryLcb,
|
|
ParentScb,
|
|
Fcb,
|
|
&TargetDirectoryName,
|
|
&SplitLinkName,
|
|
SplitLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
//
|
|
// If we are in the second pass then optionally combine these
|
|
// Lcb's and delete the split.
|
|
//
|
|
|
|
if ((SplitLinkNameFlags == NewLinkNameFlags) && !CheckBufferOnly) {
|
|
|
|
NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
|
|
NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have a traverse link and are in the second pass then combine
|
|
// with the primary Lcb.
|
|
//
|
|
|
|
if (!CheckBufferOnly) {
|
|
|
|
if (TraverseLcb != NULL) {
|
|
|
|
if (!FlagOn( RenameFlags, REUSE_TRAVERSE_LINK )) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
TraverseLcb,
|
|
NewLinkName,
|
|
NewLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
if (SplitPrimaryLcb && !SplitSourceLcb) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
SplitPrimaryLcb,
|
|
NewLinkName,
|
|
NewLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
//
|
|
// If we are in the second pass then optionally combine these
|
|
// Lcb's and delete the split.
|
|
//
|
|
|
|
if (!CheckBufferOnly) {
|
|
|
|
NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
|
|
NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
|
|
}
|
|
}
|
|
}
|
|
|
|
NtfsCombineLcbs( IrpContext,
|
|
Lcb,
|
|
TraverseLcb );
|
|
|
|
NtfsDeleteLcb( IrpContext, &TraverseLcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Checkpoint the transaction before modifying the in memory lcbs duirng the
|
|
// 2nd pass
|
|
//
|
|
|
|
if (Pass == 0) {
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
Pass += 1;
|
|
CheckBufferOnly = FALSE;
|
|
|
|
} while (Pass < 2);
|
|
|
|
} finally {
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsMoveLinkToNewDir: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
VOID
|
|
NtfsRenameLinkInDir (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PFCB Fcb,
|
|
IN OUT PLCB Lcb,
|
|
IN PUNICODE_STRING NewLinkName,
|
|
IN UCHAR NewLinkNameFlags,
|
|
IN ULONG RenameFlags,
|
|
IN PUNICODE_STRING PrevLinkName,
|
|
IN UCHAR PrevLinkNameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the in-memory work of moving renaming a link within
|
|
the same directory. It will rename an existing link to the
|
|
new name. It also merges whatever other links need to be joined with
|
|
this link. This includes the complement of a primary link pair or
|
|
an existing hard link which may be overwritten. Merging the existing
|
|
links has the effect of moving any of the Ccb's on the stale Links to
|
|
the newly modified link.
|
|
|
|
LCBs are not rolled back so after its called - no exceptions can occur that would
|
|
cause rollback. Because of this we checkpoint the current transaction before the 2nd
|
|
phase of modification when all allocations have already been done.
|
|
|
|
Arguments:
|
|
|
|
ParentScb - Scb for the directory the rename is taking place in.
|
|
|
|
Fcb - The Fcb for the file whose link is being renamed.
|
|
|
|
Lcb - This is the Lcb which is the base of the rename.
|
|
|
|
NewLinkName - This is the name to use for the new link.
|
|
|
|
NewLinkNameFlags - These are the flags to use for the new link.
|
|
|
|
RenameFlags - Flag field indicating the type of operations to perform
|
|
on the file name links.
|
|
|
|
PrevLinkName - File name for link being removed. Only meaningful for a traverse link.
|
|
|
|
PrevLinkNameFlags - File name flags for link being removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
UNICODE_STRING SplitLinkName;
|
|
UCHAR SplitLinkNameFlags = NewLinkNameFlags;
|
|
|
|
PLCB TraverseLcb = NULL;
|
|
PLCB SplitPrimaryLcb = NULL;
|
|
|
|
PFILE_NAME FileName;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
BOOLEAN Found;
|
|
|
|
ULONG Pass;
|
|
BOOLEAN CheckBufferOnly;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsRenameLinkInDir: Entered\n") );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// We have the Lcb which will be our primary Lcb and the name we need
|
|
// to perform the rename. If the current Lcb is a split primary link
|
|
// or we removed a split primary link, then we need to find
|
|
// the other split link.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( Lcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( Lcb ));
|
|
|
|
//
|
|
// If we found an existing Lcb we have to update its name as well. We may be
|
|
// able to use the new name used for the Lcb passed in. However we must check
|
|
// that we don't overwrite a DOS name with an NTFS only name.
|
|
//
|
|
|
|
if (SplitPrimaryLcb &&
|
|
(SplitPrimaryLcb->FileNameAttr->Flags == FILE_NAME_DOS) &&
|
|
(NewLinkNameFlags == FILE_NAME_NTFS)) {
|
|
|
|
//
|
|
// Lookup the dos only name on disk.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
//
|
|
// Walk through the names for this entry. There better
|
|
// be one which is not a DOS-only name.
|
|
//
|
|
|
|
Found = NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
|
|
while (Found) {
|
|
|
|
FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
|
|
|
if (FileName->Flags == FILE_NAME_DOS) { break; }
|
|
|
|
Found = NtfsLookupNextAttributeByCode( IrpContext,
|
|
Fcb,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
}
|
|
|
|
//
|
|
// We should have found the entry.
|
|
//
|
|
|
|
if (!Found) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
|
|
//
|
|
// Now build the component name.
|
|
//
|
|
|
|
SplitLinkName.Buffer = FileName->FileName;
|
|
SplitLinkName.MaximumLength =
|
|
SplitLinkName.Length = FileName->FileNameLength * sizeof( WCHAR );
|
|
SplitLinkNameFlags = FILE_NAME_DOS;
|
|
|
|
} else {
|
|
|
|
SplitLinkName = *NewLinkName;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we used a traverse link, we need to check if there is
|
|
// an Lcb for it. Ignore this for the case where we traversed to
|
|
// the other half of a primary link.
|
|
//
|
|
|
|
if (!FlagOn( RenameFlags, OVERWRITE_SOURCE_LINK ) &&
|
|
FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK | REUSE_TRAVERSE_LINK )) {
|
|
|
|
if (FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
|
|
|
|
TraverseLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
*NewLinkName,
|
|
PrevLinkNameFlags,
|
|
NULL );
|
|
|
|
} else {
|
|
|
|
TraverseLcb = NtfsCreateLcb( IrpContext,
|
|
ParentScb,
|
|
Fcb,
|
|
*PrevLinkName,
|
|
PrevLinkNameFlags,
|
|
NULL );
|
|
}
|
|
|
|
if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK )) {
|
|
|
|
//
|
|
// If this is a split primary, we need to find the name flags for
|
|
// the Lcb.
|
|
//
|
|
|
|
if (LcbSplitPrimaryLink( TraverseLcb )) {
|
|
|
|
SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
|
|
(UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
|
|
|
|
SplitLinkName = *NewLinkName;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now move and combine the Lcbs. We will do this in two passes. One will allocate buffers
|
|
// of sufficient size. The other will store the names in.
|
|
//
|
|
|
|
Pass = 0;
|
|
CheckBufferOnly = TRUE;
|
|
do {
|
|
|
|
//
|
|
// Start with the Lcb used for the rename.
|
|
//
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
Lcb,
|
|
NewLinkName,
|
|
NewLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
//
|
|
// Next do the split primary if from the source file or the target.
|
|
//
|
|
|
|
if (SplitPrimaryLcb) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
SplitPrimaryLcb,
|
|
&SplitLinkName,
|
|
SplitLinkNameFlags,
|
|
CheckBufferOnly );
|
|
|
|
//
|
|
// If we are in the second pass then optionally combine these
|
|
// Lcb's and delete the split.
|
|
//
|
|
|
|
if (!CheckBufferOnly && (SplitLinkNameFlags == NewLinkNameFlags)) {
|
|
|
|
NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
|
|
NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have a traverse link and are in the second pass then combine
|
|
// with the primary Lcb.
|
|
//
|
|
|
|
if (!CheckBufferOnly) {
|
|
|
|
if (TraverseLcb != NULL) {
|
|
|
|
if (!FlagOn( RenameFlags, REUSE_TRAVERSE_LINK )) {
|
|
|
|
NtfsRenameLcb( IrpContext,
|
|
TraverseLcb,
|
|
NewLinkName,
|
|
NewLinkNameFlags,
|
|
CheckBufferOnly );
|
|
}
|
|
|
|
NtfsCombineLcbs( IrpContext,
|
|
Lcb,
|
|
TraverseLcb );
|
|
|
|
NtfsDeleteLcb( IrpContext, &TraverseLcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Checkpoint the transaction before modifying the in memory lcbs duirng the
|
|
// 2nd pass
|
|
//
|
|
|
|
if (Pass == 0) {
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
Pass += 1;
|
|
CheckBufferOnly = FALSE;
|
|
|
|
} while (Pass < 2);
|
|
|
|
} finally {
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsRenameLinkInDir: Exit\n") );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
LONG
|
|
NtfsFileInfoExceptionFilter (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PEXCEPTION_POINTERS ExceptionPointer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Exception filter for errors during cleanup. We want to raise if this is
|
|
a retryable condition or fatal error, plow on as best we can if not.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - IrpContext
|
|
|
|
ExceptionPointer - Pointer to the exception context.
|
|
|
|
Status - Address to store the error status.
|
|
|
|
Return Value:
|
|
|
|
Exception status - EXCEPTION_CONTINUE_SEARCH if we want to raise to another handler,
|
|
EXCEPTION_EXECUTE_HANDLER if we plan to proceed on.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = ExceptionPointer->ExceptionRecord->ExceptionCode;
|
|
|
|
//
|
|
// For now break if we catch corruption errors on both free and checked
|
|
// TODO: Remove this before we ship
|
|
//
|
|
|
|
if (NtfsBreakOnCorrupt &&
|
|
((Status == STATUS_FILE_CORRUPT_ERROR) ||
|
|
(Status == STATUS_DISK_CORRUPT_ERROR))) {
|
|
|
|
if (*KdDebuggerEnabled) {
|
|
DbgPrint("*******************************************\n");
|
|
DbgPrint("NTFS detected corruption on your volume\n");
|
|
DbgPrint("IrpContext=0x%08x, VCB=0x%08x\n",IrpContext,IrpContext->Vcb);
|
|
DbgPrint("Send email to NTFSDEV\n");
|
|
DbgPrint("*******************************************\n");
|
|
DbgBreakPoint();
|
|
}
|
|
}
|
|
|
|
if (!FsRtlIsNtstatusExpected( Status )) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
} else {
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsUpdateFileDupInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the duplicate information for a file for calls
|
|
to set allocation or EOF on the main data stream. It is in a separate routine
|
|
so we don't have to put a try-except in the main path.
|
|
|
|
We will overlook any expected errors in this path. If we get any errors we
|
|
will simply leave this update to be performed at some other time.
|
|
|
|
We are guaranteed that the current transaction has been checkpointed before this
|
|
routine is called. We will look to see if the MftScb is on the exclusive list
|
|
for this IrpContext and release it if so. This is to prevent a deadlock when
|
|
we attempt to acquire the parent of this file.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb to update.
|
|
|
|
Ccb - If specified, this is the Ccb for the caller making the call.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb = NULL;
|
|
PSCB ParentScb = NULL;
|
|
ULONG FilterMatch;
|
|
|
|
PLIST_ENTRY Links;
|
|
PFCB NextFcb;
|
|
PFCB UnlockFcb = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( IrpContext->TransactionId == 0 );
|
|
|
|
//
|
|
// Check if there is an Lcb in the Ccb.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Ccb )) {
|
|
|
|
Lcb = Ccb->Lcb;
|
|
}
|
|
|
|
//
|
|
// Use a try-except to catch any errors.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Check that we don't own the Mft Scb.
|
|
//
|
|
|
|
if (Fcb->Vcb->MftScb != NULL) {
|
|
|
|
for (Links = IrpContext->ExclusiveFcbList.Flink;
|
|
Links != &IrpContext->ExclusiveFcbList;
|
|
Links = Links->Flink) {
|
|
|
|
ULONG Count;
|
|
|
|
NextFcb = (PFCB) CONTAINING_RECORD( Links,
|
|
FCB,
|
|
ExclusiveFcbLinks );
|
|
|
|
//
|
|
// If this is the Fcb for the Mft then remove it from the list.
|
|
//
|
|
|
|
if (NextFcb == Fcb->Vcb->MftScb->Fcb) {
|
|
|
|
//
|
|
// Free the snapshots for the Fcb and release the Fcb enough times
|
|
// to remove it from the list.
|
|
//
|
|
|
|
NtfsFreeSnapshotsForFcb( IrpContext, NextFcb );
|
|
|
|
Count = NextFcb->BaseExclusiveCount;
|
|
|
|
while (Count--) {
|
|
|
|
NtfsReleaseFcb( IrpContext, NextFcb );
|
|
}
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check that we don't own the quota table Scb.
|
|
//
|
|
|
|
if (Fcb->Vcb->QuotaTableScb != NULL) {
|
|
|
|
for (Links = IrpContext->ExclusiveFcbList.Flink;
|
|
Links != &IrpContext->ExclusiveFcbList;
|
|
Links = Links->Flink) {
|
|
|
|
ULONG Count;
|
|
|
|
NextFcb = (PFCB) CONTAINING_RECORD( Links,
|
|
FCB,
|
|
ExclusiveFcbLinks );
|
|
|
|
//
|
|
// If this is the Fcb for the Quota table then remove
|
|
// it from the list.
|
|
//
|
|
|
|
if (NextFcb == Fcb->Vcb->QuotaTableScb->Fcb) {
|
|
|
|
//
|
|
// Free the snapshots for the Fcb and release the Fcb enough times
|
|
// to remove it from the list.
|
|
//
|
|
|
|
NtfsFreeSnapshotsForFcb( IrpContext, NextFcb );
|
|
|
|
Count = NextFcb->BaseExclusiveCount;
|
|
|
|
while (Count--) {
|
|
|
|
NtfsReleaseFcb( IrpContext, NextFcb );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Go through and free any Scb's in the queue of shared Scb's
|
|
// for transactions.
|
|
//
|
|
|
|
if (IrpContext->SharedScb != NULL) {
|
|
|
|
NtfsReleaseSharedResources( IrpContext );
|
|
}
|
|
|
|
NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &Lcb, &ParentScb, TRUE );
|
|
NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
|
|
|
|
//
|
|
// Use a try-finally to guarantee we unlock the Fcb we might lock.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If there is no Ccb then look for one in the Lcb we just got.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( Ccb ) &&
|
|
ARGUMENT_PRESENT( Lcb )) {
|
|
|
|
PCCB NextCcb;
|
|
|
|
Links = Lcb->CcbQueue.Flink;
|
|
|
|
while (Links != &Lcb->CcbQueue) {
|
|
|
|
NextCcb = CONTAINING_RECORD( Links, CCB, LcbLinks );
|
|
|
|
NtfsLockFcb( IrpContext, NextCcb->Lcb->Fcb );
|
|
if (!FlagOn( NextCcb->Flags, CCB_FLAG_CLOSE | CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
Ccb = NextCcb;
|
|
|
|
//
|
|
// Transfer ownership of the name to the ccb - so we protected from
|
|
// closes while we're using the name
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveFcb( Fcb ));
|
|
SetFlag( Ccb->Flags, CCB_FLAG_PROTECT_NAME );
|
|
NtfsUnlockFcb( IrpContext, NextCcb->Lcb->Fcb );
|
|
|
|
UnlockFcb = NextCcb->Lcb->Fcb;
|
|
break;
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, NextCcb->Lcb->Fcb );
|
|
Links = Links->Flink;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now perform the dir notify call if there is a Ccb and this is not an
|
|
// open by FileId.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Ccb ) &&
|
|
(Fcb->Vcb->NotifyCount != 0) &&
|
|
(ParentScb != NULL) &&
|
|
!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
|
|
|
|
FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
|
|
Fcb->InfoFlags | Lcb->InfoFlags );
|
|
|
|
if (FilterMatch != 0) {
|
|
|
|
NtfsReportDirNotify( IrpContext,
|
|
Fcb->Vcb,
|
|
&Ccb->FullFileName,
|
|
Ccb->LastFileNameOffset,
|
|
NULL,
|
|
((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
|
|
(Ccb->Lcb != NULL) &&
|
|
(Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Length != 0)) ?
|
|
&Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
|
|
NULL),
|
|
FilterMatch,
|
|
FILE_ACTION_MODIFIED,
|
|
ParentScb->Fcb );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (UnlockFcb != NULL) {
|
|
|
|
NtfsLockFcb( IrpContext, UnlockFcb );
|
|
ClearFlag( Ccb->Flags, CCB_FLAG_PROTECT_NAME );
|
|
NtfsUnlockFcb( IrpContext, UnlockFcb );
|
|
}
|
|
}
|
|
|
|
NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
|
|
Fcb->InfoFlags = 0;
|
|
|
|
} except(NtfsFileInfoExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
|
|
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsStreamRename (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN PCCB Ccb,
|
|
IN BOOLEAN ReplaceIfExists,
|
|
IN PUNICODE_STRING NewStreamName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs a stream rename within a single Fcb.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Context of the call
|
|
|
|
FileObject - File object being used
|
|
|
|
Fcb - Fcb of file/directory
|
|
|
|
Scb - Stream being renamed. The parent Fcb is acquired exclusively.
|
|
|
|
Ccb - Handle used to perform the rename. Look here for usn source information.
|
|
|
|
ReplaceIfExists - TRUE => overwrite an existing stream
|
|
|
|
NewStreamName - name of new stream
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS of the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTFS_NAME_DESCRIPTOR Name;
|
|
BOOLEAN FoundIllegalCharacter;
|
|
PSCB TargetScb = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
ATTRIBUTE_TYPE_CODE TypeCode;
|
|
BOOLEAN EmptyFile;
|
|
BOOLEAN NamesSwapped = FALSE;
|
|
|
|
PSCB RestoreTargetScb = NULL;
|
|
ULONG TargetScbCompressionUnit;
|
|
USHORT TargetScbAttributeFlags;
|
|
UCHAR TargetScbCompressionUnitShift;
|
|
LONGLONG OldValidDataLengthOnDisk;
|
|
|
|
DebugDoit( int Count = 0 );
|
|
|
|
PAGED_CODE( );
|
|
|
|
DebugTrace( +1, Dbg, ( "NtfsStreamRename\n"
|
|
" IrpContext %x\n"
|
|
" Scb %x\n"
|
|
" ReplaceIf %x\n"
|
|
" NewStreamName '%Z'\n",
|
|
IrpContext, Scb, ReplaceIfExists, NewStreamName ));
|
|
|
|
//
|
|
// Take a snapshot if one doesn't exist because we'll be calling writefilesizes
|
|
//
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
//
|
|
// Capture the ccb source information.
|
|
//
|
|
|
|
if (Ccb != NULL) {
|
|
|
|
IrpContext->SourceInfo = Ccb->UsnSourceInfo;
|
|
}
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
|
|
try {
|
|
|
|
//
|
|
// Validate name is just :stream:type. No file name is specified and
|
|
// at least a stream or type must be specified.
|
|
//
|
|
|
|
RtlZeroMemory( &Name, sizeof( Name ));
|
|
|
|
if (!NtfsParseName( *NewStreamName, FALSE, &FoundIllegalCharacter, &Name ) ||
|
|
FlagOn( Name.FieldsPresent, FILE_NAME_PRESENT_FLAG ) ||
|
|
(!FlagOn( Name.FieldsPresent, ATTRIBUTE_NAME_PRESENT_FLAG ) &&
|
|
!FlagOn( Name.FieldsPresent, ATTRIBUTE_TYPE_PRESENT_FLAG )) ||
|
|
(Name.AttributeName.Length > NTFS_MAX_ATTR_NAME_LEN * sizeof( WCHAR ))) {
|
|
|
|
DebugTrace( 0, Dbg, ("Name is illegal\n"));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, (" Fields: %x\n"
|
|
" AttributeName %x %x/%x\n",
|
|
Name.FieldsPresent,
|
|
Name.AttributeName.Buffer,
|
|
Name.AttributeName.Length,
|
|
Name.AttributeName.MaximumLength ));
|
|
|
|
//
|
|
// Find out the attribute type specified
|
|
//
|
|
|
|
if (FlagOn( Name.FieldsPresent, ATTRIBUTE_TYPE_PRESENT_FLAG )) {
|
|
|
|
NtfsUpcaseName ( IrpContext->Vcb->UpcaseTable,
|
|
IrpContext->Vcb->UpcaseTableSize,
|
|
&Name.AttributeType );
|
|
TypeCode = NtfsGetAttributeTypeCode( IrpContext->Vcb, &Name.AttributeType );
|
|
|
|
} else {
|
|
|
|
TypeCode = Scb->AttributeTypeCode;
|
|
}
|
|
|
|
if (TypeCode != Scb->AttributeTypeCode) {
|
|
DebugTrace( 0, Dbg, ("Attribute types don't match %x - %x\n", Scb->AttributeTypeCode, TypeCode));
|
|
Status = STATUS_OBJECT_TYPE_MISMATCH;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Verify that the source stream is $DATA
|
|
//
|
|
|
|
if (Scb->AttributeTypeCode != $DATA) {
|
|
DebugTrace( 0, Dbg, ("Type code is illegal\n"));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Just to be non-orthogonal, we disallow renaming to default data stream
|
|
// on directories
|
|
//
|
|
|
|
if (TypeCode == $DATA &&
|
|
Name.AttributeName.Length == 0 &&
|
|
IsDirectory( &(Scb->Fcb->Info) )) {
|
|
DebugTrace( 0, Dbg, ("Cannot rename directory stream to ::$Data\n") );
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
|
|
//
|
|
// We have a valid source stream and a valid target name. Take the short cut
|
|
// if the names match. Yes, you could argue about sharing violation, or
|
|
// renaming to non-empty streams. We just claim success and let it go.
|
|
//
|
|
|
|
if (NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable,
|
|
&Scb->AttributeName, &Name.AttributeName,
|
|
TRUE )) {
|
|
DebugTrace( 0, Dbg, ("Names are the same\n"));
|
|
Status = STATUS_SUCCESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Open / Create the target stream and validate ReplaceIfExists.
|
|
//
|
|
|
|
Status = NtOfsCreateAttribute( IrpContext,
|
|
Fcb,
|
|
Name.AttributeName,
|
|
ReplaceIfExists ? CREATE_OR_OPEN : CREATE_NEW,
|
|
FALSE,
|
|
&TargetScb );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
DebugTrace( 0, Dbg, ("Unable to create target stream\n"));
|
|
leave;
|
|
}
|
|
|
|
if (TargetScb == Scb) {
|
|
DebugTrace( 0, Dbg, ("Somehow, you've got the same Scb\n"));
|
|
Status = STATUS_SUCCESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Verify that the target Scb is not in use nor has any allocation
|
|
// or data.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( TargetScb );
|
|
EmptyFile = TargetScb->Header.AllocationSize.QuadPart == 0;
|
|
NtfsReleaseFsrtlHeader( TargetScb );
|
|
|
|
if (!EmptyFile) {
|
|
DebugTrace( 0, Dbg, ("Target has allocation\n"));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
if (TargetScb->CleanupCount != 0 ||
|
|
!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)&Li0 )) {
|
|
DebugTrace( 0, Dbg, ("Target in use\n"));
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
EmptyFile = Scb->Header.AllocationSize.QuadPart == 0;
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
//
|
|
// We might end up with deallocating or even allocating an MFT
|
|
// record in the process of deleting the old stream. So, it's wise to preacquire
|
|
// QuotaControl resource, lest we deadlock with the MftScb resource.
|
|
//
|
|
|
|
if (NtfsPerformQuotaOperation( TargetScb->Fcb )) {
|
|
NtfsAcquireQuotaControl( IrpContext, TargetScb->Fcb->QuotaControl );
|
|
}
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
|
|
//
|
|
// Always force both streams to be non-resident. Then we will never have
|
|
// a conflict between the Scb and attribute. We don't want to take an
|
|
// empty non-resident stream and point it to a resident attribute.
|
|
// NOTE: we call with CreateSectionUnderWay set to true which forces
|
|
// the data to be flushed directly out to the new clusters. We need this
|
|
// because we explicitly move VDL a little later on.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
if (NtfsIsAttributeResident( NtfsFoundAttribute( &Context ))) {
|
|
NtfsConvertToNonresident( IrpContext,
|
|
Fcb,
|
|
NtfsFoundAttribute( &Context ),
|
|
TRUE,
|
|
&Context );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cache the old VDL presisted to disk so we can update it in the new location
|
|
//
|
|
|
|
OldValidDataLengthOnDisk = NtfsFoundAttribute( &Context )->Form.Nonresident.ValidDataLength;
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
|
|
|
|
if (FlagOn( TargetScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, TargetScb, NULL, &Context );
|
|
|
|
if (NtfsIsAttributeResident( NtfsFoundAttribute( &Context ))) {
|
|
NtfsConvertToNonresident( IrpContext,
|
|
Fcb,
|
|
NtfsFoundAttribute( &Context ),
|
|
TRUE,
|
|
&Context );
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
|
|
//
|
|
// Load all Mcb information for the source stream, we'll need it to generate the mapping.
|
|
//
|
|
|
|
(VOID)NtfsPreloadAllocation( IrpContext, Scb, 0, MAXLONGLONG );
|
|
|
|
//
|
|
// Make sure the attribute flags on the target match that on the source.
|
|
//
|
|
|
|
if (TargetScb->AttributeFlags != Scb->AttributeFlags) {
|
|
|
|
RestoreTargetScb = TargetScb;
|
|
TargetScbCompressionUnit = TargetScb->CompressionUnit;
|
|
TargetScbAttributeFlags = TargetScb->AttributeFlags;
|
|
TargetScbCompressionUnitShift = TargetScb->CompressionUnitShift;
|
|
|
|
NtfsModifyAttributeFlags( IrpContext, TargetScb, Scb->AttributeFlags );
|
|
}
|
|
|
|
//
|
|
// At this point, we have Scb to the source of
|
|
// the rename and a target Scb. The Source has all its allocation loaded
|
|
// and the target has no allocation. The only thing that really ties
|
|
// either Scb to the disk attribute is the AttributeName field. We swap
|
|
// the attribute names in order to swap them on disk.
|
|
//
|
|
|
|
Name.FileName = TargetScb->AttributeName;
|
|
TargetScb->AttributeName = Scb->AttributeName;
|
|
Scb->AttributeName = Name.FileName;
|
|
NamesSwapped = TRUE;
|
|
|
|
//
|
|
// If there is data in the source attribute
|
|
//
|
|
|
|
if (!EmptyFile) {
|
|
|
|
VCN AllocationClusters;
|
|
|
|
//
|
|
// Now, we bring the disk image of these attributes up to date with
|
|
// the Mcb information. First, add the allocation to the new attribute.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
|
|
AllocationClusters = LlClustersFromBytes( IrpContext->Vcb, Scb->Header.AllocationSize.QuadPart );
|
|
|
|
//
|
|
// Set the original scb to the target's size (This is really the target now)
|
|
//
|
|
|
|
ASSERT( Scb->ScbSnapshot != NULL );
|
|
Scb->Header.AllocationSize = TargetScb->Header.AllocationSize;
|
|
|
|
NtfsAddAttributeAllocation( IrpContext,
|
|
Scb,
|
|
&Context,
|
|
&Li0.QuadPart,
|
|
&AllocationClusters );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
|
|
//
|
|
// We've put the mapping into the new attribute record. However
|
|
// the valid data length in the record is probably zero. Update
|
|
// it to reflect the data in this stream already written to disk.
|
|
// Otherwise we may never update the data.
|
|
//
|
|
|
|
ASSERT( OldValidDataLengthOnDisk <= Scb->Header.FileSize.QuadPart );
|
|
|
|
NtfsVerifySizes( &Scb->Header );
|
|
NtfsWriteFileSizes( IrpContext,
|
|
Scb,
|
|
&OldValidDataLengthOnDisk,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
}
|
|
|
|
//
|
|
// Next, find all occurrences of the old attribute and delete them.
|
|
//
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, TargetScb, NULL, &Context );
|
|
|
|
do {
|
|
DebugDoit(
|
|
if (Count++ != 0) {
|
|
DebugTrace( 0, Dbg, ("Deleting attribute record %d\n", Count));
|
|
} else {
|
|
DebugTrace( 0, Dbg, ("%x Mcb's\n", Scb->Mcb.NtfsMcbArraySizeInUse ));
|
|
if (Scb->Mcb.NtfsMcbArray[0].NtfsMcbEntry != NULL) {
|
|
DebugTrace( 0, Dbg, ("First Mcb has %x entries\n",
|
|
Scb->Mcb.NtfsMcbArray[0].NtfsMcbEntry->LargeMcb.BaseMcb.PairCount ));
|
|
}
|
|
}
|
|
);
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
Fcb,
|
|
(DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD),
|
|
&Context );
|
|
} while (NtfsLookupNextAttributeForScb( IrpContext, TargetScb, &Context ));
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
|
|
//
|
|
// If we are renaming a stream on a file, we must make sure that
|
|
// there is a default data stream still on the file. Check the
|
|
// TargetScb to see if the name is for the default data stream
|
|
// and recreate the default data stream if we need to.
|
|
//
|
|
// We rely on the type code being $DATA and there being NO buffer
|
|
// in the attribute type name.
|
|
//
|
|
|
|
if (TypeCode == $DATA && TargetScb->AttributeName.Buffer == NULL) {
|
|
|
|
//
|
|
// Always create this stream non-resident in case the stream is encrypted.
|
|
//
|
|
|
|
NtfsAllocateAttribute( IrpContext,
|
|
TargetScb,
|
|
$DATA,
|
|
&TargetScb->AttributeName,
|
|
Scb->AttributeFlags,
|
|
TRUE,
|
|
TRUE,
|
|
0,
|
|
NULL );
|
|
|
|
} else {
|
|
ASSERT( !FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ));
|
|
}
|
|
|
|
//
|
|
// Checkpoint the transaction so we know we are done
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_STREAM_CHANGE );
|
|
NtfsCommitCurrentTransaction( IrpContext );
|
|
RestoreTargetScb = NULL;
|
|
|
|
|
|
//
|
|
// Let cleanup handle updating the standard information for the file
|
|
//
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
|
|
|
|
//
|
|
// If either Scb or TargetScb refers to the default data stream, then
|
|
// bring the Ccb, Scb, and Fcb flags and counts back into sync. This
|
|
// is due to the fact that all operations tied to the default data
|
|
// stream are believed to apply to the file as a whole and not
|
|
// simply to the stream.
|
|
//
|
|
|
|
if (FlagOn( TargetScb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
//
|
|
// We have renamed *TO* the default data stream. In this case we
|
|
// must mark all Ccb's for this stream as being CCB_FLAG_OPEN_AS_FILE
|
|
// and mark the Scb as the UNNAMED DATA stream
|
|
//
|
|
// Also, for each Ccb that is opened with DELETE access, we
|
|
// adjust the Fcb->FcbDeleteFile count and CCB_FLAG_DELETE_FILE.
|
|
//
|
|
|
|
PCCB ThisCcb;
|
|
|
|
DebugTrace( 0, Dbg, ("Renaming to default data stream\n"));
|
|
DebugTrace( 0, Dbg, ("Scanning Ccb's. FcbDeleteFile = %x\n", Fcb->FcbDeleteFile ));
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA );
|
|
|
|
for (ThisCcb = NtfsGetFirstCcbEntry( Scb );
|
|
ThisCcb != NULL;
|
|
ThisCcb = NtfsGetNextCcbEntry( Scb, ThisCcb )) {
|
|
|
|
DebugTrace( 0, Dbg, ("ThisCcb = %x\n", ThisCcb));
|
|
|
|
//
|
|
// Mark Ccb as being opened for the file as a whole
|
|
//
|
|
|
|
SetFlag( ThisCcb->Flags, CCB_FLAG_OPEN_AS_FILE );
|
|
|
|
//
|
|
// If deleted access was granted, then we need
|
|
// to adjust the FCB delete count
|
|
//
|
|
|
|
if (FlagOn( ThisCcb->Flags, CCB_FLAG_DELETE_ACCESS )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Found one\n" ));
|
|
Fcb->FcbDeleteFile += 1;
|
|
SetFlag( ThisCcb->Flags, CCB_FLAG_DELETE_FILE );
|
|
}
|
|
|
|
//
|
|
// If the stream was marked as delete-on-close,
|
|
// propagate that to the CCB
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE )) {
|
|
|
|
SetFlag( ThisCcb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the file size and allocation size in the Fcb Info field.
|
|
//
|
|
|
|
if (Fcb->Info.FileSize != Scb->Header.FileSize.QuadPart) {
|
|
|
|
Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
|
|
}
|
|
|
|
if (Fcb->Info.AllocatedLength != Scb->TotalAllocated) {
|
|
|
|
Fcb->Info.AllocatedLength = Scb->TotalAllocated;
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Done Ccb's. FcbDeleteFile = %x\n", Fcb->FcbDeleteFile ));
|
|
|
|
} else if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
//
|
|
// We have renamed *FROM* the default data stream. In this case we
|
|
// must unmark all Ccb's for this stream as being CCB_FLAG_OPEN_AS_FILE
|
|
//
|
|
// Also, for each Ccb that is opened with DELETE access, we
|
|
// adjust the Fcb->FcbDeleteFile count and CCB_FLAG_DELETE_FILE.
|
|
//
|
|
|
|
PCCB ThisCcb;
|
|
|
|
DebugTrace( 0, Dbg, ("Renaming from default data stream\n"));
|
|
DebugTrace( 0, Dbg, ("Scanning Ccb's. FcbDeleteFile = %x\n", Fcb->FcbDeleteFile ));
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA );
|
|
|
|
for (ThisCcb = NtfsGetFirstCcbEntry( Scb );
|
|
ThisCcb != NULL;
|
|
ThisCcb = NtfsGetNextCcbEntry( Scb, ThisCcb )) {
|
|
|
|
DebugTrace( 0, Dbg, ("ThisCcb = %x\n", ThisCcb));
|
|
|
|
//
|
|
// Unmark Ccb from representing the file as a whole.
|
|
//
|
|
|
|
ClearFlag( ThisCcb->Flags, CCB_FLAG_OPEN_AS_FILE );
|
|
|
|
//
|
|
// If deleted access was granted, then we need
|
|
// to unadjust the FCB delete count
|
|
//
|
|
|
|
if (FlagOn( ThisCcb->Flags, CCB_FLAG_DELETE_ACCESS )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Found one\n" ));
|
|
Fcb->FcbDeleteFile -= 1;
|
|
ClearFlag( ThisCcb->Flags, CCB_FLAG_DELETE_FILE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the file size and allocation size in the Fcb Info field.
|
|
//
|
|
|
|
if (Fcb->Info.FileSize != 0) {
|
|
|
|
Fcb->Info.FileSize = 0;
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
|
|
}
|
|
|
|
if (Fcb->Info.AllocatedLength != 0) {
|
|
|
|
Fcb->Info.AllocatedLength = 0;
|
|
SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Done Ccb's. FcbDeleteFile = %x\n", Fcb->FcbDeleteFile ));
|
|
}
|
|
|
|
//
|
|
// Set the Scb flag to indicate that the attribute is gone. Mark the
|
|
// Scb so it will never be returned.
|
|
//
|
|
|
|
TargetScb->ValidDataToDisk =
|
|
TargetScb->Header.AllocationSize.QuadPart =
|
|
TargetScb->Header.FileSize.QuadPart =
|
|
TargetScb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
TargetScb->AttributeTypeCode = $UNUSED;
|
|
SetFlag( TargetScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
|
|
} finally {
|
|
|
|
if (AbnormalTermination( )) {
|
|
|
|
//
|
|
// Restore the names.
|
|
//
|
|
|
|
if (NamesSwapped) {
|
|
Name.FileName = TargetScb->AttributeName;
|
|
TargetScb->AttributeName = Scb->AttributeName;
|
|
Scb->AttributeName = Name.FileName;
|
|
}
|
|
|
|
//
|
|
// Restore the Target Scb flags.
|
|
//
|
|
|
|
if (RestoreTargetScb) {
|
|
|
|
RestoreTargetScb->CompressionUnit = TargetScbCompressionUnit;
|
|
RestoreTargetScb->AttributeFlags = TargetScbAttributeFlags;
|
|
RestoreTargetScb->CompressionUnitShift = TargetScbCompressionUnitShift;
|
|
}
|
|
}
|
|
|
|
if (TargetScb != NULL) {
|
|
NtOfsCloseAttribute( IrpContext, TargetScb );
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
DebugTrace( -1, Dbg, ("NtfsStreamRename --> %x\n", Status) );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsCheckTreeForBatchOplocks (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PSCB DirectoryScb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks a directory tree and looks for batch oplocks which might
|
|
prevent the rename, link operation or short name operation from taking place.
|
|
|
|
This routine will release the Vcb if there are batch oplocks we are waiting on the
|
|
break for.
|
|
|
|
Arguments:
|
|
|
|
Irp - Irp for this request.
|
|
|
|
DirectoryScb - Scb for the root of the directory tree.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS of the operation. This routine can raise.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PSCB BatchOplockScb;
|
|
ULONG BatchOplockCount;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( NtfsIsSharedVcb( DirectoryScb->Vcb ));
|
|
|
|
Status = NtfsCheckScbForLinkRemoval( DirectoryScb, &BatchOplockScb, &BatchOplockCount );
|
|
|
|
//
|
|
// If STATUS_PENDING is returned then we need to check whether
|
|
// to break a batch oplock.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
|
|
//
|
|
// If the number of batch oplocks has grown then fail the request.
|
|
//
|
|
|
|
if ((Irp->IoStatus.Information != 0) &&
|
|
(BatchOplockCount >= Irp->IoStatus.Information)) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Remember the count of batch oplocks in the Irp and
|
|
// then call the oplock package.
|
|
//
|
|
|
|
Irp->IoStatus.Information = BatchOplockCount;
|
|
|
|
Status = FsRtlCheckOplock( &BatchOplockScb->ScbType.Data.Oplock,
|
|
Irp,
|
|
IrpContext,
|
|
NtfsOplockComplete,
|
|
NtfsPrePostIrp );
|
|
|
|
//
|
|
// If we got back success then raise CANT_WAIT to retry otherwise
|
|
// clean up.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|