From e611b132f9b8abe35b362e5870b74bce94a1e58e Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 16 May 2020 20:51:50 -0700 Subject: initial commit --- private/ntos/cntfs/fileinfo.c | 8307 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 8307 insertions(+) create mode 100644 private/ntos/cntfs/fileinfo.c (limited to 'private/ntos/cntfs/fileinfo.c') diff --git a/private/ntos/cntfs/fileinfo.c b/private/ntos/cntfs/fileinfo.c new file mode 100644 index 000000000..2f508a4fc --- /dev/null +++ b/private/ntos/cntfs/fileinfo.c @@ -0,0 +1,8307 @@ +/*++ + +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 PCCB Ccb, + 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 +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 PCCB Ccb, + 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 + ); + +NTSTATUS +NtfsSetLinkInfo ( + IN PIRP_CONTEXT IrpContext, + 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 +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 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 + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, NtfsCheckLinkForNewLink) +#pragma alloc_text(PAGE, NtfsCheckLinkForRename) +#pragma alloc_text(PAGE, NtfsCheckScbForLinkRemoval) +#pragma alloc_text(PAGE, NtfsCleanupLinkForRemoval) +#pragma alloc_text(PAGE, NtfsCommonQueryInformation) +#pragma alloc_text(PAGE, NtfsCommonSetInformation) +#pragma alloc_text(PAGE, NtfsFindTargetElements) +#pragma alloc_text(PAGE, NtfsFsdQueryInformation) +#pragma alloc_text(PAGE, NtfsFsdSetInformation) +#pragma alloc_text(PAGE, NtfsMoveLinkToNewDir) +#pragma alloc_text(PAGE, NtfsQueryAlternateNameInfo) +#pragma alloc_text(PAGE, NtfsQueryBasicInfo) +#pragma alloc_text(PAGE, NtfsQueryEaInfo) +#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, NtfsUpdateFcbFromLinkRemoval) +#pragma alloc_text(PAGE, NtfsUpdateFileDupInfo) +#endif + + +NTSTATUS +NtfsFsdQueryInformation ( + IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject, + IN PIRP Irp + ) + +/*++ + +Routine Description: + + This routine implements the FSD part of query 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; + + ASSERT_IRP( Irp ); + + UNREFERENCED_PARAMETER( VolumeDeviceObject ); + + PAGED_CODE(); + + DebugTrace( +1, Dbg, ("NtfsFsdQueryInformation\n") ); + + // + // Call the common query Information routine + // + + FsRtlEnterFileSystem(); + + ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE ); + + do { + + try { + + // + // We are either initiating this request or retrying it. + // + + if (IrpContext == NULL) { + + IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) ); + NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext ); + + } else if (Status == STATUS_LOG_FILE_FULL) { + + NtfsCheckpointForLogFileFull( IrpContext ); + } + + Status = NtfsCommonQueryInformation( IrpContext, Irp ); + break; + + } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) { + + // + // 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 + // + + Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() ); + } + + } while (Status == STATUS_CANT_WAIT || + Status == STATUS_LOG_FILE_FULL); + + if (ThreadTopLevelContext == &TopLevelContext) { + NtfsRestoreTopLevelIrp( ThreadTopLevelContext ); + } + + FsRtlExitFileSystem(); + + // + // And return to our caller + // + + DebugTrace( -1, Dbg, ("NtfsFsdQueryInformation -> %08lx\n", Status) ); + + return Status; +} + + +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 ); + + PAGED_CODE(); + + DebugTrace( +1, Dbg, ("NtfsFsdSetInformation\n") ); + + // + // Call the common set Information routine + // + + FsRtlEnterFileSystem(); + + ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE ); + + do { + + try { + + // + // We are either initiating this request or retrying it. + // + + if (IrpContext == NULL) { + + IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) ); + NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext ); + + } else if (Status == STATUS_LOG_FILE_FULL) { + + NtfsCheckpointForLogFileFull( IrpContext ); + + 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); + + if (ThreadTopLevelContext == &TopLevelContext) { + NtfsRestoreTopLevelIrp( ThreadTopLevelContext ); + } + + 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 OpenById = FALSE; + BOOLEAN FcbAcquired = FALSE; + BOOLEAN VcbAcquired = FALSE; + BOOLEAN FsRtlHeaderLocked = FALSE; + PFILE_ALL_INFORMATION AllInfo; + + ASSERT_IRP_CONTEXT( IrpContext ); + ASSERT_IRP( Irp ); + + 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: +#ifdef _CAIRO_ + case UserPropertySetOpen: +#endif // _CAIRO_ + case UserDirectoryOpen: + + if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) { + OpenById = TRUE; + } + + 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, FALSE ); + VcbAcquired = TRUE; + } + + if ((Scb->Header.PagingIoResource != NULL) && + + ((FileInformationClass == FileAllInformation) || + (FileInformationClass == FileStandardInformation) || + (FileInformationClass == FileCompressionInformation))) { + + ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE ); + + FsRtlLockFsRtlHeader( &Scb->Header ); + FsRtlHeaderLocked = TRUE; + } + + NtfsAcquireSharedFcb( IrpContext, Fcb, Scb, FALSE ); + FcbAcquired = TRUE; + + // + // Make sure the volume is still mounted. We need to test this + // with the Fcb acquired. + // + + if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) { + + 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: + + // + // This is illegal for the open by Id case. + // + + if (OpenById) { + + Status = STATUS_INVALID_PARAMETER; + break; + } + + // + // 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, Ccb, &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, Ccb, 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 FilePositionInformation: + + NtfsQueryPositionInfo( IrpContext, FileObject, Scb, Buffer, &Length ); + break; + + case FileNameInformation: + + // + // This is illegal for the open by Id case. + // + + if (OpenById) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + Status = NtfsQueryNameInfo( IrpContext, FileObject, Scb, Buffer, &Length, Ccb ); + } + + break; + + case FileAlternateNameInformation: + + // + // This is illegal for the open by Id case. + // + + if (OpenById) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + 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, Ccb, 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 ); + ExReleaseResource( 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 ); + + 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) { + + NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER ); + + DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> STATUS_INVALID_PARAMETER\n") ); + return STATUS_INVALID_PARAMETER; + } + + 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)) && + (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. + // + + if (FileInformationClass == 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->Flags, IRP_CONTEXT_FLAG_WAIT ); + ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_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->ScbState, SCB_STATE_COMPRESSED ) && + (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->Flags, 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) || + !NtfsIsExclusiveResource( Scb->Header.PagingIoResource )) { + + SetFlag( IrpContext->Flags, WaitState ); + + NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE ); + VcbAcquired = TRUE; + } + } + + SetFlag( IrpContext->Flags, WaitState ); + } + + // + // Acquire the Vcb shared for changes to allocation or basic + // information. + // + + } else if ((FileInformationClass == FileAllocationInformation) || + (FileInformationClass == FileBasicInformation) || + (FileInformationClass == FileDispositionInformation)) { + + NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE ); + VcbAcquired = TRUE; + + // + // If this is a rename or link operation then we need to make sure + // we have the user's context and acquire the Vcb. + // + + } else if ((FileInformationClass == FileRenameInformation) || + (FileInformationClass == FileLinkInformation)) { + + if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_SECURITY )) { + + IrpContext->Union.SubjectContext = NtfsAllocatePool( PagedPool, + sizeof( SECURITY_SUBJECT_CONTEXT )); + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_SECURITY ); + + SeCaptureSubjectContext( IrpContext->Union.SubjectContext ); + } + + if (IsDirectory( &Fcb->Info )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX ); + } + + if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { + + NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE ); + + } else { + + NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE ); + } + + VcbAcquired = TRUE; + } + + // + // 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) { + + ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE ); + ReleaseScbPaging = TRUE; + } + + FsRtlLockFsRtlHeader( &Scb->Header ); + IrpContext->FcbWithPagingExclusive = (PFCB)Scb; + + // + // Anyone potentially shrinking/deleting allocation must get the paging I/O + // resource first. Also acquire this in the rename path to lock the + // mapped page writer out of this file. + // + + } else if ((Scb->Header.PagingIoResource != NULL) && + ((FileInformationClass == FileEndOfFileInformation) || + (FileInformationClass == FileAllocationInformation) || + (FileInformationClass == FileRenameInformation) || + (FileInformationClass == FileLinkInformation))) { + + 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, FALSE, FALSE ); + + // + // 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 the volume is dismounted. + // + + if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) { + + NtfsRaiseStatus( IrpContext, STATUS_FILE_INVALID, 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 ); + break; + + case FilePositionInformation: + + Status = NtfsSetPositionInfo( IrpContext, FileObject, Irp, Scb ); + break; + + case FileLinkInformation: + + Status = NtfsSetLinkInfo( IrpContext, Irp, Vcb, Scb, Ccb ); + break; + + case FileAllocationInformation: + + if (TypeOfOpen == UserDirectoryOpen) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + Status = NtfsSetAllocationInfo( IrpContext, FileObject, Irp, Scb, Ccb ); + } + + break; + + case FileEndOfFileInformation: + + if (TypeOfOpen == UserDirectoryOpen) { + + Status = STATUS_INVALID_PARAMETER; + + } else { + + Status = NtfsSetEndOfFileInfo( IrpContext, FileObject, Irp, Scb, Ccb, VcbAcquired ); + } + + 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) { + + ExReleaseResource( Scb->Header.PagingIoResource ); + } + + if (Status != STATUS_PENDING) { + + if (VcbAcquired) { + + NtfsReleaseVcb( IrpContext, Vcb ); + } + + // + // Complete the request unless it is being done in the oplock + // package. + // + + if (!AbnormalTermination()) { + + NtfsCompleteRequest( &IrpContext, &Irp, Status ); + } + } + + DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> %08lx\n", Status) ); + } + + return Status; +} + + +// +// Internal Support Routine +// + +VOID +NtfsQueryBasicInfo ( + IN PIRP_CONTEXT IrpContext, + IN PFILE_OBJECT FileObject, + IN PSCB Scb, + IN PCCB Ccb, + 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 + + Ccb - Supplies the Ccb for this handle + + 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, ("NtfsQueryBasicInfo...\n") ); + + Fcb = Scb->Fcb; + + // + // Zero the output buffer and update the length. + // + + RtlZeroMemory( Buffer, sizeof(FILE_BASIC_INFORMATION) ); + + *Length -= sizeof( FILE_BASIC_INFORMATION ); + + // + // Copy over the time information + // + + Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime; + Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime; + Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime; + + Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess; + + // + // 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 ); + + if (IsDirectory( &Fcb->Info ) + && FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) { + + SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY ); + } + + // + // If this is not the main stream on the file then use the stream based + // compressed bit. + // + + if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) { + + 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; + } + + // + // 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") ); + + // + // Zero out the output buffer and update the length field. + // + + RtlZeroMemory( Buffer, sizeof(FILE_STANDARD_INFORMATION) ); + + *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 ); + } + + // + // Both the allocation and file size is in the scb header + // + + Buffer->AllocationSize.QuadPart = Scb->TotalAllocated; + Buffer->EndOfFile = Scb->Header.FileSize; + Buffer->NumberOfLinks = Scb->Fcb->LinkCount; + + // + // Get the delete and directory flags from the Fcb/Scb state. Note that + // the sense of the delete pending bit refers to the file if opened as + // file. Otherwise it refers to the attribute only. + // + // But only do the test if the Ccb has been supplied. + // + + if (ARGUMENT_PRESENT(Ccb)) { + + if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) { + + if (Scb->Fcb->LinkCount == 0 || + (Ccb->Lcb != NULL && + FlagOn( Ccb->Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE ))) { + + Buffer->DeletePending = TRUE; + } + + Buffer->Directory = BooleanIsDirectory( &Scb->Fcb->Info ); + + } else { + + Buffer->DeletePending = BooleanFlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE ); + } + + } else { + + Buffer->Directory = BooleanIsDirectory( &Scb->Fcb->Info ); + } + + // + // 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 ); + + 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 +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; + + 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 = &NtfsSystemFiles[ Scb->Fcb->FileReference.SegmentNumberLowPart ]; + + } else { + + NtfsBuildNormalizedName( IrpContext, Scb, &NormalizedName ); + SourceName = &NormalizedName; + } + + } else { + + 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. + // + + if (*Length >= Buffer->FileNameLength) { + + Status = STATUS_SUCCESS; + + } else { + + Status = STATUS_BUFFER_OVERFLOW; + Buffer->FileNameLength = *Length; + } + + // + // Update the Length + // + + *Length -= Buffer->FileNameLength; + + // + // Copy over the file name + // + + if (SourceName->Length <= Buffer->FileNameLength) { + + BytesToCopy = SourceName->Length; + + } else { + + BytesToCopy = Buffer->FileNameLength; + } + + if (BytesToCopy) { + + RtlCopyMemory( &Buffer->FileName[0], + SourceName->Buffer, + BytesToCopy ); + } + + BytesToCopy = Buffer->FileNameLength - 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( &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 { + + while (TRUE) { + + 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") ); + + try_return( Status = STATUS_BUFFER_OVERFLOW ); + } + + // + // 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 ); + } + + // + // We've finished enumerating an attribute type code. Check + // to see if we should advance to the next enumeration type. + // + +#ifndef _CAIRO + break; +#else // _CAIRO_ + if (TypeCode == $PROPERTY_SET) { + break; + } else { + + NtfsCleanupAttributeContext( &AttrContext ); + TypeCode = $PROPERTY_SET; + } +#endif // _CAIRO_ + } + + try_exit: NOTHING; + } finally { + + DebugUnwind( NtfsQueryStreamsInfo ); + + NtfsCleanupAttributeContext( &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( Scb, + NtfsFoundAttribute( &Context )); + + } finally { + + NtfsCleanupAttributeContext( &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 PCCB Ccb, + 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 + + Ccb - Supplies the Ccb for this handle + + 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 ); + } + + // + // Zero the output buffer and update the length. + // + + RtlZeroMemory( Buffer, sizeof(FILE_NETWORK_OPEN_INFORMATION) ); + + *Length -= sizeof( FILE_NETWORK_OPEN_INFORMATION ); + + // + // Copy over the time information + // + + Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime; + Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime; + Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime; + + Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess; + + // + // Both the allocation and file size are in the scb header + // + + Buffer->AllocationSize.QuadPart = Scb->TotalAllocated; + Buffer->EndOfFile.QuadPart = Scb->Header.FileSize.QuadPart; + + // + // 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 ); + + if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) { + + if (IsDirectory( &Fcb->Info )) { + + SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY ); + + // + // Set the sizes back to zero for a directory. + // + + Buffer->AllocationSize.QuadPart = + Buffer->EndOfFile.QuadPart = 0; + } + + // + // 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; + } + + // + // 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; + + PFILE_BASIC_INFORMATION Buffer; + + 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; + + // + // 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 | + FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_RESERVED0 | + FILE_ATTRIBUTE_RESERVED1 | + FILE_ATTRIBUTE_COMPRESSED) ); + + // + // Update the attributes in the Fcb if this is a change to the file. + // + + Fcb->Info.FileAttributes = (Fcb->Info.FileAttributes & + (FILE_ATTRIBUTE_COMPRESSED | + FILE_ATTRIBUTE_DIRECTORY | + DUP_FILE_NAME_INDEX_PRESENT)) | + Buffer->FileAttributes; + + 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; + } + } + + // + // 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) { + + 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) { + + 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) { + + 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) { + + 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 )) { + + NtfsUpdateStandardInformation( IrpContext, Fcb ); + + if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) { + + NtfsWriteFileSizes( IrpContext, + Scb, + &Scb->Header.ValidDataLength.QuadPart, + FALSE, + TRUE + ); + + 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; + 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. + // + + Lcb = Ccb->Lcb; + + if (Lcb == NULL) { + + 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 ((Scb == Scb->Vcb->MftScb) || + (Scb == Scb->Vcb->Mft2Scb) || + (Scb == Scb->Vcb->LogFileScb) || + (Scb == Scb->Vcb->VolumeDasdScb) || + (Scb == Scb->Vcb->AttributeDefTableScb) || + (Scb == Scb->Vcb->UpcaseTableScb) || + (Scb == Scb->Vcb->RootIndexScb) || + (Scb == Scb->Vcb->BitmapScb) || + (Scb == Scb->Vcb->BadClusterFileScb) || + (Scb == Scb->Vcb->QuotaTableScb) || + (Scb == Scb->Vcb->MftBitmapScb)) { + + DebugTrace( 0, Dbg, ("Scb is one of the special system files\n") ); + + try_return( Status = STATUS_CANNOT_DELETE ); + } + + // + // 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 ((BOOLEAN)!LcbLinkIsDeleted( Lcb ) + && (BOOLEAN)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 )) { + + FsRtlNotifyFullChangeDirectory( Scb->Vcb->NotifySync, + &Scb->Vcb->DirNotifyList, + FileObject->FsContext, + NULL, + FALSE, + FALSE, + 0, + 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; + + // + // Only do the auditing if we have a user handle. + // + + if (FileHandle != NULL) { + + Status = ObQueryObjectAuditingByHandle( FileHandle, + &GenerateOnClose ); + + // + // If we have a valid handle, perform the audit. + // + + if (NT_SUCCESS( Status ) && 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 + ) + +/*++ + +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; + + PFCB TargetLinkFcb = NULL; + BOOLEAN ExistingTargetLinkFcb; + BOOLEAN AcquiredTargetLinkFcb = FALSE; + USHORT TargetLinkFcbCountAdj = 0; + + BOOLEAN AcquiredFcbTable = FALSE; + PERESOURCE ResourceToRelease = 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; + + PLIST_ENTRY Links; + PSCB ThisScb; + + NAME_PAIR NamePair; + LONGLONG TunneledCreationTime; + ULONG TunneledDataSize; + BOOLEAN HaveTunneledInformation = FALSE; + + ASSERT_IRP_CONTEXT( IrpContext ); + ASSERT_FILE_OBJECT( FileObject ); + ASSERT_IRP( Irp ); + ASSERT_SCB( Scb ); + + PAGED_CODE (); + + DebugTrace( +1, Dbg, ("NtfsSetRenameInfo...\n") ); + + // + // 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) || + (NtfsSegmentNumber( &Fcb->FileReference ) < FIRST_USER_FILE_NUMBER)) { + + 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->Flags, IRP_CONTEXT_FLAG_WAIT )) { + + Status = NtfsPostRequest( IrpContext, Irp ); + + DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Can't wait\n") ); + return Status; + } + + // + // Use a try-finally to facilitate cleanup. + // + + try { + + // + // Initialize the local variables. + // + + ParentScb = Lcb->Scb; + TargetFileObject = IrpSp->Parameters.SetFile.FileObject; + + 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 )) { + + PSCB BatchOplockScb; + ULONG BatchOplockCount; + + Status = NtfsCheckScbForLinkRemoval( Scb, &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; + leave; + } + + // + // 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 ); + + } else if (Status == STATUS_PENDING) { + + NtfsReleaseVcb( IrpContext, Vcb ); + } + + leave; + + } else if (!NT_SUCCESS( Status )) { + + 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.Buffer == NULL) || + (ParentScb->ScbType.Index.NormalizedName.Length == 0)) { + + NtfsBuildNormalizedName( IrpContext, + 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.Buffer == NULL) || + (Scb->ScbType.Index.NormalizedName.Length == 0))) { + + NtfsUpdateNormalizedName( IrpContext, + ParentScb, + Scb, + NULL, + 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->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { + + if (!NtfsAcquireExclusiveFcb( IrpContext, + TargetParentScb->Fcb, + TargetParentScb, + FALSE, + TRUE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_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 ); + + // + // 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 ); + + // + // We need to acquire this file carefully in the event that we don't hold + // the Vcb exclusively. + // + + if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { + + if (TargetLinkFcb->PagingIoResource != NULL) { + + if (!ExAcquireResourceExclusive( TargetLinkFcb->PagingIoResource, FALSE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX ); + NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); + } + + ResourceToRelease = TargetLinkFcb->PagingIoResource; + } + + if (!NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, FALSE, TRUE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_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. + // + + if (TargetLinkFcb->PagingIoResource != NULL) { + ResourceToRelease = TargetLinkFcb->PagingIoResource; + ExAcquireResourceExclusive( ResourceToRelease, TRUE ); + } + + NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, FALSE, FALSE ); + } + + 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, + TargetParentScb->Fcb, + NULL ); + + NtfsConditionallyFixupQuota( IrpContext, TargetLinkFcb ); + + } + + // + // 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 )) { + + NtfsReleaseVcb( IrpContext, Vcb ); + + Status = FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock, + Irp, + IrpContext, + NtfsOplockComplete, + NtfsPrePostIrp ); + + break; + } + } + + leave; + } + + NtfsCleanupLinkForRemoval( TargetLinkFcb, ExistingTargetLinkFcb ); + + if (TargetLinkFcb->LinkCount == 1) { + + NtfsDeleteFile( IrpContext, + TargetLinkFcb, + TargetParentScb, + NULL ); + + TargetLinkFcbCountAdj += 1; + + } else { + + NtfsRemoveLink( IrpContext, + TargetLinkFcb, + TargetParentScb, + PrevLinkName, + NULL ); + + TargetLinkFcbCountAdj += 1; + NtfsUpdateFcb( TargetLinkFcb ); + } + + // + // 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 { + + TargetLinkFcb = Fcb; + NtfsRemoveLink( IrpContext, + Fcb, + TargetParentScb, + PrevLinkName, + NULL ); + + FcbLinkCountAdj += 1; + } + } + } + + NtfsUnpinBcb( &IndexEntryBcb ); + + // + // 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 )) { + + NtfsRemoveLink( IrpContext, + Fcb, + ParentScb, + Lcb->ExactCaseLink.LinkName, + &NamePair ); + + // + // 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 + // + + FsRtlAddToTunnelCache( &Vcb->Tunnel, + *(PULONGLONG)&ParentScb->Fcb->FileReference, + &NamePair.Short, + &NamePair.Long, + BooleanFlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS ), + sizeof( LONGLONG ), + &Fcb->Info.CreationTime ); + } + } + + 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) ); + + // + // Grunge the tunnel cache for property restoration + // + + if (!IsDirectory( &Fcb->Info ) && + !FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) { + + NtfsResetNamePair( &NamePair ); + TunneledDataSize = sizeof( LONGLONG ); + + if (FsRtlFindInTunnelCache( &Vcb->Tunnel, + *(PULONGLONG)&TargetParentScb->Fcb->FileReference, + &NewLinkName, + &NamePair.Short, + &NamePair.Long, + &TunneledDataSize, + &TunneledCreationTime)) { + + ASSERT( TunneledDataSize == sizeof( LONGLONG )); + HaveTunneledInformation = 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 ); + + // + // Restore timestamps on tunneled files + // + + if (HaveTunneledInformation) { + + Fcb->Info.CreationTime = TunneledCreationTime; + 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 ); + } + + // + // 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; + } + + // + // 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 ); + } + + // + // 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 ); + } + + // + // Nothing should fail from this point forward. + // + // Now make the change to the normalized name. The buffer should be + // large enough. + // + + if (IsDirectory( &Fcb->Info ) && FlagOn( RenameFlags, ADD_TARGET_LINK )) { + + NtfsUpdateNormalizedName( IrpContext, + TargetParentScb, + Scb, + FileNameAttr, + 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 ); + + // + // If the link count is going to 0, we need to perform the work of + // removing the file. + // + + if (TargetLinkFcb->LinkCount == 1) { + + SetFlag( TargetLinkFcb->FcbState, FCB_STATE_FILE_DELETED ); + + // + // Remove this from the Fcb table if in it. + // + + if (FlagOn( TargetLinkFcb->FcbState, FCB_STATE_IN_FCB_TABLE )) { + + NtfsAcquireFcbTable( IrpContext, Vcb ); + AcquiredFcbTable = TRUE; + + NtfsDeleteFcbTableEntry( Vcb, TargetLinkFcb->FileReference ); + + NtfsReleaseFcbTable( IrpContext, Vcb ); + AcquiredFcbTable = FALSE; + + ClearFlag( TargetLinkFcb->FcbState, FCB_STATE_IN_FCB_TABLE ); + } + + // + // We need to mark all of the Scbs as gone. + // + + for (Links = TargetLinkFcb->ScbQueue.Flink; + Links != &TargetLinkFcb->ScbQueue; + Links = Links->Flink) { + + ThisScb = CONTAINING_RECORD( Links, + SCB, + FcbLinks ); + + SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ); + } + } + } + + // + // Change the time stamps in the parent if we modified the links in this directory. + // + + if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) { + + NtfsUpdateFcb( ParentScb->Fcb ); + } + + // + // 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 ); + + // + // 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.Buffer != NULL ? + &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.Buffer != NULL ? + &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.Buffer != NULL ? + &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; + + // + // Now go through and mark everything as deleted. + // + + if (TargetLinkFcb->LinkCount == 0) { + + SetFlag( TargetLinkFcb->FcbState, FCB_STATE_FILE_DELETED ); + + // + // We need to mark all of the Scbs as gone. + // + + for (Links = TargetLinkFcb->ScbQueue.Flink; + Links != &TargetLinkFcb->ScbQueue; + Links = Links->Flink) { + + ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks ); + + if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) { + + NtfsSnapshotScb( IrpContext, ThisScb ); + + ThisScb->ValidDataToDisk = + ThisScb->Header.AllocationSize.QuadPart = + ThisScb->Header.FileSize.QuadPart = + ThisScb->Header.ValidDataLength.QuadPart = 0; + + SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ); + } + } + } + } + + Fcb->TotalLinks -= FcbLinkCountAdj; + Fcb->LinkCount -= FcbLinkCountAdj; + + } finally { + + DebugUnwind( NtfsSetRenameInfo ); + + if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); } + if (ResourceToRelease != NULL) { ExReleaseResource( ResourceToRelease ); } + NtfsUnpinBcb( &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 buffer for the tunneled names, deallocate them now. + // + + if (NamePair.Long.Buffer != NamePair.LongBuffer) { + + NtfsFreePool( NamePair.Long.Buffer ); + } + + // + // If we allocated a file name attribute, we deallocate it now. + // + + if (FileNameAttr != NULL) { NtfsFreePool( FileNameAttr ); } + + // + // 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, + FALSE, + 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 + ) + +/*++ + +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; + + 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; + PERESOURCE ResourceToRelease = NULL; + + BOOLEAN ReportDirNotify = FALSE; + PWCHAR NextChar; + + PINDEX_ENTRY IndexEntry; + PBCB IndexEntryBcb = NULL; + + PLIST_ENTRY Links; + PSCB ThisScb; + + 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 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 ((Lcb != NULL) && 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 (NtfsSegmentNumber( &Fcb->FileReference ) < FIRST_USER_FILE_NUMBER) { + + Status = STATUS_INVALID_PARAMETER; + DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) ); + return Status; + } + + // + // Verify that we can wait. + // + + if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) { + + Status = NtfsPostRequest( IrpContext, Irp ); + + if (Status == STATUS_PENDING) { + + NtfsReleaseVcb( IrpContext, Vcb ); + } + + 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; + } + + // + // Use a try-finally to facilitate cleanup. + // + + try { + + // + // 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.Buffer == NULL) || + (ParentScb->ScbType.Index.NormalizedName.Length == 0)) { + + NtfsBuildNormalizedName( IrpContext, + 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->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { + + if (!NtfsAcquireExclusiveFcb( IrpContext, + TargetParentScb->Fcb, + TargetParentScb, + FALSE, + TRUE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_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 ); + + // + // 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 { + + PrevLinkName.Buffer = NtfsAllocatePool( PagedPool, 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 ); + + // + // We need to acquire this file carefully in the event that we don't hold + // the Vcb exclusively. + // + + if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) { + + if (PreviousFcb->PagingIoResource != NULL) { + + if (!ExAcquireResourceExclusive( PreviousFcb->PagingIoResource, FALSE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX ); + NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL ); + } + + ResourceToRelease = PreviousFcb->PagingIoResource; + } + + if (!NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, FALSE, TRUE )) { + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_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. + // + + if (PreviousFcb->PagingIoResource != NULL) { + ResourceToRelease = PreviousFcb->PagingIoResource; + ExAcquireResourceExclusive( ResourceToRelease, TRUE ); + } + + NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, FALSE, FALSE ); + } + + // + // 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, + TargetParentScb->Fcb, + NULL ); + + 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. + // + + NtfsReleaseVcb( IrpContext, Vcb ); + + 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, + ExistingPrevFcb ); + + // + // If the link count on this file is 1, then delete the file. Otherwise just + // delete the link. + // + + if (PreviousFcb->LinkCount == 1) { + + NtfsDeleteFile( IrpContext, + PreviousFcb, + TargetParentScb, + NULL ); + + PrevFcbLinkCountAdj += 1; + + } else { + + NtfsRemoveLink( IrpContext, + PreviousFcb, + TargetParentScb, + PrevLinkName, + NULL ); + + PrevFcbLinkCountAdj += 1; + NtfsUpdateFcb( PreviousFcb ); + } + + // + // 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 ); + + 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( &IndexEntryBcb ); + + // + // Check that we have permission to add a file to this directory. + // + + NtfsCheckIndexForAddOrDelete( IrpContext, + TargetParentScb->Fcb, + FILE_ADD_FILE ); + + // + // 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 ); + + // + // 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 ); + + LinkCountAdj -= 1; + NtfsUpdateFcb( TargetParentScb->Fcb ); + + // + // 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 ); + } + } + + // + // 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; + + // + // Now go through and mark everything as deleted. + // + + if (PreviousFcb->LinkCount == 0) { + + SetFlag( PreviousFcb->FcbState, FCB_STATE_FILE_DELETED ); + +#ifdef _CAIRO_ + + // + // 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 ); + } + +#endif // _CAIRO_ + + // + // We need to mark all of the Scbs as gone. + // + + for (Links = PreviousFcb->ScbQueue.Flink; + Links != &PreviousFcb->ScbQueue; + Links = Links->Flink) { + + ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks ); + + if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) { + + NtfsSnapshotScb( IrpContext, ThisScb ); + + ThisScb->ValidDataToDisk = + ThisScb->Header.AllocationSize.QuadPart = + ThisScb->Header.FileSize.QuadPart = + ThisScb->Header.ValidDataLength.QuadPart = 0; + + SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ); + } + } + } + } + + } finally { + + DebugUnwind( NtfsSetLinkInfo ); + + 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 (ResourceToRelease != NULL) { ExReleaseResource( ResourceToRelease ); } + + // + // If we allocated a file name attribute, we deallocate it now. + // + + if (NewLinkNameAttr != NULL) { NtfsFreePool( NewLinkNameAttr ); } + + // + // If we have the Fcb for a removed link and it didn't previously + // exist, call our teardown routine. + // + + if (Status != STATUS_PENDING) { + + if ((PreviousFcb != NULL) && + (PreviousFcb->CleanupCount == 0)) { + + NtfsTeardownStructures( IrpContext, + PreviousFcb, + NULL, + FALSE, + FALSE, + NULL ); + } + } + + NtfsUnpinBcb( &IndexEntryBcb ); + + DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: 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; + + try { + + // + // Check if the file does not use intermediate buffering. If it does + // not use intermediate buffering then the new position we're supplied + // must be aligned properly for the device + // + + if (FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING )) { + + PDEVICE_OBJECT DeviceObject; + + DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject; + + if ((Buffer->CurrentByteOffset.LowPart & DeviceObject->AlignmentRequirement) != 0) { + + DebugTrace( 0, Dbg, ("Offset missaligned %08lx %08lx\n", Buffer->CurrentByteOffset.LowPart, Buffer->CurrentByteOffset.HighPart) ); + + try_return( Status = STATUS_INVALID_PARAMETER ); + } + } + + // + // Set the new current byte offset in the file object + // + + FileObject->CurrentByteOffset = Buffer->CurrentByteOffset; + + Status = STATUS_SUCCESS; + + try_exit: NOTHING; + } finally { + + DebugUnwind( NtfsSetPositionInfo ); + + NOTHING; + } + + // + // And return to our caller + // + + DebugTrace( -1, Dbg, ("NtfsSetPositionInfo -> %08lx\n", Status) ); + + return Status; +} + + +// +// 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 UpdateCacheManager = FALSE; + BOOLEAN ClearCheckSizeFlag = FALSE; + + LONGLONG NewAllocationSize; + LONGLONG PrevAllocationSize; + + LONGLONG CurrentTime; + + 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") ); + + // + // 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 ); + } + + // + // 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; + + // + // If we will be decreasing file size, grab the paging io resource + // exclusive, if there is one. + // + + if (NewAllocationSize < Scb->Header.FileSize.QuadPart) { + + // + // Check if there is a user mapped file which could prevent truncation. + // + + if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer, + (PLARGE_INTEGER)&NewAllocationSize )) { + + Status = STATUS_USER_MAPPED_FILE; + DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo -> %08lx\n", Status) ); + + return Status; + } + } + + // + // Use a try-finally so we can update the on disk time-stamps. + // + + try { + + // + // 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 ), + BooleanFlagOn(Irp->Flags, IRP_PAGING_IO), + &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, + BooleanFlagOn(Irp->Flags, IRP_PAGING_IO), + FALSE, + &AttrContext ); + + NtfsCleanupAttributeContext( &AttrContext ); + CleanupAttrContext = FALSE; + + // + // Now update the sizes in the Scb. + // + + Scb->Header.AllocationSize.LowPart = + Scb->Header.FileSize.LowPart = + Scb->Header.ValidDataLength.LowPart = (ULONG) NewAllocationSize; + + Scb->TotalAllocated = NewAllocationSize; + + // + // Remember to update the cache manager. + // + + UpdateCacheManager = TRUE; + } + + } 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) { + + NtfsAddAllocation( IrpContext, + FileObject, + Scb, + LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ), + LlClustersFromBytes( Scb->Vcb, NewAllocationSize - Scb->Header.AllocationSize.QuadPart ), + FALSE ); + + // + // 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) { + + 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 (UpdateCacheManager) { + + // + // We want to checkpoint the transaction if there is one active. + // + + if (IrpContext->TransactionId != 0) { + + NtfsCheckpointCurrentTransaction( IrpContext ); + } + + // + // 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 kernel mode in case + // mm is in the process of creating a section. + // + + if (!CcIsFileCached(FileObject) && + !FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) && + !FlagOn(Irp->Flags, IRP_PAGING_IO)) { + NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE ); + } + + // + // Only call if the file is cached now, because the other case + // may cause recursion in write! + + if (CcIsFileCached(FileObject)) { + CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); + } + + // + // 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 ); + } + } + + Status = STATUS_SUCCESS; + + } finally { + + DebugUnwind( NtfsSetAllocation ); + + if (CleanupAttrContext) { + + NtfsCleanupAttributeContext( &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; + + 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) { + + // + // 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; + + // + // We can always move the valid data length in the Scb up to valid data + // on disk for this call back. Otherwise we may lose data in a mapped + // file if a user does a cached write to the middle of a page. + // For the typical case, Scb valid data length and file size are + // equal so no adjustment is necessary. + // + + if ((Scb->Header.ValidDataLength.QuadPart < NewFileSize) && + (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) && + (Scb->Header.ValidDataLength.QuadPart < Scb->ValidDataToDisk)) { + + // + // Set the valid data length to the smaller of ValidDataToDisk + // or file size. + // + + if (Scb->ValidDataToDisk < NewFileSize) { + + NewValidDataLength = Scb->ValidDataToDisk; + + } else { + + NewValidDataLength = NewFileSize; + } + + ExAcquireFastMutex( Scb->Header.FastMutex ); + + Scb->Header.ValidDataLength.QuadPart = NewValidDataLength; + ExReleaseFastMutex( Scb->Header.FastMutex ); + } + + NtfsWriteFileSizes( IrpContext, + Scb, + &NewValidDataLength, + 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 { + + // + // Check if we really are changing the file size. + // + + if (Scb->Header.FileSize.QuadPart != NewFileSize) { + + FileSizeChanged = TRUE; + + } + + // + // Check if we are shrinking a mapped file in the non-lazywriter case. MM + // will tell us if someone currently has the file mapped. + // + + if ((NewFileSize < Scb->Header.FileSize.QuadPart) && + !MmCanFileBeTruncated( FileObject->SectionObjectPointer, + (PLARGE_INTEGER)&NewFileSize )) { + + Status = STATUS_USER_MAPPED_FILE; + DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) ); + + return Status; + } + +#ifdef _CAIRO_ + + // + // If this is a change file size call after the stream has been cleaned + // up the fix the quota now. + // + + if (FileSizeChanged && + Scb->CleanupCount == 0 && + NtfsPerformQuotaOperation( Scb->Fcb)) { + + LONGLONG Delta = NewFileSize - Scb->Header.FileSize.QuadPart; + + ASSERT(!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED )); + ASSERT(!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )); + ASSERT( FALSE ); + NtfsUpdateFileQuota( IrpContext, + Scb->Fcb, + &Delta, + TRUE, + FALSE ); + + SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ); + } + +#endif // _CAIRO_ + + // + // 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; + + 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 ); + + // + // Either convert or change the attribute value. + // + + if (NewFileSize >= Scb->Vcb->BytesPerFileRecordSegment) { + + NtfsConvertToNonresident( IrpContext, + Fcb, + NtfsFoundAttribute( &AttrContext ), + BooleanFlagOn(Irp->Flags, IRP_PAGING_IO), + &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, + BooleanFlagOn(Irp->Flags, IRP_PAGING_IO), + 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; + } + + NonResidentPath = FALSE; + } + + } finally { + + NtfsCleanupAttributeContext( &AttrContext ); + } + + } else { + + NonResidentPath = FALSE; + } + } + + // + // 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 && + !CcIsFileCached(FileObject) && + !FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) && + !FlagOn(Irp->Flags, IRP_PAGING_IO)) { + + NtfsCreateInternalAttributeStream( IrpContext, Scb, 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 the allocation. + // + + NtfsAddAllocation( IrpContext, + FileObject, + Scb, + LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ), + LlClustersFromBytes(Scb->Vcb, (NewFileSize - Scb->Header.AllocationSize.QuadPart)), + FALSE ); + } + + 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->ValidDataToDisk = + 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; + } + + if (NewFileSize < Scb->ValidDataToDisk) { + + Scb->ValidDataToDisk = NewFileSize; + } + + 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 ); + + NtfsWriteFileSizes( IrpContext, + Scb, + &Scb->Header.ValidDataLength.QuadPart, + BooleanFlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ), + 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 ); + } + + // + // Only call if the file is cached now, because the other case + // may cause recursion in write! + + if (CcIsFileCached(FileObject)) { + + // + // We want to checkpoint the transaction if there is one active. + // + + if (IrpContext->TransactionId != 0) { + + NtfsCheckpointCurrentTransaction( IrpContext ); + } + + CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); + } + + // + // 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 ); + } + } + + Status = STATUS_SUCCESS; + + DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) ); + + return Status; +} + + +// +// 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. + // + + if (NextScb->Fcb->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 / 2] == 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 BOOLEAN ExistingFcb + ) + +/*++ + +Routine Description: + + This routine does the all 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. + + 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. + // + + if (ExistingFcb) { + + PLIST_ENTRY Links; + PLCB ThisLcb; + + for (Links = PreviousFcb->LcbQueue.Flink; + Links != &PreviousFcb->LcbQueue; + Links = Links->Flink ) { + + ThisLcb = CONTAINING_RECORD( Links, + LCB, + FcbLinks ); + + NtfsRemovePrefix( 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. + +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 ); + } + } + + Pass += 1; + CheckBufferOnly = FALSE; + + } while (Pass < 2); + + } finally { + + if (CleanupAttrContext) { + + NtfsCleanupAttributeContext( &AttrContext ); + } + } + + DebugTrace( -1, Dbg, ("NtfsMoveLinkToNewDir: Exit\n") ); + + return; +} + + +// +// Local support routine. +// + +VOID +NtfsCreateLinkInSameDir ( + IN PIRP_CONTEXT IrpContext, + IN PSCB ParentScb, + IN PFCB Fcb, + IN UNICODE_STRING NewLinkName, + IN UCHAR NewFileNameFlags, + IN UNICODE_STRING PrevLinkName, + IN UCHAR PrevLinkNameFlags + ) + +/*++ + +Routine Description: + + This routine is called when we are replacing a link in a single directory. + We need to find the link being renamed and any auxilary links and + then give them their new names. + +Arguments: + + ParentScb - Scb for the directory the rename is taking place in. + + Fcb - The Fcb for the file whose link is being renamed. + + NewLinkName - This is the name to use for the new link. + + NewFileNameFlags - 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; + + PAGED_CODE(); + + DebugTrace( +1, Dbg, ("NtfsCreateLinkInSameDir: Entered\n") ); + + // + // Initialize our local variables. + // + + SplitPrimaryLcb = NULL; + + 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, + NewFileNameFlags, + FALSE ); + + if (SplitPrimaryLcb != NULL) { + + NtfsRenameLcb( IrpContext, + SplitPrimaryLcb, + &NewLinkName, + NewFileNameFlags, + FALSE ); + + NtfsCombineLcbs( IrpContext, + TraverseLcb, + SplitPrimaryLcb ); + + NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb ); + } + + DebugTrace( -1, Dbg, ("NtfsCreateLinkInSameDir: 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. + +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 any + // 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 ); + } + } + + Pass += 1; + CheckBufferOnly = FALSE; + + } while (Pass < 2); + + } finally { + + if (CleanupAttrContext) { + + NtfsCleanupAttributeContext( &AttrContext ); + } + + DebugTrace( -1, Dbg, ("NtfsRenameLinkInDir: Exit\n") ); + } + + return; +} + + +// +// 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; + + 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 ); + } + + break; + } + } + } + +#ifdef _CAIRO_ + + // + // Check that we don't own the quota table Scb. + // CAIROBUG: Combine these two loops when cairo ifdefs removed. + // + + 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 Mft 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 ); + } + +#endif // _CAIRO_ + + + NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &Lcb, &ParentScb, TRUE ); + NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb ); + + // + // If there is no Ccb then look for one in the Lcb we just got. + // + + if (!ARGUMENT_PRESENT( Ccb ) && + ARGUMENT_PRESENT( Lcb )) { + + PLIST_ENTRY Links; + PCCB NextCcb; + + Links = Lcb->CcbQueue.Flink; + + while (Links != &Lcb->CcbQueue) { + + NextCcb = CONTAINING_RECORD( Links, CCB, LcbLinks ); + if (!FlagOn( NextCcb->Flags, + CCB_FLAG_CLOSE | CCB_FLAG_OPEN_BY_FILE_ID )) { + + Ccb = NextCcb; + break; + } + + 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.Buffer != NULL) ? + &Ccb->Lcb->Scb->ScbType.Index.NormalizedName : + NULL), + FilterMatch, + FILE_ACTION_MODIFIED, + ParentScb->Fcb ); + } + } + + NtfsUpdateLcbDuplicateInfo( Fcb, Lcb ); + Fcb->InfoFlags = 0; + + } except(FsRtlIsNtstatusExpected(GetExceptionCode()) ? + EXCEPTION_EXECUTE_HANDLER : + EXCEPTION_CONTINUE_SEARCH) { + + NOTHING; + } + + return; +} -- cgit v1.2.3