mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8307 lines
238 KiB
8307 lines
238 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FileInfo.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the set and query file information routines for Ntfs
|
|
called by the dispatch driver.
|
|
|
|
Author:
|
|
|
|
Brian Andrew [BrianAn] 15-Jan-1992
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (NTFS_BUG_CHECK_FILEINFO)
|
|
|
|
//
|
|
// The local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_FILEINFO)
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('FFtN')
|
|
|
|
#define SIZEOF_FILE_NAME_INFORMATION (FIELD_OFFSET( FILE_NAME_INFORMATION, FileName[0]) \
|
|
+ sizeof( WCHAR ))
|
|
|
|
//
|
|
// Local flags for rename and set link
|
|
//
|
|
|
|
#define TRAVERSE_MATCH (0x00000001)
|
|
#define EXACT_CASE_MATCH (0x00000002)
|
|
#define ACTIVELY_REMOVE_SOURCE_LINK (0x00000004)
|
|
#define REMOVE_SOURCE_LINK (0x00000008)
|
|
#define REMOVE_TARGET_LINK (0x00000010)
|
|
#define ADD_TARGET_LINK (0x00000020)
|
|
#define REMOVE_TRAVERSE_LINK (0x00000040)
|
|
#define REUSE_TRAVERSE_LINK (0x00000080)
|
|
#define MOVE_TO_NEW_DIR (0x00000100)
|
|
#define ADD_PRIMARY_LINK (0x00000200)
|
|
#define OVERWRITE_SOURCE_LINK (0x00000400)
|
|
|
|
//
|
|
// Additional local flags for set link
|
|
//
|
|
|
|
#define CREATE_IN_NEW_DIR (0x00000400)
|
|
|
|
//
|
|
// Local procedure prototypes
|
|
//
|
|
|
|
//
|
|
// VOID
|
|
// NtfsBuildLastFileName (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PFILE_OBJECT FileObject,
|
|
// IN ULONG FileNameOffset,
|
|
// OUT PUNICODE_STRING FileName
|
|
// );
|
|
//
|
|
|
|
#define NtfsBuildLastFileName(IC,FO,OFF,FN) { \
|
|
(FN)->MaximumLength = (FN)->Length = (FO)->FileName.Length - OFF; \
|
|
(FN)->Buffer = (PWSTR) Add2Ptr( (FO)->FileName.Buffer, OFF ); \
|
|
}
|
|
|
|
VOID
|
|
NtfsQueryBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PSCB Scb,
|
|
IN 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;
|
|
}
|