8308 lines
238 KiB
C
8308 lines
238 KiB
C
/*++
|
||
|
||
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;
|
||
}
|