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.
9354 lines
263 KiB
9354 lines
263 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
StrucSup.c
|
|
|
|
|
|
Abstract:
|
|
|
|
This module implements the Ntfs in-memory data structure manipulation
|
|
routines
|
|
|
|
Author:
|
|
|
|
Gary Kimura [GaryKi] 21-May-1991
|
|
Tom Miller [TomM] 9-Sep-1991
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
#include "lockorder.h"
|
|
|
|
//
|
|
// Temporarily reference our local attribute definitions
|
|
//
|
|
|
|
extern ATTRIBUTE_DEFINITION_COLUMNS NtfsAttributeDefinitions[];
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (NTFS_BUG_CHECK_STRUCSUP)
|
|
|
|
//
|
|
// The debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_STRUCSUP)
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('sFtN')
|
|
|
|
//
|
|
// Define a structure to use when renaming or moving Lcb's so that
|
|
// all the allocation for new filenames will succeed before munging names.
|
|
// This new allocation can be for the filename attribute in an Lcb or the
|
|
// filename in a Ccb.
|
|
//
|
|
|
|
typedef struct _NEW_FILENAME {
|
|
|
|
//
|
|
// Ntfs structure which needs the allocation.
|
|
//
|
|
|
|
PVOID Structure;
|
|
PVOID NewAllocation;
|
|
|
|
} NEW_FILENAME;
|
|
typedef NEW_FILENAME *PNEW_FILENAME;
|
|
|
|
|
|
//
|
|
// Local support routines
|
|
//
|
|
|
|
VOID
|
|
NtfsCheckScbForCache (
|
|
IN OUT PSCB Scb
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsRemoveScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN BOOLEAN CheckForAttributeTable
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsPrepareFcbForRemoval (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PSCB StartingScb OPTIONAL,
|
|
IN BOOLEAN CheckForAttributeTable
|
|
);
|
|
|
|
VOID
|
|
NtfsTeardownFromLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFCB StartingFcb,
|
|
IN PLCB StartingLcb,
|
|
IN BOOLEAN CheckForAttributeTable,
|
|
IN ULONG AcquireFlags,
|
|
OUT PBOOLEAN RemovedStartingLcb,
|
|
OUT PBOOLEAN RemovedStartingFcb
|
|
);
|
|
|
|
VOID
|
|
NtfsReserveCcbNamesInLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PLCB Lcb,
|
|
IN PULONG ParentNameLength OPTIONAL,
|
|
IN ULONG LastComponentNameLength
|
|
);
|
|
|
|
VOID
|
|
NtfsClearRecursiveLcb (
|
|
IN PLCB Lcb
|
|
);
|
|
|
|
|
|
//
|
|
// The following local routines are for manipulating the Fcb Table.
|
|
// The first three are generic table calls backs.
|
|
//
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS
|
|
NtfsFcbTableCompare (
|
|
IN PRTL_GENERIC_TABLE FcbTable,
|
|
IN PVOID FirstStruct,
|
|
IN PVOID SecondStruct
|
|
);
|
|
|
|
//
|
|
// VOID
|
|
// NtfsInsertFcbTableEntry (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PVCB Vcb,
|
|
// IN PFCB Fcb,
|
|
// IN FILE_REFERENCE FileReference
|
|
// );
|
|
//
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
#define NtfsInsertFcbTableEntry(IC,V,F,FR) { \
|
|
FCB_TABLE_ELEMENT _Key; \
|
|
PFCB_TABLE_ELEMENT _NewKey; \
|
|
_Key.FileReference = (FR); \
|
|
_Key.Fcb = (F); \
|
|
_NewKey = RtlInsertElementGenericTable( &(V)->FcbTable, \
|
|
&_Key, \
|
|
sizeof(FCB_TABLE_ELEMENT), \
|
|
NULL ); \
|
|
ASSERT( _NewKey->Fcb == _Key.Fcb ); \
|
|
}
|
|
#else
|
|
#define NtfsInsertFcbTableEntry(IC,V,F,FR) { \
|
|
FCB_TABLE_ELEMENT _Key; \
|
|
_Key.FileReference = (FR); \
|
|
_Key.Fcb = (F); \
|
|
(VOID) RtlInsertElementGenericTable( &(V)->FcbTable, \
|
|
&_Key, \
|
|
sizeof(FCB_TABLE_ELEMENT), \
|
|
NULL ); \
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// VOID
|
|
// NtfsInsertFcbTableEntryFull (
|
|
// IN PIRP_CONTEXT IrpContext,
|
|
// IN PVCB Vcb,
|
|
// IN PFCB Fcb,
|
|
// IN FILE_REFERENCE FileReference,
|
|
// IN PVOID NodeOrParent,
|
|
// IN ULONG SearchResult
|
|
// );
|
|
//
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
#define NtfsInsertFcbTableEntryFull(IC,V,F,FR,N,SR) { \
|
|
FCB_TABLE_ELEMENT _Key; \
|
|
PFCB_TABLE_ELEMENT _NewKey; \
|
|
_Key.FileReference = (FR); \
|
|
_Key.Fcb = (F); \
|
|
_NewKey = RtlInsertElementGenericTableFull( &(V)->FcbTable, \
|
|
&_Key, \
|
|
sizeof(FCB_TABLE_ELEMENT), \
|
|
NULL, \
|
|
(N), \
|
|
(SR) \
|
|
); \
|
|
ASSERT( _NewKey->Fcb == _Key.Fcb ); \
|
|
}
|
|
#else
|
|
#define NtfsInsertFcbTableEntryFull(IC,V,F,FR,N,SR) { \
|
|
FCB_TABLE_ELEMENT _Key; \
|
|
_Key.FileReference = (FR); \
|
|
_Key.Fcb = (F); \
|
|
(VOID) RtlInsertElementGenericTableFull( &(V)->FcbTable, \
|
|
&_Key, \
|
|
sizeof(FCB_TABLE_ELEMENT), \
|
|
NULL, \
|
|
(N), \
|
|
(SR) \
|
|
); \
|
|
}
|
|
#endif
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsAllocateCompressionSync)
|
|
#pragma alloc_text(PAGE, NtfsBuildNormalizedName)
|
|
#pragma alloc_text(PAGE, NtfsBuildRelativeName)
|
|
#pragma alloc_text(PAGE, NtfsCheckScbForCache)
|
|
#pragma alloc_text(PAGE, NtfsClearRecursiveLcb)
|
|
#pragma alloc_text(PAGE, NtfsCombineLcbs)
|
|
#pragma alloc_text(PAGE, NtfsCreateCcb)
|
|
#pragma alloc_text(PAGE, NtfsCreateFcb)
|
|
#pragma alloc_text(PAGE, NtfsCreateFileLock)
|
|
#pragma alloc_text(PAGE, NtfsCreateLcb)
|
|
#pragma alloc_text(PAGE, NtfsCreatePrerestartScb)
|
|
#pragma alloc_text(PAGE, NtfsCreateRootFcb)
|
|
#pragma alloc_text(PAGE, NtfsCreateScb)
|
|
#pragma alloc_text(PAGE, NtfsDeallocateCompressionSync)
|
|
#pragma alloc_text(PAGE, NtfsDeleteCcb)
|
|
#pragma alloc_text(PAGE, NtfsDeleteFcb)
|
|
#pragma alloc_text(PAGE, NtfsDeleteLcb)
|
|
#pragma alloc_text(PAGE, NtfsDeleteNormalizedName)
|
|
#pragma alloc_text(PAGE, NtfsDeleteScb)
|
|
#pragma alloc_text(PAGE, NtfsDeleteVcb)
|
|
#pragma alloc_text(PAGE, NtfsFcbTableCompare)
|
|
#pragma alloc_text(PAGE, NtfsGetDeallocatedClusters)
|
|
#pragma alloc_text(PAGE, NtfsGetNextFcbTableEntry)
|
|
#pragma alloc_text(PAGE, NtfsGetNextScb)
|
|
#pragma alloc_text(PAGE, NtfsInitializeVcb)
|
|
#pragma alloc_text(PAGE, NtfsLookupLcbByFlags)
|
|
#pragma alloc_text(PAGE, NtfsMoveLcb)
|
|
#pragma alloc_text(PAGE, NtfsPostToNewLengthQueue)
|
|
#pragma alloc_text(PAGE, NtfsProcessNewLengthQueue)
|
|
#pragma alloc_text(PAGE, NtfsRemoveScb)
|
|
#pragma alloc_text(PAGE, NtfsRenameLcb)
|
|
#pragma alloc_text(PAGE, NtfsReserveCcbNamesInLcb)
|
|
#pragma alloc_text(PAGE, NtfsTeardownStructures)
|
|
#pragma alloc_text(PAGE, NtfsTestStatusProc)
|
|
#pragma alloc_text(PAGE, NtfsUpdateNormalizedName)
|
|
#pragma alloc_text(PAGE, NtfsUpdateScbSnapshots)
|
|
#pragma alloc_text(PAGE, NtfsWalkUpTree)
|
|
#endif
|
|
|
|
|
|
VOID
|
|
NtfsInitializeVcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PVCB Vcb,
|
|
IN PDEVICE_OBJECT TargetDeviceObject,
|
|
IN PVPB Vpb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes and inserts a new Vcb record into the in-memory
|
|
data structure. The Vcb record "hangs" off the end of the Volume device
|
|
object and must be allocated by our caller.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the address of the Vcb record being initialized.
|
|
|
|
TargetDeviceObject - Supplies the address of the target device object to
|
|
associate with the Vcb record.
|
|
|
|
Vpb - Supplies the address of the Vpb to associate with the Vcb record.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG i;
|
|
ULONG NumberProcessors;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsInitializeVcb, Vcb = %08lx\n", Vcb) );
|
|
|
|
//
|
|
// First zero out the Vcb
|
|
//
|
|
|
|
RtlZeroMemory( Vcb, sizeof(VCB) );
|
|
|
|
//
|
|
// Set the node type code and size
|
|
//
|
|
|
|
Vcb->NodeTypeCode = NTFS_NTC_VCB;
|
|
Vcb->NodeByteSize = sizeof(VCB);
|
|
|
|
//
|
|
// Set the following Vcb flags before putting the Vcb in the
|
|
// Vcb queue. This will lock out checkpoints until the
|
|
// volume is mounted.
|
|
//
|
|
|
|
SetFlag( Vcb->CheckpointFlags,
|
|
VCB_CHECKPOINT_IN_PROGRESS |
|
|
VCB_LAST_CHECKPOINT_CLEAN |
|
|
VCB_LAST_CHECKPOINT_PSEUDO_CLEAN);
|
|
|
|
//
|
|
// Insert this vcb record into the vcb queue off of the global data
|
|
// record
|
|
//
|
|
|
|
InsertTailList( &NtfsData.VcbQueue, &Vcb->VcbLinks );
|
|
|
|
//
|
|
// Set the target device object and vpb fields
|
|
//
|
|
|
|
ObReferenceObject( TargetDeviceObject );
|
|
Vcb->TargetDeviceObject = TargetDeviceObject;
|
|
Vcb->Vpb = Vpb;
|
|
|
|
//
|
|
// Set the state and condition fields. The removable media flag
|
|
// is set based on the real device's characteristics.
|
|
//
|
|
|
|
if (FlagOn(Vpb->RealDevice->Characteristics, FILE_REMOVABLE_MEDIA)) {
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_REMOVABLE_MEDIA );
|
|
}
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
|
|
|
|
//
|
|
// Initialized the ModifiedOpenFilesListhead and the delete notify queue.
|
|
//
|
|
|
|
InitializeListHead( &Vcb->NotifyUsnDeleteIrps );
|
|
InitializeListHead( &Vcb->ModifiedOpenFiles );
|
|
InitializeListHead( &Vcb->TimeOutListA );
|
|
InitializeListHead( &Vcb->TimeOutListB );
|
|
|
|
Vcb->CurrentTimeOutFiles = &Vcb->TimeOutListA;
|
|
Vcb->AgedTimeOutFiles = &Vcb->TimeOutListB;
|
|
|
|
//
|
|
// Initialize list of OpenAttribute structures.
|
|
//
|
|
|
|
InitializeListHead( &Vcb->OpenAttributeData );
|
|
|
|
//
|
|
// Initialize list of deallocated clusters
|
|
//
|
|
|
|
InitializeListHead( &Vcb->DeallocatedClusterListHead );
|
|
|
|
//
|
|
// Initialize the synchronization objects in the Vcb.
|
|
//
|
|
|
|
ExInitializeResourceLite( &Vcb->Resource );
|
|
ExInitializeResourceLite( &Vcb->MftFlushResource );
|
|
|
|
ExInitializeFastMutex( &Vcb->FcbTableMutex );
|
|
ExInitializeFastMutex( &Vcb->FcbSecurityMutex );
|
|
ExInitializeFastMutex( &Vcb->ReservedClustersMutex );
|
|
ExInitializeFastMutex( &Vcb->HashTableMutex );
|
|
ExInitializeFastMutex( &Vcb->CheckpointMutex );
|
|
ExInitializeFastMutex( &Vcb->ReservedMappingMutex );
|
|
|
|
KeInitializeEvent( &Vcb->CheckpointNotifyEvent, NotificationEvent, TRUE );
|
|
|
|
//
|
|
// Initialize the Fcb Table
|
|
//
|
|
|
|
RtlInitializeGenericTable( &Vcb->FcbTable,
|
|
NtfsFcbTableCompare,
|
|
NtfsAllocateFcbTableEntry,
|
|
NtfsFreeFcbTableEntry,
|
|
NULL );
|
|
|
|
//
|
|
// Initialize the property tunneling structure
|
|
//
|
|
|
|
FsRtlInitializeTunnelCache(&Vcb->Tunnel);
|
|
|
|
|
|
#ifdef BENL_DBG
|
|
InitializeListHead( &(Vcb->RestartRedoHead) );
|
|
InitializeListHead( &(Vcb->RestartUndoHead) );
|
|
#endif
|
|
|
|
//
|
|
// Initialize the transactions done event
|
|
//
|
|
|
|
KeInitializeEvent( &Vcb->TransactionsDoneEvent, NotificationEvent, FALSE );
|
|
|
|
//
|
|
// Possible calls that might fail begins here
|
|
//
|
|
|
|
//
|
|
// Initialize the list head and mutex for the dir notify Irps.
|
|
// Also the rename resource.
|
|
//
|
|
|
|
InitializeListHead( &Vcb->DirNotifyList );
|
|
InitializeListHead( &Vcb->ViewIndexNotifyList );
|
|
FsRtlNotifyInitializeSync( &Vcb->NotifySync );
|
|
|
|
//
|
|
// Allocate and initialize struct array for performance data. This
|
|
// attempt to allocate could raise STATUS_INSUFFICIENT_RESOURCES.
|
|
//
|
|
|
|
NumberProcessors = KeNumberProcessors;
|
|
Vcb->Statistics = NtfsAllocatePool( NonPagedPool,
|
|
sizeof(FILE_SYSTEM_STATISTICS) * NumberProcessors );
|
|
|
|
RtlZeroMemory( Vcb->Statistics, sizeof(FILE_SYSTEM_STATISTICS) * NumberProcessors );
|
|
|
|
for (i = 0; i < NumberProcessors; i += 1) {
|
|
Vcb->Statistics[i].Common.FileSystemType = FILESYSTEM_STATISTICS_TYPE_NTFS;
|
|
Vcb->Statistics[i].Common.Version = 1;
|
|
Vcb->Statistics[i].Common.SizeOfCompleteStructure =
|
|
sizeof(FILE_SYSTEM_STATISTICS);
|
|
}
|
|
|
|
//
|
|
// Initialize the cached runs.
|
|
//
|
|
|
|
NtfsInitializeCachedRuns( &Vcb->CachedRuns );
|
|
|
|
#ifdef NTFS_CHECK_CACHED_RUNS
|
|
Vcb->CachedRuns.Vcb = Vcb;
|
|
#endif
|
|
|
|
//
|
|
// Initialize the hash table.
|
|
//
|
|
|
|
NtfsInitializeHashTable( &Vcb->HashTable );
|
|
|
|
//
|
|
// Allocate a spare Vpb for the dismount case.
|
|
//
|
|
|
|
Vcb->SpareVpb = NtfsAllocatePoolWithTag( NonPagedPool, sizeof( VPB ), 'VftN' );
|
|
|
|
//
|
|
// Capture the current change count in the device we talk to.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_REMOVABLE_MEDIA )) {
|
|
|
|
ULONG ChangeCount = 0;
|
|
|
|
NtfsDeviceIoControlAsync( IrpContext,
|
|
Vcb->TargetDeviceObject,
|
|
IOCTL_DISK_CHECK_VERIFY,
|
|
(PVOID) &ChangeCount,
|
|
sizeof( ChangeCount ));
|
|
|
|
//
|
|
// Ignore any error for now. We will see it later if there is
|
|
// one.
|
|
//
|
|
|
|
Vcb->DeviceChangeCount = ChangeCount;
|
|
}
|
|
|
|
//
|
|
// Set the dirty page table hint to its initial value
|
|
//
|
|
|
|
Vcb->DirtyPageTableSizeHint = INITIAL_DIRTY_TABLE_HINT;
|
|
|
|
//
|
|
// Initialize the recently deallocated cluster mcbs and put the 1st one on the list.
|
|
//
|
|
|
|
FsRtlInitializeLargeMcb( &Vcb->DeallocatedClusters1.Mcb, PagedPool );
|
|
FsRtlInitializeLargeMcb( &Vcb->DeallocatedClusters2.Mcb, PagedPool );
|
|
|
|
Vcb->DeallocatedClusters1.Lsn.QuadPart = 0;
|
|
InsertHeadList( &Vcb->DeallocatedClusterListHead, &Vcb->DeallocatedClusters1.Link );
|
|
|
|
//
|
|
// Initialize a reserved mapping buffer for mapping user data under low memory
|
|
//
|
|
|
|
Vcb->ReservedMapping = MmAllocateMappingAddress( 2 * PAGE_SIZE, RESERVE_POOL_TAG );
|
|
if (!Vcb->ReservedMapping) {
|
|
NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsInitializeVcb -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsDeleteVcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PVCB *Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine removes the Vcb record from Ntfs's in-memory data
|
|
structures.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb to be removed
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the Vcb was deleted, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOLUME_DEVICE_OBJECT VolDo;
|
|
BOOLEAN AcquiredFcbTable;
|
|
PSCB Scb;
|
|
PFCB Fcb;
|
|
BOOLEAN VcbDeleted = FALSE;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( *Vcb );
|
|
|
|
ASSERTMSG("Cannot delete Vcb ", !FlagOn((*Vcb)->VcbState, VCB_STATE_VOLUME_MOUNTED));
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteVcb, *Vcb = %08lx\n", *Vcb) );
|
|
|
|
//
|
|
// Remember the volume device object.
|
|
//
|
|
|
|
VolDo = CONTAINING_RECORD( *Vcb, VOLUME_DEVICE_OBJECT, Vcb );
|
|
|
|
//
|
|
// Make sure that we can really delete the vcb
|
|
//
|
|
|
|
ASSERT( (*Vcb)->CloseCount == 0 );
|
|
|
|
NtOfsPurgeSecurityCache( *Vcb );
|
|
|
|
//
|
|
// If the Vcb log file object is present then we need to
|
|
// dereference it and uninitialize it through the cache.
|
|
//
|
|
|
|
if (((*Vcb)->LogFileObject != NULL) &&
|
|
!FlagOn( (*Vcb)->CheckpointFlags, VCB_DEREFERENCED_LOG_FILE )) {
|
|
|
|
CcUninitializeCacheMap( (*Vcb)->LogFileObject,
|
|
&Li0,
|
|
NULL );
|
|
|
|
//
|
|
// Set a flag indicating that we are dereferencing the LogFileObject.
|
|
//
|
|
|
|
SetFlag( (*Vcb)->CheckpointFlags, VCB_DEREFERENCED_LOG_FILE );
|
|
ObDereferenceObject( (*Vcb)->LogFileObject );
|
|
}
|
|
|
|
//
|
|
// Only proceed if the log file object went away. In the typical case the
|
|
// close will come in through a recursive call from the ObDereference call
|
|
// above.
|
|
//
|
|
|
|
if ((*Vcb)->LogFileObject == NULL) {
|
|
|
|
//
|
|
// If the OnDiskOat is not the same as the embedded table then
|
|
// free the OnDisk table.
|
|
//
|
|
|
|
if (((*Vcb)->OnDiskOat != NULL) &&
|
|
((*Vcb)->OnDiskOat != &(*Vcb)->OpenAttributeTable)) {
|
|
|
|
NtfsFreeRestartTable( (*Vcb)->OnDiskOat );
|
|
NtfsFreePool( (*Vcb)->OnDiskOat );
|
|
(*Vcb)->OnDiskOat = NULL;
|
|
}
|
|
|
|
//
|
|
// Uninitialize the Mcb's for the deallocated cluster Mcb's.
|
|
//
|
|
|
|
if ((*Vcb)->DeallocatedClusters1.Link.Flink == NULL) {
|
|
FsRtlUninitializeLargeMcb( &(*Vcb)->DeallocatedClusters1.Mcb );
|
|
}
|
|
if ((*Vcb)->DeallocatedClusters2.Link.Flink == NULL) {
|
|
FsRtlUninitializeLargeMcb( &(*Vcb)->DeallocatedClusters2.Mcb );
|
|
}
|
|
|
|
while (!IsListEmpty(&(*Vcb)->DeallocatedClusterListHead )) {
|
|
|
|
PDEALLOCATED_CLUSTERS Clusters;
|
|
|
|
Clusters = (PDEALLOCATED_CLUSTERS) RemoveHeadList( &(*Vcb)->DeallocatedClusterListHead );
|
|
FsRtlUninitializeLargeMcb( &Clusters->Mcb );
|
|
if ((Clusters != &((*Vcb)->DeallocatedClusters2)) &&
|
|
(Clusters != &((*Vcb)->DeallocatedClusters1))) {
|
|
|
|
NtfsFreePool( Clusters );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up the Root Lcb if present.
|
|
//
|
|
|
|
if ((*Vcb)->RootLcb != NULL) {
|
|
|
|
//
|
|
// Cleanup the Lcb so the DeleteLcb routine won't look at any
|
|
// other structures.
|
|
//
|
|
|
|
InitializeListHead( &(*Vcb)->RootLcb->ScbLinks );
|
|
InitializeListHead( &(*Vcb)->RootLcb->FcbLinks );
|
|
ClearFlag( (*Vcb)->RootLcb->LcbState,
|
|
LCB_STATE_EXACT_CASE_IN_TREE | LCB_STATE_IGNORE_CASE_IN_TREE );
|
|
|
|
NtfsDeleteLcb( IrpContext, &(*Vcb)->RootLcb );
|
|
(*Vcb)->RootLcb = NULL;
|
|
}
|
|
|
|
//
|
|
// Make sure the Fcb table is completely emptied. It is possible that an occasional Fcb
|
|
// (along with its Scb) will not be deleted when the file object closes come in.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
PVOID RestartKey;
|
|
|
|
//
|
|
// Always reinitialize the search so we get the first element in the tree.
|
|
//
|
|
|
|
RestartKey = NULL;
|
|
NtfsAcquireFcbTable( IrpContext, *Vcb );
|
|
Fcb = NtfsGetNextFcbTableEntry( *Vcb, &RestartKey );
|
|
NtfsReleaseFcbTable( IrpContext, *Vcb );
|
|
|
|
if (Fcb == NULL) { break; }
|
|
|
|
while ((Scb = NtfsGetNextChildScb( Fcb, NULL )) != NULL) {
|
|
|
|
NtfsDeleteScb( IrpContext, &Scb );
|
|
}
|
|
|
|
NtfsAcquireFcbTable( IrpContext, *Vcb );
|
|
NtfsDeleteFcb( IrpContext, &Fcb, &AcquiredFcbTable );
|
|
}
|
|
|
|
//
|
|
// Free the upcase table and attribute definitions. The upcase
|
|
// table only gets freed if it is not the global table.
|
|
//
|
|
|
|
if (((*Vcb)->UpcaseTable != NULL) && ((*Vcb)->UpcaseTable != NtfsData.UpcaseTable)) {
|
|
|
|
NtfsFreePool( (*Vcb)->UpcaseTable );
|
|
}
|
|
|
|
(*Vcb)->UpcaseTable = NULL;
|
|
|
|
if (((*Vcb)->AttributeDefinitions != NULL) &&
|
|
((*Vcb)->AttributeDefinitions != NtfsAttributeDefinitions)) {
|
|
|
|
NtfsFreePool( (*Vcb)->AttributeDefinitions );
|
|
(*Vcb)->AttributeDefinitions = NULL;
|
|
}
|
|
|
|
//
|
|
// Free the device name string if present.
|
|
//
|
|
|
|
if ((*Vcb)->DeviceName.Buffer != NULL) {
|
|
|
|
NtfsFreePool( (*Vcb)->DeviceName.Buffer );
|
|
(*Vcb)->DeviceName.Buffer = NULL;
|
|
}
|
|
|
|
FsRtlNotifyUninitializeSync( &(*Vcb)->NotifySync );
|
|
|
|
//
|
|
// We will free the structure allocated for the Lfs handle.
|
|
//
|
|
|
|
LfsDeleteLogHandle( (*Vcb)->LogHandle );
|
|
(*Vcb)->LogHandle = NULL;
|
|
|
|
//
|
|
// Delete the vcb resource and also free the restart tables
|
|
//
|
|
|
|
//
|
|
// Empty the list of OpenAttribute Data.
|
|
//
|
|
|
|
NtfsFreeAllOpenAttributeData( *Vcb );
|
|
|
|
NtfsFreeRestartTable( &(*Vcb)->OpenAttributeTable );
|
|
NtfsFreeRestartTable( &(*Vcb)->TransactionTable );
|
|
|
|
//
|
|
// The Vpb in the Vcb may be a temporary Vpb and we should free it here.
|
|
//
|
|
|
|
if (FlagOn( (*Vcb)->VcbState, VCB_STATE_TEMP_VPB )) {
|
|
|
|
NtfsFreePool( (*Vcb)->Vpb );
|
|
(*Vcb)->Vpb = NULL;
|
|
}
|
|
|
|
//
|
|
// Uninitialize the hash table.
|
|
//
|
|
|
|
NtfsUninitializeHashTable( &(*Vcb)->HashTable );
|
|
|
|
ExDeleteResourceLite( &(*Vcb)->Resource );
|
|
ExDeleteResourceLite( &(*Vcb)->MftFlushResource );
|
|
|
|
//
|
|
// Delete the space used to store performance counters.
|
|
//
|
|
|
|
if ((*Vcb)->Statistics != NULL) {
|
|
NtfsFreePool( (*Vcb)->Statistics );
|
|
(*Vcb)->Statistics = NULL;
|
|
}
|
|
|
|
//
|
|
// Tear down the file property tunneling structure
|
|
//
|
|
|
|
FsRtlDeleteTunnelCache(&(*Vcb)->Tunnel);
|
|
|
|
#ifdef NTFS_CHECK_BITMAP
|
|
if ((*Vcb)->BitmapCopy != NULL) {
|
|
|
|
ULONG Count = 0;
|
|
|
|
while (Count < (*Vcb)->BitmapPages) {
|
|
|
|
if (((*Vcb)->BitmapCopy + Count)->Buffer != NULL) {
|
|
|
|
NtfsFreePool( ((*Vcb)->BitmapCopy + Count)->Buffer );
|
|
}
|
|
|
|
Count += 1;
|
|
}
|
|
|
|
NtfsFreePool( (*Vcb)->BitmapCopy );
|
|
(*Vcb)->BitmapCopy = NULL;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Release the reserved mapping
|
|
//
|
|
|
|
if ((*Vcb)->ReservedMapping) {
|
|
MmFreeMappingAddress( (*Vcb)->ReservedMapping, RESERVE_POOL_TAG );
|
|
}
|
|
|
|
//
|
|
// Drop the reference on the target device object
|
|
//
|
|
|
|
ObDereferenceObject( (*Vcb)->TargetDeviceObject );
|
|
|
|
//
|
|
// Check that the Usn queues are empty.
|
|
//
|
|
|
|
ASSERT( IsListEmpty( &(*Vcb)->NotifyUsnDeleteIrps ));
|
|
ASSERT( IsListEmpty( &(*Vcb)->ModifiedOpenFiles ));
|
|
ASSERT( IsListEmpty( &(*Vcb)->TimeOutListA ));
|
|
ASSERT( IsListEmpty( &(*Vcb)->TimeOutListB ));
|
|
|
|
//
|
|
// Unnitialize the cached runs.
|
|
//
|
|
|
|
NtfsUninitializeCachedRuns( &(*Vcb)->CachedRuns );
|
|
|
|
//
|
|
// Free any spare Vpb we might have stored in the Vcb.
|
|
//
|
|
|
|
if ((*Vcb)->SpareVpb != NULL) {
|
|
|
|
NtfsFreePool( (*Vcb)->SpareVpb );
|
|
(*Vcb)->SpareVpb = NULL;
|
|
}
|
|
|
|
//
|
|
// Return the Vcb (i.e., the VolumeDeviceObject) to pool and null out
|
|
// the input pointer to be safe
|
|
//
|
|
|
|
IoDeleteDevice( (PDEVICE_OBJECT)VolDo );
|
|
|
|
*Vcb = NULL;
|
|
VcbDeleted = TRUE;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteVcb -> VOID\n") );
|
|
|
|
return VcbDeleted;
|
|
}
|
|
|
|
|
|
PFCB
|
|
NtfsCreateRootFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates, initializes, and inserts a new root FCB record
|
|
into the in memory data structure. It also creates the necessary Root LCB
|
|
record and inserts the root name into the prefix table.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb to associate with the new root Fcb and Lcb
|
|
|
|
Return Value:
|
|
|
|
PFCB - returns pointer to the newly allocated root FCB.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB RootFcb;
|
|
PLCB RootLcb;
|
|
|
|
//
|
|
// The following variables are only used for abnormal termination
|
|
//
|
|
|
|
PVOID UnwindStorage = NULL;
|
|
PERESOURCE UnwindResource = NULL;
|
|
PFAST_MUTEX UnwindFastMutex = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateRootFcb, Vcb = %08lx\n", Vcb) );
|
|
|
|
try {
|
|
|
|
//
|
|
// Allocate a new fcb and zero it out. We use Fcb locally so we
|
|
// don't have to continually go through the Vcb
|
|
//
|
|
|
|
RootFcb =
|
|
UnwindStorage = (PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbIndexLookasideList );
|
|
|
|
RtlZeroMemory( RootFcb, sizeof(FCB_INDEX) );
|
|
|
|
//
|
|
// Set the proper node type code and byte size
|
|
//
|
|
|
|
RootFcb->NodeTypeCode = NTFS_NTC_FCB;
|
|
RootFcb->NodeByteSize = sizeof(FCB);
|
|
|
|
SetFlag( RootFcb->FcbState, FCB_STATE_COMPOUND_INDEX );
|
|
|
|
//
|
|
// Initialize the Lcb queue and point back to our Vcb.
|
|
//
|
|
|
|
InitializeListHead( &RootFcb->LcbQueue );
|
|
|
|
RootFcb->Vcb = Vcb;
|
|
|
|
//
|
|
// File Reference
|
|
//
|
|
|
|
NtfsSetSegmentNumber( &RootFcb->FileReference,
|
|
0,
|
|
ROOT_FILE_NAME_INDEX_NUMBER );
|
|
RootFcb->FileReference.SequenceNumber = ROOT_FILE_NAME_INDEX_NUMBER;
|
|
|
|
//
|
|
// Initialize the Scb
|
|
//
|
|
|
|
InitializeListHead( &RootFcb->ScbQueue );
|
|
|
|
//
|
|
// Allocate and initialize the resource variable
|
|
//
|
|
|
|
UnwindResource = RootFcb->Resource = NtfsAllocateEresource();
|
|
|
|
//
|
|
// Allocate and initialize the Fcb fast mutex.
|
|
//
|
|
|
|
UnwindFastMutex =
|
|
RootFcb->FcbMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
|
|
ExInitializeFastMutex( UnwindFastMutex );
|
|
|
|
//
|
|
// Insert this new fcb into the fcb table
|
|
//
|
|
|
|
NtfsInsertFcbTableEntry( IrpContext, Vcb, RootFcb, RootFcb->FileReference );
|
|
SetFlag( RootFcb->FcbState, FCB_STATE_IN_FCB_TABLE );
|
|
|
|
//
|
|
// Now insert this new root fcb into it proper position in the graph with a
|
|
// root lcb. First allocate an initialize the root lcb and then build the
|
|
// lcb/scb graph.
|
|
//
|
|
|
|
{
|
|
//
|
|
// Use the root Lcb within the Fcb.
|
|
//
|
|
|
|
RootLcb = Vcb->RootLcb = (PLCB) &((PFCB_INDEX) RootFcb)->Lcb;
|
|
|
|
RootLcb->NodeTypeCode = NTFS_NTC_LCB;
|
|
RootLcb->NodeByteSize = sizeof(LCB);
|
|
|
|
//
|
|
// Insert the root lcb into the Root Fcb's queue
|
|
//
|
|
|
|
InsertTailList( &RootFcb->LcbQueue, &RootLcb->FcbLinks );
|
|
RootLcb->Fcb = RootFcb;
|
|
|
|
//
|
|
// Use the embedded file name attribute.
|
|
//
|
|
|
|
RootLcb->FileNameAttr = (PFILE_NAME) &RootLcb->ParentDirectory;
|
|
|
|
RootLcb->FileNameAttr->ParentDirectory = RootFcb->FileReference;
|
|
RootLcb->FileNameAttr->FileNameLength = 1;
|
|
RootLcb->FileNameAttr->Flags = FILE_NAME_NTFS | FILE_NAME_DOS;
|
|
|
|
RootLcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &RootLcb->FileNameAttr->FileName;
|
|
|
|
RootLcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( RootLcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( 2 ));
|
|
|
|
RootLcb->ExactCaseLink.LinkName.MaximumLength =
|
|
RootLcb->ExactCaseLink.LinkName.Length =
|
|
RootLcb->IgnoreCaseLink.LinkName.MaximumLength =
|
|
RootLcb->IgnoreCaseLink.LinkName.Length = 2;
|
|
|
|
RootLcb->ExactCaseLink.LinkName.Buffer[0] =
|
|
RootLcb->IgnoreCaseLink.LinkName.Buffer[0] = L'\\';
|
|
|
|
SetFlag( RootLcb->FileNameAttr->Flags, FILE_NAME_NTFS | FILE_NAME_DOS );
|
|
|
|
//
|
|
// Initialize both the ccb.
|
|
//
|
|
|
|
InitializeListHead( &RootLcb->CcbQueue );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateRootFcb );
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
if (UnwindResource) { NtfsFreeEresource( UnwindResource ); }
|
|
if (UnwindStorage) { NtfsFreePool( UnwindStorage ); }
|
|
if (UnwindFastMutex) { NtfsFreePool( UnwindFastMutex ); }
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateRootFcb -> %8lx\n", RootFcb) );
|
|
|
|
return RootFcb;
|
|
}
|
|
|
|
|
|
PFCB
|
|
NtfsCreateFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN FILE_REFERENCE FileReference,
|
|
IN BOOLEAN IsPagingFile,
|
|
IN BOOLEAN LargeFcb,
|
|
OUT PBOOLEAN ReturnedExistingFcb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates and initializes a new Fcb record. The record
|
|
is not placed within the Fcb/Scb graph but is only inserted in the
|
|
FcbTable.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb to associate the new FCB under.
|
|
|
|
FileReference - Supplies the file reference to use to identify the
|
|
Fcb with. We will search the Fcb table for any preexisting
|
|
Fcb's with the same file reference number.
|
|
|
|
IsPagingFile - Indicates if we are creating an FCB for a paging file
|
|
or some other type of file.
|
|
|
|
LargeFcb - Indicates if we should use the larger of the compound Fcb's.
|
|
|
|
ReturnedExistingFcb - Optionally indicates to the caller if the
|
|
returned Fcb already existed
|
|
|
|
Return Value:
|
|
|
|
PFCB - Returns a pointer to the newly allocated FCB
|
|
|
|
--*/
|
|
|
|
{
|
|
FCB_TABLE_ELEMENT Key;
|
|
PFCB_TABLE_ELEMENT Entry;
|
|
|
|
PFCB Fcb;
|
|
|
|
PVOID NodeOrParent;
|
|
TABLE_SEARCH_RESULT SearchResult;
|
|
|
|
BOOLEAN LocalReturnedExistingFcb;
|
|
BOOLEAN DeletedOldFcb = FALSE;
|
|
|
|
//
|
|
// The following variables are only used for abnormal termination
|
|
//
|
|
|
|
PVOID UnwindStorage = NULL;
|
|
PERESOURCE UnwindResource = NULL;
|
|
PFAST_MUTEX UnwindFastMutex = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
ASSERT_SHARED_RESOURCE( &Vcb->Resource );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateFcb\n") );
|
|
|
|
if (!ARGUMENT_PRESENT(ReturnedExistingFcb)) { ReturnedExistingFcb = &LocalReturnedExistingFcb; }
|
|
|
|
//
|
|
// First search the FcbTable for a matching fcb
|
|
//
|
|
|
|
Key.FileReference = FileReference;
|
|
Fcb = NULL;
|
|
|
|
if ((Entry = RtlLookupElementGenericTableFull( &Vcb->FcbTable, &Key, &NodeOrParent, &SearchResult )) != NULL) {
|
|
|
|
Fcb = Entry->Fcb;
|
|
|
|
//
|
|
// It's possible that this Fcb has been deleted but in truncating and
|
|
// growing the Mft we are reusing some of the file references.
|
|
// If this file has been deleted but the Fcb is waiting around for
|
|
// closes, we will remove it from the Fcb table and create a new Fcb
|
|
// below.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
|
|
|
|
//
|
|
// Remove it from the Fcb table and remember to create an
|
|
// Fcb below.
|
|
//
|
|
|
|
NtfsDeleteFcbTableEntry( Fcb->Vcb,
|
|
Fcb->FileReference );
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE );
|
|
DeletedOldFcb = TRUE;
|
|
Fcb = NULL;
|
|
|
|
} else {
|
|
|
|
*ReturnedExistingFcb = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now check if we have an Fcb.
|
|
//
|
|
|
|
if (Fcb == NULL) {
|
|
|
|
*ReturnedExistingFcb = FALSE;
|
|
|
|
try {
|
|
|
|
//
|
|
// Allocate a new FCB and zero it out.
|
|
//
|
|
|
|
if (IsPagingFile ||
|
|
NtfsSegmentNumber( &FileReference ) <= MASTER_FILE_TABLE2_NUMBER ||
|
|
NtfsSegmentNumber( &FileReference ) == BAD_CLUSTER_FILE_NUMBER ||
|
|
NtfsSegmentNumber( &FileReference ) == BIT_MAP_FILE_NUMBER) {
|
|
|
|
Fcb = UnwindStorage = NtfsAllocatePoolWithTag( NonPagedPool,
|
|
sizeof(FCB),
|
|
'fftN' );
|
|
RtlZeroMemory( Fcb, sizeof(FCB) );
|
|
|
|
if (IsPagingFile) {
|
|
|
|
//
|
|
// We can't have the pagingfile on a readonly volume.
|
|
//
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
NtfsRaiseStatus( IrpContext, STATUS_MEDIA_WRITE_PROTECTED, NULL, NULL );
|
|
}
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_PAGING_FILE );
|
|
|
|
//
|
|
// We don't want to dismount this volume now that
|
|
// we have a pagefile open on it.
|
|
//
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT );
|
|
}
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_NONPAGED );
|
|
|
|
} else {
|
|
|
|
if (LargeFcb) {
|
|
|
|
Fcb = UnwindStorage =
|
|
(PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbIndexLookasideList );
|
|
|
|
RtlZeroMemory( Fcb, sizeof( FCB_INDEX ));
|
|
SetFlag( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX );
|
|
|
|
} else {
|
|
|
|
Fcb = UnwindStorage =
|
|
(PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbDataLookasideList );
|
|
|
|
RtlZeroMemory( Fcb, sizeof( FCB_DATA ));
|
|
SetFlag( Fcb->FcbState, FCB_STATE_COMPOUND_DATA );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the proper node type code and byte size
|
|
//
|
|
|
|
Fcb->NodeTypeCode = NTFS_NTC_FCB;
|
|
Fcb->NodeByteSize = sizeof(FCB);
|
|
|
|
//
|
|
// Initialize the Lcb queue and point back to our Vcb, and indicate
|
|
// that we are a directory
|
|
//
|
|
|
|
InitializeListHead( &Fcb->LcbQueue );
|
|
|
|
Fcb->Vcb = Vcb;
|
|
|
|
//
|
|
// File Reference
|
|
//
|
|
|
|
Fcb->FileReference = FileReference;
|
|
|
|
//
|
|
// Initialize the Scb
|
|
//
|
|
|
|
InitializeListHead( &Fcb->ScbQueue );
|
|
|
|
//
|
|
// Allocate and initialize the resource variable
|
|
//
|
|
|
|
UnwindResource = Fcb->Resource = NtfsAllocateEresource();
|
|
|
|
//
|
|
// Allocate and initialize fast mutex for the Fcb.
|
|
//
|
|
|
|
UnwindFastMutex = Fcb->FcbMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
|
|
ExInitializeFastMutex( UnwindFastMutex );
|
|
|
|
//
|
|
// Insert this new fcb into the fcb table. We have to use the basic
|
|
// version of this function when we deleted an old fcb because the "smarter" one
|
|
// will just return back the old entry rather than researching
|
|
//
|
|
|
|
if (DeletedOldFcb) {
|
|
NtfsInsertFcbTableEntry( IrpContext, Vcb, Fcb, FileReference );
|
|
} else {
|
|
NtfsInsertFcbTableEntryFull( IrpContext, Vcb, Fcb, FileReference, NodeOrParent, SearchResult );
|
|
}
|
|
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE );
|
|
|
|
//
|
|
// Set the flag to indicate if this is a system file.
|
|
//
|
|
|
|
if (NtfsSegmentNumber( &FileReference ) < FIRST_USER_FILE_NUMBER) {
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_SYSTEM_FILE );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateFcb );
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
if (UnwindFastMutex) { NtfsFreePool( UnwindFastMutex ); }
|
|
if (UnwindResource) { NtfsFreeEresource( UnwindResource ); }
|
|
if (UnwindStorage) { NtfsFreePool( UnwindStorage ); }
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateFcb -> %08lx\n", Fcb) );
|
|
|
|
return Fcb;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PFCB *Fcb,
|
|
OUT PBOOLEAN AcquiredFcbTable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deallocates and removes an FCB record from all Ntfs's in-memory
|
|
data structures. It assumes that it does not have anything Scb children nor
|
|
does it have any lcb edges going into it at the time of the call.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the FCB to be removed
|
|
|
|
AcquiredFcbTable - Set to FALSE when this routine releases the
|
|
FcbTable.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FCB( *Fcb );
|
|
ASSERT( IsListEmpty(&(*Fcb)->ScbQueue) );
|
|
ASSERT( (NodeType(*Fcb) == NTFS_NTC_FCB) );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteFcb, *Fcb = %08lx\n", *Fcb) );
|
|
|
|
//
|
|
// First free any possible Scb snapshots.
|
|
//
|
|
|
|
NtfsFreeSnapshotsForFcb( IrpContext, *Fcb );
|
|
|
|
//
|
|
// This Fcb may be in the ExclusiveFcb list of the IrpContext.
|
|
// If it is (The Flink is not NULL), we remove it.
|
|
// And release the global resource.
|
|
//
|
|
|
|
if ((*Fcb)->ExclusiveFcbLinks.Flink != NULL) {
|
|
|
|
RemoveEntryList( &(*Fcb)->ExclusiveFcbLinks );
|
|
}
|
|
|
|
//
|
|
// Clear the IrpContext field for any request which may own the paging
|
|
// IO resource for this Fcb.
|
|
//
|
|
|
|
if (IrpContext->CleanupStructure == *Fcb) {
|
|
|
|
IrpContext->CleanupStructure = NULL;
|
|
|
|
} else if (IrpContext->TopLevelIrpContext->CleanupStructure == *Fcb) {
|
|
|
|
IrpContext->TopLevelIrpContext->CleanupStructure = NULL;
|
|
}
|
|
|
|
//
|
|
// Either we own the FCB or nobody should own it. The extra acquire
|
|
// here does not matter since we will free the resource below.
|
|
//
|
|
|
|
ASSERT( NtfsAcquireResourceExclusive( IrpContext, (*Fcb), FALSE ));
|
|
ASSERT( ExGetSharedWaiterCount( (*Fcb)->Resource ) == 0 );
|
|
ASSERT( ExGetExclusiveWaiterCount( (*Fcb)->Resource) == 0 );
|
|
|
|
#ifdef NTFSDBG
|
|
|
|
//
|
|
// Lock order package needs to know this resource is gone
|
|
//
|
|
|
|
if (IrpContext->Vcb && FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
NtfsChangeResourceOrderState( IrpContext, NtfsIdentifyFcb( IrpContext->Vcb, *Fcb ), TRUE, FALSE );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Deallocate the resources protecting the Fcb
|
|
//
|
|
|
|
NtfsFreeEresource( (*Fcb)->Resource );
|
|
|
|
if ( (*Fcb)->PagingIoResource != NULL ) {
|
|
|
|
if (IrpContext->CleanupStructure == *Fcb) {
|
|
IrpContext->CleanupStructure = NULL;
|
|
}
|
|
|
|
NtfsFreeEresource( (*Fcb)->PagingIoResource );
|
|
}
|
|
|
|
//
|
|
// Deallocate the fast mutex.
|
|
//
|
|
|
|
if ((*Fcb)->FcbMutex != NULL) {
|
|
|
|
NtfsFreePool( (*Fcb)->FcbMutex );
|
|
}
|
|
|
|
//
|
|
// Remove the fcb from the fcb table if present.
|
|
//
|
|
|
|
if (FlagOn( (*Fcb)->FcbState, FCB_STATE_IN_FCB_TABLE )) {
|
|
|
|
NtfsDeleteFcbTableEntry( (*Fcb)->Vcb, (*Fcb)->FileReference );
|
|
ClearFlag( (*Fcb)->FcbState, FCB_STATE_IN_FCB_TABLE );
|
|
}
|
|
|
|
NtfsReleaseFcbTable( IrpContext, (*Fcb)->Vcb );
|
|
*AcquiredFcbTable = FALSE;
|
|
|
|
//
|
|
// Dereference and possibly deallocate the security descriptor if present.
|
|
//
|
|
|
|
if ((*Fcb)->SharedSecurity != NULL) {
|
|
|
|
NtfsAcquireFcbSecurity( (*Fcb)->Vcb );
|
|
RemoveReferenceSharedSecurityUnsafe( &(*Fcb)->SharedSecurity );
|
|
NtfsReleaseFcbSecurity( (*Fcb)->Vcb );
|
|
}
|
|
|
|
//
|
|
// Release the quota control block.
|
|
//
|
|
|
|
if (NtfsPerformQuotaOperation( *Fcb )) {
|
|
NtfsDereferenceQuotaControlBlock( (*Fcb)->Vcb, &(*Fcb)->QuotaControl );
|
|
}
|
|
|
|
//
|
|
// Delete the UsnRecord if one exists.
|
|
//
|
|
|
|
if ((*Fcb)->FcbUsnRecord != NULL) {
|
|
|
|
PUSN_FCB ThisUsn, LastUsn;
|
|
|
|
//
|
|
// See if the Fcb is in one of the Usn blocks.
|
|
//
|
|
|
|
ThisUsn = &IrpContext->Usn;
|
|
|
|
do {
|
|
|
|
if (ThisUsn->CurrentUsnFcb == (*Fcb)) {
|
|
|
|
//
|
|
// Cleanup the UsnFcb in the IrpContext. It's possible that
|
|
// we might want to reuse the UsnFcb later in this request.
|
|
//
|
|
|
|
if (ThisUsn != &IrpContext->Usn) {
|
|
|
|
LastUsn->NextUsnFcb = ThisUsn->NextUsnFcb;
|
|
NtfsFreePool( ThisUsn );
|
|
|
|
} else {
|
|
|
|
ThisUsn->CurrentUsnFcb = NULL;
|
|
ThisUsn->NewReasons = 0;
|
|
ThisUsn->RemovedSourceInfo = 0;
|
|
ThisUsn->UsnFcbFlags = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ThisUsn->NextUsnFcb == NULL) { break; }
|
|
|
|
LastUsn = ThisUsn;
|
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// Remove the Fcb from the list in the Usn journal.
|
|
//
|
|
|
|
if ((*Fcb)->FcbUsnRecord->ModifiedOpenFilesLinks.Flink != NULL) {
|
|
NtfsLockFcb( IrpContext, (*Fcb)->Vcb->UsnJournal->Fcb );
|
|
RemoveEntryList( &(*Fcb)->FcbUsnRecord->ModifiedOpenFilesLinks );
|
|
|
|
if ((*Fcb)->FcbUsnRecord->TimeOutLinks.Flink != NULL) {
|
|
|
|
RemoveEntryList( &(*Fcb)->FcbUsnRecord->TimeOutLinks );
|
|
}
|
|
NtfsUnlockFcb( IrpContext, (*Fcb)->Vcb->UsnJournal->Fcb );
|
|
}
|
|
|
|
NtfsFreePool( (*Fcb)->FcbUsnRecord );
|
|
}
|
|
|
|
//
|
|
// Let our top-level caller know the Fcb was deleted.
|
|
//
|
|
|
|
if ((*Fcb)->FcbContext != NULL) {
|
|
|
|
(*Fcb)->FcbContext->FcbDeleted = TRUE;
|
|
}
|
|
|
|
//
|
|
// Deallocate the Fcb itself
|
|
//
|
|
|
|
if (FlagOn( (*Fcb)->FcbState, FCB_STATE_NONPAGED )) {
|
|
|
|
NtfsFreePool( *Fcb );
|
|
|
|
} else {
|
|
|
|
if (FlagOn( (*Fcb)->FcbState, FCB_STATE_COMPOUND_INDEX )) {
|
|
|
|
ExFreeToPagedLookasideList( &NtfsFcbIndexLookasideList, *Fcb );
|
|
|
|
} else {
|
|
|
|
ExFreeToPagedLookasideList( &NtfsFcbDataLookasideList, *Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Zero out the input pointer
|
|
//
|
|
|
|
*Fcb = NULL;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteFcb -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
PFCB
|
|
NtfsGetNextFcbTableEntry (
|
|
IN PVCB Vcb,
|
|
IN PVOID *RestartKey
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will enumerate through all of the fcb's for the given
|
|
vcb
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb used in this operation
|
|
|
|
RestartKey - This value is used by the table package to maintain
|
|
its position in the enumeration. It is initialized to NULL
|
|
for the first search.
|
|
|
|
Return Value:
|
|
|
|
PFCB - A pointer to the next fcb or NULL if the enumeration is
|
|
completed
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB Fcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
Fcb = (PFCB) RtlEnumerateGenericTableWithoutSplaying( &Vcb->FcbTable, RestartKey );
|
|
|
|
if (Fcb != NULL) {
|
|
|
|
Fcb = ((PFCB_TABLE_ELEMENT)(Fcb))->Fcb;
|
|
}
|
|
|
|
return Fcb;
|
|
}
|
|
|
|
|
|
PSCB
|
|
NtfsCreateScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
|
|
IN PCUNICODE_STRING AttributeName,
|
|
IN BOOLEAN ReturnExistingOnly,
|
|
OUT PBOOLEAN ReturnedExistingScb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates, initializes, and inserts a new Scb record into
|
|
the in memory data structures, provided one does not already exist
|
|
with the identical attribute record.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb to associate the new SCB under.
|
|
|
|
AttributeTypeCode - Supplies the attribute type code for the new Scb
|
|
|
|
AttributeName - Supplies the attribute name for the new Scb, with
|
|
AttributeName->Length == 0 if there is no name.
|
|
|
|
ReturnExistingOnly - If specified as TRUE then only an existing Scb
|
|
will be returned. If no matching Scb exists then NULL is returned.
|
|
|
|
ReturnedExistingScb - Indicates if this procedure found an existing
|
|
Scb with the identical attribute record (variable is set to TRUE)
|
|
or if this procedure needed to create a new Scb (variable is set to
|
|
FALSE).
|
|
|
|
Return Value:
|
|
|
|
PSCB - Returns a pointer to the newly allocated SCB or NULL if there is
|
|
no Scb and ReturnExistingOnly is TRUE.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB Scb;
|
|
NODE_TYPE_CODE NodeTypeCode;
|
|
NODE_BYTE_SIZE NodeByteSize;
|
|
BOOLEAN LocalReturnedExistingScb;
|
|
BOOLEAN PagingIoResource;
|
|
BOOLEAN ModifiedNoWrite;
|
|
#if (defined(NTFS_RWCMP_TRACE) || defined(SYSCACHE) || defined(NTFS_RWC_DEBUG) || defined(SYSCACHE_DEBUG))
|
|
BOOLEAN SyscacheFile = FALSE;
|
|
#endif
|
|
|
|
//
|
|
// The following variables are only used for abnormal termination
|
|
//
|
|
|
|
PVOID UnwindStorage[4];
|
|
POPLOCK UnwindOplock;
|
|
PNTFS_MCB UnwindMcb;
|
|
|
|
PLARGE_MCB UnwindAddedClustersMcb;
|
|
PLARGE_MCB UnwindRemovedClustersMcb;
|
|
|
|
BOOLEAN UnwindFromQueue;
|
|
|
|
BOOLEAN Nonpaged;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_FCB( Fcb );
|
|
|
|
ASSERT( AttributeTypeCode >= $STANDARD_INFORMATION );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateScb\n") );
|
|
|
|
if (!ARGUMENT_PRESENT(ReturnedExistingScb)) { ReturnedExistingScb = &LocalReturnedExistingScb; }
|
|
|
|
//
|
|
// Search the scb queue of the fcb looking for a matching
|
|
// attribute type code and attribute name
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
|
|
Scb = NULL;
|
|
while ((Scb = NtfsGetNextChildScb( Fcb, Scb )) != NULL) {
|
|
|
|
ASSERT_SCB( Scb );
|
|
|
|
//
|
|
// For every scb already in the fcb's queue check for a matching
|
|
// type code and name. If we find a match we return from this
|
|
// procedure right away.
|
|
//
|
|
|
|
if ((AttributeTypeCode == Scb->AttributeTypeCode) &&
|
|
!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED) &&
|
|
NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable,
|
|
&Scb->AttributeName,
|
|
(PUNICODE_STRING) AttributeName,
|
|
FALSE )) {
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
*ReturnedExistingScb = TRUE;
|
|
|
|
if (NtfsIsExclusiveScb(Scb)) {
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", Scb) );
|
|
|
|
return Scb;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the user only wanted an existing Scb then return NULL.
|
|
//
|
|
|
|
if (ReturnExistingOnly) {
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", NULL) );
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// We didn't find it so we are not going to be returning an existing Scb
|
|
// Initialize local variables for later cleanup.
|
|
//
|
|
|
|
PagingIoResource = FALSE;
|
|
ModifiedNoWrite = TRUE;
|
|
UnwindOplock = NULL;
|
|
UnwindMcb = NULL;
|
|
UnwindAddedClustersMcb = NULL;
|
|
UnwindRemovedClustersMcb = NULL;
|
|
UnwindFromQueue = FALSE;
|
|
Nonpaged = FALSE;
|
|
UnwindStorage[0] = NULL;
|
|
UnwindStorage[1] = NULL;
|
|
UnwindStorage[2] = NULL;
|
|
UnwindStorage[3] = NULL;
|
|
|
|
*ReturnedExistingScb = FALSE;
|
|
|
|
try {
|
|
|
|
//
|
|
// Decide the node type and size of the Scb. Also decide if it will be
|
|
// allocated from paged or non-paged pool.
|
|
//
|
|
|
|
if (AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
if (NtfsSegmentNumber( &Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) {
|
|
NodeTypeCode = NTFS_NTC_SCB_ROOT_INDEX;
|
|
} else {
|
|
NodeTypeCode = NTFS_NTC_SCB_INDEX;
|
|
}
|
|
|
|
NodeByteSize = SIZEOF_SCB_INDEX;
|
|
|
|
} else if ((NtfsSegmentNumber( &Fcb->FileReference ) <= MASTER_FILE_TABLE2_NUMBER) &&
|
|
(AttributeTypeCode == $DATA)) {
|
|
|
|
NodeTypeCode = NTFS_NTC_SCB_MFT;
|
|
NodeByteSize = SIZEOF_SCB_MFT;
|
|
|
|
} else {
|
|
|
|
NodeTypeCode = NTFS_NTC_SCB_DATA;
|
|
NodeByteSize = SIZEOF_SCB_DATA;
|
|
|
|
//
|
|
// If this is a user data stream then remember that we need
|
|
// a paging IO resource. Test for the cases where we DONT want
|
|
// to mark this stream as MODIFIED_NO_WRITE.
|
|
//
|
|
// If we need a paging IO resource the file must be a data stream.
|
|
//
|
|
|
|
if ((AttributeTypeCode == $DATA) ||
|
|
(AttributeTypeCode >= $FIRST_USER_DEFINED_ATTRIBUTE)) {
|
|
|
|
//
|
|
// For Data streams in the Root File or non-system files we need
|
|
// a paging IO resource and don't want to mark the file as
|
|
// MODIFIED_NO_WRITE.
|
|
//
|
|
|
|
//
|
|
// We should never reach this point for either the volume bitmap or
|
|
// volume dasd files.
|
|
//
|
|
|
|
ASSERT( (NtfsSegmentNumber( &Fcb->FileReference ) != VOLUME_DASD_NUMBER) &&
|
|
(NtfsSegmentNumber( &Fcb->FileReference ) != BIT_MAP_FILE_NUMBER) );
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
//
|
|
// Make sure that all files in the reserved area are marked as system except
|
|
// the root index.
|
|
//
|
|
|
|
ASSERT( (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER) ||
|
|
(NtfsSegmentNumber( &Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) );
|
|
|
|
ModifiedNoWrite = FALSE;
|
|
PagingIoResource = TRUE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// The scb will come from non-paged if the Fcb is non-paged or
|
|
// it is an attribute list.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_NONPAGED ) || (AttributeTypeCode == $ATTRIBUTE_LIST)) {
|
|
|
|
Scb = UnwindStorage[0] = NtfsAllocatePoolWithTag( NonPagedPool, NodeByteSize, 'nftN' );
|
|
Nonpaged = TRUE;
|
|
|
|
} else if (AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
//
|
|
// If the Fcb is an INDEX Fcb and the Scb is unused, then
|
|
// use that. Otherwise allocate from the lookaside list.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
|
|
(SafeNodeType( &((PFCB_INDEX) Fcb)->Scb ) == 0)) {
|
|
|
|
Scb = (PSCB) &((PFCB_INDEX) Fcb)->Scb;
|
|
|
|
} else {
|
|
|
|
Scb = UnwindStorage[0] = (PSCB)NtfsAllocatePoolWithTag( PagedPool, SIZEOF_SCB_INDEX, 'SftN' );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (((IrpContext->OriginatingIrp != NULL) &&
|
|
(FsRtlIsSyscacheFile( IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject )))) {
|
|
|
|
|
|
KdPrint( ("NTFS: Found syscache dir: fo:0x%x scb:0x%x filref: 0x%x\n",
|
|
IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject,
|
|
Scb,
|
|
NtfsUnsafeSegmentNumber( &Fcb->FileReference )) );
|
|
SyscacheFile = TRUE;
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
|
|
//
|
|
// We can use the Scb field in the Fcb in all cases if it is
|
|
// unused. We will only use it for a data stream since
|
|
// it will have the longest life.
|
|
//
|
|
|
|
ASSERT( FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) ||
|
|
FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_DATA ));
|
|
|
|
if ((AttributeTypeCode == $DATA) &&
|
|
(SafeNodeType( &((PFCB_INDEX) Fcb)->Scb ) == 0)) {
|
|
|
|
Scb = (PSCB) &((PFCB_INDEX) Fcb)->Scb;
|
|
|
|
} else {
|
|
|
|
Scb = UnwindStorage[0] = (PSCB)ExAllocateFromPagedLookasideList( &NtfsScbDataLookasideList );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (((IrpContext->OriginatingIrp != NULL) &&
|
|
(FsRtlIsSyscacheFile( IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject )))) {
|
|
|
|
|
|
KdPrint( ("NTFS: Found syscache file: fo:0x%x scb:0x%x filref: 0x%x\n",
|
|
IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject,
|
|
Scb,
|
|
NtfsUnsafeSegmentNumber( &Fcb->FileReference )) );
|
|
SyscacheFile = TRUE;
|
|
}
|
|
|
|
if (!IsListEmpty( &Fcb->LcbQueue )) {
|
|
PLCB Lcb = (PLCB) CONTAINING_RECORD( Fcb->LcbQueue.Flink, LCB, FcbLinks.Flink );
|
|
|
|
while (TRUE) {
|
|
|
|
if ((Lcb->Scb != NULL) &&
|
|
(FlagOn( Lcb->Scb->ScbPersist, SCB_PERSIST_SYSCACHE_DIR ))) {
|
|
|
|
SyscacheFile = TRUE;
|
|
KdPrint( ("NTFS: Found syscache file in dir: fo:0x%x scb:0x%x filref: 0x%x\n",
|
|
IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject,
|
|
Scb,
|
|
NtfsUnsafeSegmentNumber( &Fcb->FileReference )) );
|
|
}
|
|
|
|
if (Lcb->FcbLinks.Flink != &Fcb->LcbQueue) {
|
|
Lcb = (PLCB)CONTAINING_RECORD( Lcb->FcbLinks.Flink, LCB, FcbLinks.Flink );
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if (defined(NTFS_RWCMP_TRACE) || defined(SYSCACHE) || defined(NTFS_RWC_DEBUG))
|
|
if (( IrpContext->OriginatingIrp != NULL)
|
|
(FsRtlIsSyscacheFile(IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject))) {
|
|
|
|
SyscacheFile = TRUE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Store the Scb address and zero it out.
|
|
//
|
|
|
|
RtlZeroMemory( Scb, NodeByteSize );
|
|
|
|
#if (defined(NTFS_RWCMP_TRACE) || defined(SYSCACHE) || defined(NTFS_RWC_DEBUG))
|
|
if (SyscacheFile) {
|
|
SetFlag( Scb->ScbState, SCB_STATE_SYSCACHE_FILE );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Set the proper node type code and byte size
|
|
//
|
|
|
|
Scb->Header.NodeTypeCode = NodeTypeCode;
|
|
Scb->Header.NodeByteSize = NodeByteSize;
|
|
|
|
//
|
|
// Set a back pointer to the resource we will be using
|
|
//
|
|
|
|
Scb->Header.Resource = Fcb->Resource;
|
|
|
|
//
|
|
// Decide if we will be using the PagingIoResource
|
|
//
|
|
|
|
if (PagingIoResource) {
|
|
|
|
PERESOURCE NewResource;
|
|
|
|
//
|
|
// Initialize it in the Fcb if it is not already there, and
|
|
// setup the pointer and flag in the Scb.
|
|
//
|
|
|
|
if (Fcb->PagingIoResource == NULL) {
|
|
|
|
//
|
|
// If this is a superseding open and our caller wants
|
|
// to acquire the paging io resource, then do it now.
|
|
// We could be in a state where there was no paging
|
|
// IO resource when we acquired the Fcb but will need
|
|
// it if this transaction needs to be unwound.
|
|
//
|
|
|
|
NewResource = NtfsAllocateEresource();
|
|
|
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) &&
|
|
(IrpContext->MajorFunction == IRP_MJ_CREATE) &&
|
|
(IrpContext->OriginatingIrp != NULL) &&
|
|
(IrpContext->CleanupStructure == NULL)) {
|
|
|
|
ExAcquireResourceExclusiveLite( NewResource, TRUE );
|
|
IrpContext->CleanupStructure = Fcb;
|
|
}
|
|
|
|
Fcb->PagingIoResource = NewResource;
|
|
}
|
|
|
|
Scb->Header.PagingIoResource = Fcb->PagingIoResource;
|
|
}
|
|
|
|
//
|
|
// Insert this Scb into our parents scb queue, and point back to
|
|
// our parent fcb and vcb. Put this entry at the head of the list.
|
|
// Any Scb on the delayed close queue goes to the end of the list.
|
|
//
|
|
|
|
InsertHeadList( &Fcb->ScbQueue, &Scb->FcbLinks );
|
|
UnwindFromQueue = TRUE;
|
|
|
|
Scb->Fcb = Fcb;
|
|
Scb->Vcb = Fcb->Vcb;
|
|
|
|
//
|
|
// If the attribute name exists then allocate a buffer for the
|
|
// attribute name and iniitalize it.
|
|
//
|
|
|
|
if (AttributeName->Length != 0) {
|
|
|
|
//
|
|
// The typical case is the $I30 string. If this matches then
|
|
// point to a common string.
|
|
//
|
|
|
|
if ((AttributeName->Length == NtfsFileNameIndex.Length) &&
|
|
(RtlEqualMemory( AttributeName->Buffer,
|
|
NtfsFileNameIndex.Buffer,
|
|
AttributeName->Length ) )) {
|
|
|
|
Scb->AttributeName = NtfsFileNameIndex;
|
|
|
|
} else {
|
|
|
|
Scb->AttributeName.Length = AttributeName->Length;
|
|
Scb->AttributeName.MaximumLength = (USHORT)(AttributeName->Length + sizeof( WCHAR ));
|
|
|
|
Scb->AttributeName.Buffer = UnwindStorage[1] =
|
|
NtfsAllocatePool(PagedPool, AttributeName->Length + sizeof( WCHAR ));
|
|
|
|
RtlCopyMemory( Scb->AttributeName.Buffer, AttributeName->Buffer, AttributeName->Length );
|
|
Scb->AttributeName.Buffer[AttributeName->Length / sizeof( WCHAR )] = L'\0';
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the attribute Type Code
|
|
//
|
|
|
|
Scb->AttributeTypeCode = AttributeTypeCode;
|
|
if (NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode ) &&
|
|
!FlagOn( Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA );
|
|
}
|
|
|
|
//
|
|
// If this is an Mft Scb then initialize the cluster Mcb's.
|
|
//
|
|
|
|
if (NodeTypeCode == NTFS_NTC_SCB_MFT) {
|
|
|
|
FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.AddedClusters, NonPagedPool );
|
|
UnwindAddedClustersMcb = &Scb->ScbType.Mft.AddedClusters;
|
|
|
|
FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.RemovedClusters, NonPagedPool );
|
|
UnwindRemovedClustersMcb = &Scb->ScbType.Mft.RemovedClusters;
|
|
}
|
|
|
|
//
|
|
// Get the mutex for the Scb. We may be able to use the one in the Fcb.
|
|
// We can if the Scb is paged.
|
|
//
|
|
|
|
if (Nonpaged) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_NONPAGED );
|
|
UnwindStorage[3] =
|
|
Scb->Header.FastMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
|
|
ExInitializeFastMutex( Scb->Header.FastMutex );
|
|
|
|
} else {
|
|
|
|
Scb->Header.FastMutex = Fcb->FcbMutex;
|
|
}
|
|
|
|
//
|
|
// Initialize the FCB advanced header. Note that the mutex
|
|
// has already been setup (just above) so we don't re-setitup
|
|
// here. We will not support filter contexts for paging files
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
FsRtlSetupAdvancedHeader( &Scb->Header, NULL );
|
|
} else {
|
|
SetFlag( Scb->Header.Flags, FSRTL_FLAG_ADVANCED_HEADER );
|
|
}
|
|
|
|
//
|
|
// Allocate the Nonpaged portion of the Scb.
|
|
//
|
|
|
|
Scb->NonpagedScb =
|
|
UnwindStorage[2] = (PSCB_NONPAGED)ExAllocateFromNPagedLookasideList( &NtfsScbNonpagedLookasideList );
|
|
|
|
RtlZeroMemory( Scb->NonpagedScb, sizeof( SCB_NONPAGED ));
|
|
|
|
Scb->NonpagedScb->NodeTypeCode = NTFS_NTC_SCB_NONPAGED;
|
|
Scb->NonpagedScb->NodeByteSize = sizeof( SCB_NONPAGED );
|
|
Scb->NonpagedScb->Vcb = Scb->Vcb;
|
|
|
|
//
|
|
// Fill in the advanced fields
|
|
//
|
|
|
|
Scb->Header.PendingEofAdvances = &Scb->EofListHead;
|
|
InitializeListHead( &Scb->EofListHead );
|
|
|
|
NtfsInitializeNtfsMcb( &Scb->Mcb,
|
|
&Scb->Header,
|
|
&Scb->McbStructs,
|
|
FlagOn( Scb->ScbState, SCB_STATE_NONPAGED )
|
|
? NonPagedPool : PagedPool);
|
|
|
|
UnwindMcb = &Scb->Mcb;
|
|
|
|
InitializeListHead( &Scb->CcbQueue );
|
|
|
|
//
|
|
// Do that data stream specific initialization.
|
|
//
|
|
|
|
if (NodeTypeCode == NTFS_NTC_SCB_DATA) {
|
|
|
|
FsRtlInitializeOplock( &Scb->ScbType.Data.Oplock );
|
|
UnwindOplock = &Scb->ScbType.Data.Oplock;
|
|
InitializeListHead( &Scb->ScbType.Data.WaitForNewLength );
|
|
#ifdef COMPRESS_ON_WIRE
|
|
InitializeListHead( &Scb->ScbType.Data.CompressionSyncList );
|
|
#endif
|
|
|
|
//
|
|
// Set a flag if this is the Usn Journal.
|
|
//
|
|
|
|
if (!PagingIoResource &&
|
|
(*((PLONGLONG) &Fcb->Vcb->UsnJournalReference) == *((PLONGLONG) &Fcb->FileReference)) &&
|
|
(AttributeName->Length == JournalStreamName.Length) &&
|
|
RtlEqualMemory( AttributeName->Buffer,
|
|
JournalStreamName.Buffer,
|
|
JournalStreamName.Length )) {
|
|
|
|
SetFlag( Scb->ScbPersist, SCB_PERSIST_USN_JOURNAL );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (SyscacheFile)
|
|
{
|
|
|
|
Scb->LogSetNumber = InterlockedIncrement( &NtfsCurrentSyscacheLogSet ) % NUM_SC_LOGSETS;
|
|
NtfsSyscacheLogSet[Scb->LogSetNumber].Scb = Scb;
|
|
NtfsSyscacheLogSet[Scb->LogSetNumber].SegmentNumberUnsafe =
|
|
NtfsUnsafeSegmentNumber( &Fcb->FileReference );
|
|
|
|
if (NtfsSyscacheLogSet[Scb->LogSetNumber].SyscacheLog == NULL) {
|
|
NtfsSyscacheLogSet[Scb->LogSetNumber].SyscacheLog = NtfsAllocatePoolWithTagNoRaise( NonPagedPool, sizeof(SYSCACHE_LOG) * NUM_SC_EVENTS, ' neB' );
|
|
}
|
|
Scb->SyscacheLog = NtfsSyscacheLogSet[Scb->LogSetNumber].SyscacheLog;
|
|
Scb->SyscacheLogEntryCount = NUM_SC_EVENTS;
|
|
Scb->CurrentSyscacheLogEntry = -1;
|
|
|
|
//
|
|
// Degrade gracefully if no memory
|
|
//
|
|
|
|
if (!Scb->SyscacheLog) {
|
|
Scb->SyscacheLogEntryCount = 0;
|
|
} else {
|
|
memset( Scb->SyscacheLog, 0x61626162, sizeof( SYSCACHE_LOG ) * NUM_SC_EVENTS );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
|
|
//
|
|
// There is a deallocated queue for indexes and the Mft.
|
|
//
|
|
|
|
InitializeListHead( &Scb->ScbType.Index.RecentlyDeallocatedQueue );
|
|
|
|
//
|
|
// Initialize index-specific fields.
|
|
//
|
|
|
|
if (AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
InitializeListHead( &Scb->ScbType.Index.LcbQueue );
|
|
}
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
if (SyscacheFile) {
|
|
SetFlag( Scb->ScbPersist, SCB_PERSIST_SYSCACHE_DIR );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// If this Scb should be marked as containing Lsn's or
|
|
// Update Sequence Arrays, do so now.
|
|
//
|
|
|
|
NtfsCheckScbForCache( Scb );
|
|
|
|
//
|
|
// We shouldn't make this call during restart.
|
|
//
|
|
|
|
ASSERT( !FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ));
|
|
|
|
//
|
|
// Set the flag indicating that we want the Mapped Page Writer out of this file.
|
|
//
|
|
|
|
if (ModifiedNoWrite) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
|
|
}
|
|
|
|
//
|
|
// Let's make sure we caught all of the interesting cases.
|
|
//
|
|
|
|
ASSERT( ModifiedNoWrite ?
|
|
(((Scb->AttributeTypeCode != $DATA) ||
|
|
FlagOn( Scb->ScbState, SCB_STATE_USA_PRESENT ) ||
|
|
FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE ))) :
|
|
(((Scb->AttributeTypeCode == $DATA) &&
|
|
!FlagOn( Scb->ScbState, SCB_STATE_USA_PRESENT ) &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE ))) );
|
|
|
|
//
|
|
// Decide whether this is a view index and set
|
|
// the appropriate scb state bit accordingly.
|
|
//
|
|
|
|
if (FlagOn( Fcb->Info.FileAttributes, DUP_VIEW_INDEX_PRESENT ) &&
|
|
(Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
|
|
(Scb->AttributeName.Buffer != NtfsFileNameIndex.Buffer)) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_VIEW_INDEX );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateScb );
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
if (UnwindFromQueue) { RemoveEntryList( &Scb->FcbLinks ); }
|
|
if (UnwindMcb != NULL) { NtfsUninitializeNtfsMcb( UnwindMcb ); }
|
|
|
|
if (UnwindAddedClustersMcb != NULL) { FsRtlUninitializeLargeMcb( UnwindAddedClustersMcb ); }
|
|
if (UnwindRemovedClustersMcb != NULL) { FsRtlUninitializeLargeMcb( UnwindRemovedClustersMcb ); }
|
|
if (UnwindOplock != NULL) { FsRtlUninitializeOplock( UnwindOplock ); }
|
|
if (UnwindStorage[0]) { NtfsFreePool( UnwindStorage[0] );
|
|
} else if (Scb != NULL) { Scb->Header.NodeTypeCode = 0; }
|
|
if (UnwindStorage[1]) { NtfsFreePool( UnwindStorage[1] ); }
|
|
if (UnwindStorage[2]) { NtfsFreePool( UnwindStorage[2] ); }
|
|
if (UnwindStorage[3]) { NtfsFreePool( UnwindStorage[3] ); }
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", Scb) );
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
ASSERT( SyscacheFile || (Scb->SyscacheLogEntryCount == 0 && Scb->SyscacheLog == 0 ));
|
|
#endif
|
|
|
|
return Scb;
|
|
}
|
|
|
|
|
|
PSCB
|
|
NtfsCreatePrerestartScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFILE_REFERENCE FileReference,
|
|
IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
|
|
IN PUNICODE_STRING AttributeName OPTIONAL,
|
|
IN ULONG BytesPerIndexBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates, initializes, and inserts a new Scb record into
|
|
the in memory data structures, provided one does not already exist
|
|
with the identical attribute record. It does this on the FcbTable
|
|
off of the Vcb. If necessary this routine will also create the fcb
|
|
if one does not already exist for the indicated file reference.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb to associate the new SCB under.
|
|
|
|
FileReference - Supplies the file reference for the new SCB this is
|
|
used to identify/create a new lookaside Fcb.
|
|
|
|
AttributeTypeCode - Supplies the attribute type code for the new SCB
|
|
|
|
AttributeName - Supplies the optional attribute name of the SCB
|
|
|
|
BytesPerIndexBuffer - For index Scbs, this must specify the bytes per
|
|
index buffer.
|
|
|
|
Return Value:
|
|
|
|
PSCB - Returns a pointer to the newly allocated SCB
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB Scb;
|
|
PFCB Fcb;
|
|
|
|
NODE_TYPE_CODE NodeTypeCode;
|
|
NODE_BYTE_SIZE NodeByteSize;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
ASSERT( AttributeTypeCode >= $STANDARD_INFORMATION );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreatePrerestartScb\n") );
|
|
|
|
//
|
|
// Use a try-finally to release the Fcb table.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
|
|
try {
|
|
|
|
//
|
|
// First make sure we have an Fcb of the proper file reference
|
|
// and indicate that it is from prerestart
|
|
//
|
|
|
|
Fcb = NtfsCreateFcb( IrpContext,
|
|
Vcb,
|
|
*FileReference,
|
|
FALSE,
|
|
TRUE,
|
|
NULL );
|
|
} finally {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// Search the child scbs of this fcb for a matching Scb (based on
|
|
// attribute type code and attribute name) if one is not found then
|
|
// we'll create a new scb. When we exit the following loop if the
|
|
// scb pointer to not null then we've found a preexisting scb.
|
|
//
|
|
|
|
Scb = NULL;
|
|
while ((Scb = NtfsGetNextChildScb(Fcb, Scb)) != NULL) {
|
|
|
|
ASSERT_SCB( Scb );
|
|
|
|
//
|
|
// The the attribute type codes match and if supplied the name also
|
|
// matches then we got our scb
|
|
//
|
|
|
|
if (Scb->AttributeTypeCode == AttributeTypeCode) {
|
|
|
|
if (!ARGUMENT_PRESENT( AttributeName )) {
|
|
|
|
if (Scb->AttributeName.Length == 0) {
|
|
|
|
break;
|
|
}
|
|
|
|
} else if (AttributeName->Length == 0
|
|
&& Scb->AttributeName.Length == 0) {
|
|
|
|
break;
|
|
|
|
} else if (NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable,
|
|
AttributeName,
|
|
&Scb->AttributeName,
|
|
FALSE )) { // Ignore Case
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If scb now null then we need to create a minimal scb. We always allocate
|
|
// these out of non-paged pool.
|
|
//
|
|
|
|
if (Scb == NULL) {
|
|
|
|
BOOLEAN ShareScb = FALSE;
|
|
|
|
//
|
|
// Allocate new scb and zero it out and set the node type code and byte size.
|
|
//
|
|
|
|
if (AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
if (NtfsSegmentNumber( FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) {
|
|
|
|
NodeTypeCode = NTFS_NTC_SCB_ROOT_INDEX;
|
|
} else {
|
|
NodeTypeCode = NTFS_NTC_SCB_INDEX;
|
|
}
|
|
|
|
NodeByteSize = SIZEOF_SCB_INDEX;
|
|
|
|
} else if (NtfsSegmentNumber( FileReference ) <= MASTER_FILE_TABLE2_NUMBER
|
|
&& (AttributeTypeCode == $DATA)) {
|
|
|
|
NodeTypeCode = NTFS_NTC_SCB_MFT;
|
|
NodeByteSize = SIZEOF_SCB_MFT;
|
|
|
|
} else {
|
|
|
|
NodeTypeCode = NTFS_NTC_SCB_DATA;
|
|
NodeByteSize = SIZEOF_SCB_DATA;
|
|
}
|
|
|
|
Scb = NtfsAllocatePoolWithTag( NonPagedPool, NodeByteSize, 'tftN' );
|
|
|
|
RtlZeroMemory( Scb, NodeByteSize );
|
|
|
|
//
|
|
// Fill in the node type code and size.
|
|
//
|
|
|
|
Scb->Header.NodeTypeCode = NodeTypeCode;
|
|
Scb->Header.NodeByteSize = NodeByteSize;
|
|
|
|
//
|
|
// Show that all of the Scb's are from nonpaged pool.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_NONPAGED );
|
|
|
|
//
|
|
// Initialize all of the fields that don't require allocations
|
|
// first. We want to make sure we don't leave the Scb in
|
|
// a state that could cause a crash during Scb teardown.
|
|
//
|
|
|
|
//
|
|
// Set a back pointer to the resource we will be using
|
|
//
|
|
|
|
Scb->Header.Resource = Fcb->Resource;
|
|
|
|
//
|
|
// Insert this scb into our parents scb queue and point back to our
|
|
// parent fcb and vcb. Put this entry at the head of the list.
|
|
// Any Scb on the delayed close queue goes to the end of the list.
|
|
//
|
|
|
|
InsertHeadList( &Fcb->ScbQueue, &Scb->FcbLinks );
|
|
|
|
Scb->Fcb = Fcb;
|
|
Scb->Vcb = Vcb;
|
|
|
|
InitializeListHead( &Scb->CcbQueue );
|
|
|
|
//
|
|
// Set the attribute type code recently deallocated information structures.
|
|
//
|
|
|
|
Scb->AttributeTypeCode = AttributeTypeCode;
|
|
|
|
//
|
|
// Fill in the advanced fields
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
|
|
FsRtlSetupAdvancedHeader( &Scb->Header, NULL );
|
|
} else {
|
|
SetFlag( Scb->Header.Flags, FSRTL_FLAG_ADVANCED_HEADER );
|
|
}
|
|
|
|
Scb->Header.PendingEofAdvances = &Scb->EofListHead;
|
|
InitializeListHead( &Scb->EofListHead );
|
|
|
|
//
|
|
// Do that data stream specific initialization.
|
|
//
|
|
|
|
if (NodeTypeCode == NTFS_NTC_SCB_DATA) {
|
|
|
|
FsRtlInitializeOplock( &Scb->ScbType.Data.Oplock );
|
|
InitializeListHead( &Scb->ScbType.Data.WaitForNewLength );
|
|
#ifdef COMPRESS_ON_WIRE
|
|
InitializeListHead( &Scb->ScbType.Data.CompressionSyncList );
|
|
#endif
|
|
|
|
//
|
|
// Set a flag if this is the Usn Journal.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( AttributeName ) &&
|
|
(*((PLONGLONG) &Vcb->UsnJournalReference) == *((PLONGLONG) &Fcb->FileReference)) &&
|
|
(AttributeName->Length == JournalStreamName.Length) &&
|
|
RtlEqualMemory( AttributeName->Buffer,
|
|
JournalStreamName.Buffer,
|
|
JournalStreamName.Length )) {
|
|
|
|
SetFlag( Scb->ScbPersist, SCB_PERSIST_USN_JOURNAL );
|
|
}
|
|
|
|
#ifdef SYSCACHE
|
|
InitializeListHead( &Scb->ScbType.Data.SyscacheEventList );
|
|
#endif
|
|
} else {
|
|
|
|
//
|
|
// There is a deallocated queue for indexes and the Mft.
|
|
//
|
|
|
|
InitializeListHead( &Scb->ScbType.Index.RecentlyDeallocatedQueue );
|
|
|
|
//
|
|
// Initialize index-specific fields.
|
|
//
|
|
|
|
if (AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
Scb->ScbType.Index.BytesPerIndexBuffer = BytesPerIndexBuffer;
|
|
|
|
InitializeListHead( &Scb->ScbType.Index.LcbQueue );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is an Mft Scb then initialize the cluster Mcb's.
|
|
//
|
|
|
|
if (NodeTypeCode == NTFS_NTC_SCB_MFT) {
|
|
|
|
FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.AddedClusters, NonPagedPool );
|
|
|
|
FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.RemovedClusters, NonPagedPool );
|
|
}
|
|
|
|
Scb->NonpagedScb = (PSCB_NONPAGED)ExAllocateFromNPagedLookasideList( &NtfsScbNonpagedLookasideList );
|
|
|
|
RtlZeroMemory( Scb->NonpagedScb, sizeof( SCB_NONPAGED ));
|
|
|
|
Scb->NonpagedScb->NodeTypeCode = NTFS_NTC_SCB_NONPAGED;
|
|
Scb->NonpagedScb->NodeByteSize = sizeof( SCB_NONPAGED );
|
|
Scb->NonpagedScb->Vcb = Vcb;
|
|
|
|
//
|
|
// Allocate and insert the mutext into the advanced header. This is
|
|
// done now (instead of up with the call to FsRtlSetupAdvancedHeader)
|
|
// to guarentee the existing order during initilization.
|
|
//
|
|
|
|
Scb->Header.FastMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
|
|
ExInitializeFastMutex( Scb->Header.FastMutex );
|
|
|
|
NtfsInitializeNtfsMcb( &Scb->Mcb, &Scb->Header, &Scb->McbStructs, NonPagedPool );
|
|
|
|
//
|
|
// If the attribute name is present and the name length is greater than 0
|
|
// then allocate a buffer for the attribute name and initialize it.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( AttributeName ) && (AttributeName->Length != 0)) {
|
|
|
|
//
|
|
// The typical case is the $I30 string. If this matches then
|
|
// point to a common string.
|
|
//
|
|
|
|
if ((AttributeName->Length == NtfsFileNameIndex.Length) &&
|
|
(RtlEqualMemory( AttributeName->Buffer,
|
|
NtfsFileNameIndex.Buffer,
|
|
AttributeName->Length ) )) {
|
|
|
|
Scb->AttributeName = NtfsFileNameIndex;
|
|
|
|
} else {
|
|
|
|
Scb->AttributeName.Length = AttributeName->Length;
|
|
Scb->AttributeName.MaximumLength = (USHORT)(AttributeName->Length + sizeof( WCHAR ));
|
|
|
|
Scb->AttributeName.Buffer = NtfsAllocatePool(PagedPool, AttributeName->Length + sizeof( WCHAR ));
|
|
|
|
RtlCopyMemory( Scb->AttributeName.Buffer, AttributeName->Buffer, AttributeName->Length );
|
|
Scb->AttributeName.Buffer[AttributeName->Length / sizeof( WCHAR )] = L'\0';
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this Scb should be marked as containing Lsn's or
|
|
// Update Sequence Arrays, do so now.
|
|
//
|
|
|
|
NtfsCheckScbForCache( Scb );
|
|
|
|
//
|
|
// Always mark the prerestart Scb's as MODIFIED_NO_WRITE.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreatePrerestartScb -> %08lx\n", Scb) );
|
|
|
|
return Scb;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsFreeScbAttributeName (
|
|
IN PWSTR AttributeNameBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees the pool used by an Scb attribute name iff it is
|
|
not one of the default system attribute names.
|
|
|
|
Arguments:
|
|
|
|
AttributeName - Supplies the attribute name buffer to free
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
if ((AttributeNameBuffer != NULL) &&
|
|
(AttributeNameBuffer != NtfsFileNameIndex.Buffer) &&
|
|
(AttributeNameBuffer != NtfsObjId.Buffer) &&
|
|
(AttributeNameBuffer != NtfsQuota.Buffer)) {
|
|
|
|
NtfsFreePool( AttributeNameBuffer );
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PSCB *Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deallocates and removes an Scb record
|
|
from Ntfs's in-memory data structures. It assume that is does not have
|
|
any children lcb emanating from it.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the SCB to be removed
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
POPEN_ATTRIBUTE_ENTRY AttributeEntry;
|
|
USHORT ThisNodeType;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( *Scb );
|
|
ASSERT( (*Scb)->CleanupCount == 0 );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteScb, *Scb = %08lx\n", *Scb) );
|
|
|
|
Fcb = (*Scb)->Fcb;
|
|
Vcb = Fcb->Vcb;
|
|
|
|
RemoveEntryList( &(*Scb)->FcbLinks );
|
|
|
|
ThisNodeType = SafeNodeType( *Scb );
|
|
|
|
//
|
|
// If this is a bitmap Scb for a directory then make sure the record
|
|
// allocation structure is uninitialized. Otherwise we will leave a
|
|
// stale pointer for the record allocation package.
|
|
//
|
|
|
|
if (((*Scb)->AttributeTypeCode == $BITMAP) &&
|
|
IsDirectory( &Fcb->Info)) {
|
|
|
|
PLIST_ENTRY Links;
|
|
PSCB IndexAllocationScb;
|
|
|
|
Links = Fcb->ScbQueue.Flink;
|
|
|
|
while (Links != &Fcb->ScbQueue) {
|
|
|
|
IndexAllocationScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
if (IndexAllocationScb->AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
NtfsUninitializeRecordAllocation( IrpContext,
|
|
&IndexAllocationScb->ScbType.Index.RecordAllocationContext );
|
|
|
|
IndexAllocationScb->ScbType.Index.AllocationInitialized = FALSE;
|
|
|
|
break;
|
|
}
|
|
|
|
Links = Links->Flink;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark our entry in the Open Attribute Table as free,
|
|
// although it will not be deleted until some future
|
|
// checkpoint. Log this change as well, as long as the
|
|
// log file is active.
|
|
//
|
|
|
|
if (((*Scb)->NonpagedScb != NULL) &&
|
|
((*Scb)->NonpagedScb->OpenAttributeTableIndex != 0)) {
|
|
|
|
NtfsAcquireSharedRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|
AttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
|
|
(*Scb)->NonpagedScb->OpenAttributeTableIndex );
|
|
AttributeEntry->OatData->Overlay.Scb = NULL;
|
|
|
|
if ((*Scb)->AttributeName.Buffer != NULL) {
|
|
|
|
AttributeEntry->OatData->AttributeNamePresent = TRUE;
|
|
}
|
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|
|
|
//
|
|
// "Steal" the name, and let it belong to the Open Attribute Table
|
|
// entry and deallocate it only during checkpoints.
|
|
//
|
|
|
|
(*Scb)->AttributeName.Buffer = NULL;
|
|
}
|
|
|
|
//
|
|
// Uninitialize the file lock and oplock variables if this
|
|
// a data Scb. For the index case make sure that the lcb queue
|
|
// is empty. If this is for an Mft Scb then uninitialize the
|
|
// allocation Mcb's.
|
|
//
|
|
|
|
NtfsUninitializeNtfsMcb( &(*Scb)->Mcb );
|
|
|
|
if (ThisNodeType == NTFS_NTC_SCB_DATA ) {
|
|
|
|
FsRtlUninitializeOplock( &(*Scb)->ScbType.Data.Oplock );
|
|
|
|
if ((*Scb)->ScbType.Data.FileLock != NULL) {
|
|
|
|
FsRtlFreeFileLock( (*Scb)->ScbType.Data.FileLock );
|
|
}
|
|
|
|
#ifdef NTFS_RWC_DEBUG
|
|
|
|
ASSERT( IsListEmpty( &(*Scb)->ScbType.Data.CompressionSyncList ));
|
|
if ((*Scb)->ScbType.Data.HistoryBuffer != NULL) {
|
|
|
|
NtfsFreePool( (*Scb)->ScbType.Data.HistoryBuffer );
|
|
(*Scb)->ScbType.Data.HistoryBuffer = NULL;
|
|
}
|
|
#endif
|
|
} else if (ThisNodeType != NTFS_NTC_SCB_MFT) {
|
|
|
|
//
|
|
// Walk through and remove any Lcb's from the queue.
|
|
//
|
|
|
|
while (!IsListEmpty( &(*Scb)->ScbType.Index.LcbQueue )) {
|
|
|
|
PLCB NextLcb;
|
|
|
|
NextLcb = CONTAINING_RECORD( (*Scb)->ScbType.Index.LcbQueue.Flink,
|
|
LCB,
|
|
ScbLinks );
|
|
|
|
NtfsDeleteLcb( IrpContext, &NextLcb );
|
|
}
|
|
|
|
if ((*Scb)->ScbType.Index.NormalizedName.Buffer != NULL) {
|
|
|
|
NtfsDeleteNormalizedName( *Scb );
|
|
}
|
|
|
|
} else {
|
|
|
|
FsRtlUninitializeLargeMcb( &(*Scb)->ScbType.Mft.AddedClusters );
|
|
FsRtlUninitializeLargeMcb( &(*Scb)->ScbType.Mft.RemovedClusters );
|
|
}
|
|
|
|
if ((*Scb)->EncryptionContext != NULL) {
|
|
|
|
//
|
|
// Let the encryption driver do anything necessary to clean up
|
|
// its private data structures.
|
|
//
|
|
|
|
if (NtfsData.EncryptionCallBackTable.CleanUp != NULL) {
|
|
|
|
NtfsData.EncryptionCallBackTable.CleanUp( &(*Scb)->EncryptionContext );
|
|
}
|
|
|
|
//
|
|
// If the encryption driver didn't clear this in its cleanup routine,
|
|
// or if there is no cleanup routine registered, we should free any
|
|
// for the encryption context ourselves.
|
|
//
|
|
|
|
if ((*Scb)->EncryptionContext != NULL) {
|
|
|
|
NtfsFreePool( (*Scb)->EncryptionContext );
|
|
(*Scb)->EncryptionContext = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Show there is no longer a snapshot Scb, if there is a snapshot.
|
|
// We rely on the snapshot package to correctly recognize the
|
|
// the case where the Scb field is gone.
|
|
//
|
|
|
|
if ((*Scb)->ScbSnapshot != NULL) {
|
|
|
|
(*Scb)->ScbSnapshot->Scb = NULL;
|
|
}
|
|
|
|
//
|
|
// Cleanup Filesystem Filter contexts (this was moved to the point
|
|
// before the FastMutex is freed because this routine now uses it)
|
|
//
|
|
|
|
if (FlagOn( (*Scb)->Header.Flags2, FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS )) {
|
|
|
|
FsRtlTeardownPerStreamContexts( (PFSRTL_ADVANCED_FCB_HEADER)&(*Scb)->Header );
|
|
}
|
|
|
|
//
|
|
// Deallocate the fast mutex if not in the Fcb.
|
|
//
|
|
|
|
if (((*Scb)->Header.FastMutex != (*Scb)->Fcb->FcbMutex) &&
|
|
((*Scb)->Header.FastMutex != NULL)) {
|
|
|
|
NtfsFreePool( (*Scb)->Header.FastMutex );
|
|
}
|
|
|
|
//
|
|
// Deallocate the non-paged scb.
|
|
//
|
|
|
|
if ((*Scb)->NonpagedScb != NULL) {
|
|
|
|
ExFreeToNPagedLookasideList( &NtfsScbNonpagedLookasideList, (*Scb)->NonpagedScb );
|
|
}
|
|
|
|
//
|
|
// Deallocate the attribute name.
|
|
//
|
|
|
|
NtfsFreeScbAttributeName( (*Scb)->AttributeName.Buffer );
|
|
|
|
//
|
|
// See if CollationData is to be deleted.
|
|
//
|
|
|
|
if (FlagOn((*Scb)->ScbState, SCB_STATE_DELETE_COLLATION_DATA)) {
|
|
NtfsFreePool((*Scb)->ScbType.Index.CollationData);
|
|
}
|
|
|
|
//
|
|
// Always directly free the Mft and non-paged Scb's.
|
|
//
|
|
|
|
if (FlagOn( (*Scb)->ScbState, SCB_STATE_NONPAGED ) ||
|
|
(ThisNodeType == NTFS_NTC_SCB_MFT)) {
|
|
|
|
NtfsFreePool( *Scb );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Free any final reserved clusters for data Scb's.
|
|
//
|
|
|
|
|
|
if (ThisNodeType == NTFS_NTC_SCB_DATA) {
|
|
|
|
//
|
|
// Free the reserved bitmap and reserved clusters if present.
|
|
//
|
|
|
|
if ((*Scb)->ScbType.Data.ReservedBitMap != NULL) {
|
|
NtfsDeleteReservedBitmap( *Scb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now free the Scb itself.
|
|
//
|
|
// Check if this is an embedded Scb. This could be part of either an INDEX_FCB
|
|
// or a DATA_FCB. We depend on the fact that the Scb would be in the same
|
|
// location in either case.
|
|
//
|
|
|
|
if ((*Scb) == (PSCB) &((PFCB_DATA) (*Scb)->Fcb)->Scb) {
|
|
|
|
(*Scb)->Header.NodeTypeCode = 0;
|
|
|
|
} else if (SafeNodeType( *Scb ) == NTFS_NTC_SCB_DATA) {
|
|
|
|
ExFreeToPagedLookasideList( &NtfsScbDataLookasideList, *Scb );
|
|
|
|
} else {
|
|
|
|
NtfsFreePool( *Scb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Zero out the input pointer
|
|
//
|
|
|
|
*Scb = NULL;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteScb -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsUpdateNormalizedName (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB ParentScb,
|
|
IN PSCB Scb,
|
|
IN PFILE_NAME FileName OPTIONAL,
|
|
IN BOOLEAN CheckBufferSizeOnly,
|
|
IN BOOLEAN NewDirectory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to update the normalized name in an IndexScb.
|
|
This name will be the path from the root without any short name components.
|
|
This routine will append the given name if present provided this is not a
|
|
DOS only name. In any other case this routine will go to the disk to
|
|
find the name. This routine will handle the case where there is an existing buffer
|
|
and the data will fit, as well as the case where the buffer doesn't exist
|
|
or is too small.
|
|
|
|
Arguments:
|
|
|
|
ParentScb - Supplies the parent of the current Scb. The name for the target
|
|
scb is appended to the name in this Scb.
|
|
|
|
Scb - Supplies the target Scb to add the name to.
|
|
|
|
FileName - If present this is a filename attribute for this Scb. We check
|
|
that it is not a DOS-only name.
|
|
|
|
CheckBufferSizeOnly - Indicates that we don't want to change the name yet. Just
|
|
verify that the buffer is the correct size.
|
|
|
|
NewDirectory - Is this a new directory that isn't in the hash/prefix table yet?
|
|
If so skip acquiring the hashtable
|
|
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if we updated the name in the Scb, FALSE otherwise. We would return
|
|
FALSE only if the parent becomes uninitialized on us. Any callers who can't
|
|
tolerate this must own the parent.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT Context;
|
|
PFILE_NAME OriginalFileName;
|
|
BOOLEAN CleanupContext = FALSE;
|
|
ULONG Length;
|
|
ULONG UnsafeLength;
|
|
ULONG SeparatorLength;
|
|
BOOLEAN UpdatedName = TRUE;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( NodeType( Scb ) == NTFS_NTC_SCB_INDEX );
|
|
ASSERT( NodeType( ParentScb ) == NTFS_NTC_SCB_INDEX ||
|
|
NodeType( ParentScb ) == NTFS_NTC_SCB_ROOT_INDEX );
|
|
|
|
//
|
|
// Use a try-finally to clean up the attribute context.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If the parent is the root then we don't need an extra separator.
|
|
//
|
|
|
|
SeparatorLength = 1;
|
|
if (ParentScb == ParentScb->Vcb->RootIndexScb) {
|
|
|
|
SeparatorLength = 0;
|
|
}
|
|
|
|
//
|
|
// Remember if we got a file name from our caller.
|
|
//
|
|
|
|
OriginalFileName = FileName;
|
|
|
|
//
|
|
// The only safe time to examine the normalized name structures are
|
|
// when holding the hash table mutex. These values shouldn't change
|
|
// often but if they do (and we are doing unsynchronized tests) then
|
|
// we will simply restart the logic.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// If the filename isn't present or is a DOS-only name then go to
|
|
// disk to find another name for this Scb.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( FileName ) || (FileName->Flags == FILE_NAME_DOS)) {
|
|
|
|
BOOLEAN Found;
|
|
|
|
NtfsInitializeAttributeContext( &Context );
|
|
CleanupContext = TRUE;
|
|
|
|
//
|
|
// Walk through the names for this entry. There better
|
|
// be one which is not a DOS-only name.
|
|
//
|
|
|
|
Found = NtfsLookupAttributeByCode( IrpContext,
|
|
Scb->Fcb,
|
|
&Scb->Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&Context );
|
|
|
|
while (Found) {
|
|
|
|
FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context ));
|
|
|
|
if (FileName->Flags != FILE_NAME_DOS) { break; }
|
|
|
|
Found = NtfsLookupNextAttributeByCode( IrpContext,
|
|
Scb->Fcb,
|
|
$FILE_NAME,
|
|
&Context );
|
|
}
|
|
|
|
//
|
|
// We should have found the entry.
|
|
//
|
|
|
|
if (!Found) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Compute the length we need for the name. This is unsynchronized so
|
|
// we will verify it later.
|
|
|
|
UnsafeLength = ParentScb->ScbType.Index.NormalizedName.Length + (FileName->FileNameLength + SeparatorLength) * sizeof( WCHAR );
|
|
|
|
//
|
|
// If the current buffer is insufficient then allocate a new one.
|
|
// Note that these are all unsafe tests. We will have to
|
|
// verify the values after acquiring the hash table mutex.
|
|
//
|
|
|
|
if (Scb->ScbType.Index.NormalizedName.MaximumLength < UnsafeLength) {
|
|
|
|
PVOID OldBuffer;
|
|
PVOID NewBuffer;
|
|
|
|
NewBuffer = NtfsAllocatePoolWithTag( PagedPool, UnsafeLength, 'oftN' );
|
|
|
|
//
|
|
// Now acquire the Hash table mutex and verify the numbers. If they
|
|
// are still valid then continue.
|
|
//
|
|
|
|
if (!NewDirectory) {
|
|
NtfsAcquireHashTable( Scb->Vcb );
|
|
}
|
|
|
|
//
|
|
// Check for unexpected changes.
|
|
//
|
|
|
|
Length = ParentScb->ScbType.Index.NormalizedName.Length + (FileName->FileNameLength + SeparatorLength) * sizeof( WCHAR );
|
|
|
|
if ((ParentScb->ScbType.Index.NormalizedName.Length == 0) ||
|
|
(Length > UnsafeLength)) {
|
|
|
|
//
|
|
// The following is an exit condition for us.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
UpdatedName = FALSE;
|
|
}
|
|
|
|
if (!NewDirectory) {
|
|
NtfsReleaseHashTable( Scb->Vcb );
|
|
}
|
|
|
|
//
|
|
// Free pool and clean up.
|
|
//
|
|
|
|
NtfsFreePool( NewBuffer );
|
|
if (CleanupContext) {
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
CleanupContext = FALSE;
|
|
}
|
|
|
|
FileName = OriginalFileName;
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Now copy over the existing data.
|
|
//
|
|
|
|
OldBuffer = Scb->ScbType.Index.NormalizedName.Buffer;
|
|
|
|
if (OldBuffer != NULL) {
|
|
|
|
RtlCopyMemory( NewBuffer,
|
|
OldBuffer,
|
|
Scb->ScbType.Index.NormalizedName.MaximumLength );
|
|
|
|
NtfsFreePool( OldBuffer );
|
|
}
|
|
|
|
//
|
|
// Swap out the old buffer and max length. No change to the hash value at
|
|
// this point.
|
|
//
|
|
|
|
Scb->ScbType.Index.NormalizedName.Buffer = NewBuffer;
|
|
Scb->ScbType.Index.NormalizedName.MaximumLength = (USHORT) Length;
|
|
|
|
//
|
|
// Acquire the hash table and verify that nothing has changed on us.
|
|
//
|
|
|
|
} else {
|
|
|
|
if (!NewDirectory) {
|
|
NtfsAcquireHashTable( Scb->Vcb );
|
|
}
|
|
|
|
//
|
|
// Check for unexpected changes.
|
|
//
|
|
|
|
Length = ParentScb->ScbType.Index.NormalizedName.Length + (FileName->FileNameLength + SeparatorLength) * sizeof( WCHAR );
|
|
|
|
if ((ParentScb->ScbType.Index.NormalizedName.Length == 0) ||
|
|
(Length > UnsafeLength)) {
|
|
|
|
//
|
|
// The following is an exit condition for us.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
UpdatedName = FALSE;
|
|
}
|
|
|
|
if (!NewDirectory) {
|
|
NtfsReleaseHashTable( Scb->Vcb );
|
|
}
|
|
|
|
//
|
|
// Cleanup for retry.
|
|
//
|
|
|
|
if (CleanupContext) {
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
CleanupContext = FALSE;
|
|
}
|
|
|
|
FileName = OriginalFileName;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we hold the hash table and know that the buffer is sufficient
|
|
// for the new data. However it still contains the previous data. If we aren't
|
|
// just updating the buffer lengths then store the new data.
|
|
//
|
|
|
|
if (!CheckBufferSizeOnly) {
|
|
|
|
PCHAR NextChar;
|
|
|
|
//
|
|
// Copy the new name into the buffer.
|
|
//
|
|
|
|
Scb->ScbType.Index.NormalizedName.Length = (USHORT) Length;
|
|
NextChar = (PCHAR) Scb->ScbType.Index.NormalizedName.Buffer;
|
|
|
|
//
|
|
// Now copy the name in. Don't forget to add the separator if the parent isn't
|
|
// the root.
|
|
//
|
|
|
|
RtlCopyMemory( NextChar,
|
|
ParentScb->ScbType.Index.NormalizedName.Buffer,
|
|
ParentScb->ScbType.Index.NormalizedName.Length );
|
|
|
|
NextChar += ParentScb->ScbType.Index.NormalizedName.Length;
|
|
|
|
if (SeparatorLength == 1) {
|
|
|
|
*((PWCHAR) NextChar) = L'\\';
|
|
NextChar += sizeof( WCHAR );
|
|
}
|
|
|
|
//
|
|
// Now append this name to the parent name.
|
|
//
|
|
|
|
RtlCopyMemory( NextChar,
|
|
FileName->FileName,
|
|
FileName->FileNameLength * sizeof( WCHAR ));
|
|
|
|
Scb->ScbType.Index.HashValue = 0;
|
|
NtfsConvertNameToHash( Scb->ScbType.Index.NormalizedName.Buffer,
|
|
Scb->ScbType.Index.NormalizedName.Length,
|
|
Scb->Vcb->UpcaseTable,
|
|
&Scb->ScbType.Index.HashValue );
|
|
}
|
|
|
|
if (!NewDirectory) {
|
|
NtfsReleaseHashTable( Scb->Vcb );
|
|
}
|
|
|
|
//
|
|
// Only one pass required in the typical case.
|
|
//
|
|
|
|
break;
|
|
|
|
//
|
|
// We either break out specifically or set this to FALSE.
|
|
//
|
|
|
|
} while (UpdatedName);
|
|
|
|
} finally {
|
|
|
|
if (CleanupContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &Context );
|
|
}
|
|
}
|
|
|
|
return UpdatedName;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteNormalizedName (
|
|
IN PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to delete the normalized name from an Scb.
|
|
We make this a function in order to serialize the normalized name
|
|
deletion with the hash package. The user has already done
|
|
the check to see if this Scb has a normalized name. Note that the
|
|
name may not be valid (Length == 0) but it does have a buffer
|
|
requiring cleanup.
|
|
|
|
Arguments:
|
|
|
|
Scb - Index Scb with a normalized name.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID OldBuffer;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( (NodeType( Scb ) == NTFS_NTC_SCB_INDEX) ||
|
|
(NodeType( Scb ) == NTFS_NTC_SCB_ROOT_INDEX) );
|
|
ASSERT( Scb->ScbType.Index.NormalizedName.Buffer != NULL );
|
|
|
|
//
|
|
// The hash table mutex is needed to synchronize with callers in the hash
|
|
// package who look at this Scb name without serializing with the Scb.
|
|
// They must hold the hash mutex for their entire operation.
|
|
//
|
|
|
|
NtfsAcquireHashTable( Scb->Vcb );
|
|
OldBuffer = Scb->ScbType.Index.NormalizedName.Buffer;
|
|
Scb->ScbType.Index.NormalizedName.Buffer = NULL;
|
|
Scb->ScbType.Index.NormalizedName.MaximumLength = Scb->ScbType.Index.NormalizedName.Length = 0;
|
|
|
|
NtfsReleaseHashTable( Scb->Vcb );
|
|
|
|
NtfsFreePool( OldBuffer );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
NtfsWalkUpTree (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN NTFSWALKUPFUNCTION WalkUpFunction,
|
|
IN OUT PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks up the tree from child to parent, applying
|
|
a function at each level. Processing terminates when WalkUpFunction
|
|
returns a failure status code. The current convention is that
|
|
WalkUpFunctions return STATUS_NO_MORE_FILES when a successful upward
|
|
traversal occurs. Other status codes are private to
|
|
caller/WalkUpFunction.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - context of the call
|
|
|
|
Fcb - beginning file
|
|
|
|
WalkUpFunction - function that is applied to each level
|
|
|
|
Context - Pointer to caller-private data passed to WalkUpFunction
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - at end of complete walk
|
|
|
|
Status code returned by WalkUpFunction otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB ThisFcb = Fcb;
|
|
PFCB NextFcb = NULL;
|
|
PSCB NextScb = NULL;
|
|
PLCB NextLcb;
|
|
BOOLEAN AcquiredNextFcb = FALSE;
|
|
BOOLEAN AcquiredThisFcb = FALSE;
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
|
|
BOOLEAN FoundEntry = TRUE;
|
|
BOOLEAN CleanupAttrContext = FALSE;
|
|
PFILE_NAME FileName;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_SHARED_RESOURCE( &Fcb->Vcb->Resource );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If the starting Fcb is for a directory try to find the corresponding
|
|
// Scb with the normalized name in it
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_DUP_INITIALIZED ) &&
|
|
IsDirectory( &ThisFcb->Info )) {
|
|
|
|
do {
|
|
NextScb = NtfsGetNextChildScb( Fcb, NextScb );
|
|
} while ((NextScb != NULL) && (NextScb->AttributeTypeCode != $INDEX_ALLOCATION));
|
|
}
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// If we reach the root then exit.
|
|
//
|
|
|
|
if (ThisFcb == ThisFcb->Vcb->RootIndexScb->Fcb) {
|
|
|
|
//
|
|
// Special case root directory
|
|
//
|
|
|
|
Status = WalkUpFunction( IrpContext, ThisFcb, ThisFcb->Vcb->RootIndexScb, NULL, Context );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Find a non-dos name for the current Scb. There better be one.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttrContext = TRUE;
|
|
|
|
FoundEntry = NtfsLookupAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
&ThisFcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
|
|
while (FoundEntry) {
|
|
|
|
FileName = (PFILE_NAME)
|
|
NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
|
|
|
|
if (FileName->Flags != FILE_NAME_DOS ) {
|
|
break;
|
|
}
|
|
|
|
FoundEntry = NtfsLookupNextAttributeByCode( IrpContext,
|
|
ThisFcb,
|
|
$FILE_NAME,
|
|
&AttrContext );
|
|
}
|
|
|
|
if (!FoundEntry) {
|
|
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
NextFcb );
|
|
}
|
|
|
|
ASSERT( NextScb == NULL || NextScb->Fcb == ThisFcb );
|
|
Status = WalkUpFunction( IrpContext, ThisFcb, NextScb, FileName, Context );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now get the parent for the current component. Acquire the Fcb for
|
|
// synchronization. We can either walk up the Lcb chain or look it up
|
|
// in the Fcb table. It must be for the same name as the file name
|
|
// since there is only one path up the tree for a directory.
|
|
//
|
|
|
|
if (!IsListEmpty( &ThisFcb->LcbQueue ) && IsDirectory( &ThisFcb->Info )) {
|
|
|
|
NextLcb =
|
|
(PLCB) CONTAINING_RECORD( ThisFcb->LcbQueue.Flink, LCB, FcbLinks );
|
|
NextScb = NextLcb->Scb;
|
|
NextFcb = NextScb->Fcb;
|
|
|
|
NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, ACQUIRE_NO_DELETE_CHECK );
|
|
AcquiredNextFcb = TRUE;
|
|
|
|
ASSERT( NtfsEqualMftRef( &FileName->ParentDirectory,
|
|
&NextFcb->FileReference ));
|
|
|
|
} else {
|
|
UNICODE_STRING ComponentName;
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Fcb->Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
NextFcb = NtfsCreateFcb( IrpContext,
|
|
Fcb->Vcb,
|
|
FileName->ParentDirectory,
|
|
FALSE,
|
|
TRUE,
|
|
NULL );
|
|
|
|
NextFcb->ReferenceCount += 1;
|
|
|
|
//
|
|
// Try to do an unsafe acquire. Otherwise we must drop the Fcb table
|
|
// and acquire the Fcb and then reacquire the Fcb table.
|
|
//
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, ACQUIRE_NO_DELETE_CHECK | ACQUIRE_DONT_WAIT )) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Fcb->Vcb );
|
|
NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, ACQUIRE_NO_DELETE_CHECK );
|
|
NtfsAcquireFcbTable( IrpContext, Fcb->Vcb );
|
|
|
|
}
|
|
|
|
NextFcb->ReferenceCount -= 1;
|
|
NtfsReleaseFcbTable( IrpContext, Fcb->Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
AcquiredNextFcb = TRUE;
|
|
|
|
NextScb = NtfsCreateScb( IrpContext,
|
|
NextFcb,
|
|
$INDEX_ALLOCATION,
|
|
&NtfsFileNameIndex,
|
|
FALSE,
|
|
NULL );
|
|
|
|
ComponentName.Buffer = FileName->FileName;
|
|
ComponentName.MaximumLength =
|
|
ComponentName.Length = FileName->FileNameLength * sizeof( WCHAR );
|
|
|
|
NextLcb = NtfsCreateLcb( IrpContext,
|
|
NextScb,
|
|
ThisFcb,
|
|
ComponentName,
|
|
FileName->Flags,
|
|
NULL );
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
CleanupAttrContext = FALSE;
|
|
|
|
//
|
|
// Release the current Fcb and move up the tree.
|
|
//
|
|
|
|
if (AcquiredThisFcb) {
|
|
NtfsReleaseFcb( IrpContext, ThisFcb );
|
|
}
|
|
|
|
ThisFcb = NextFcb;
|
|
AcquiredThisFcb = TRUE;
|
|
AcquiredNextFcb = FALSE;
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Fcb->Vcb ); }
|
|
if (AcquiredNextFcb) { NtfsReleaseFcb( IrpContext, NextFcb ); }
|
|
if (AcquiredThisFcb) { NtfsReleaseFcb( IrpContext, ThisFcb ); }
|
|
|
|
if (CleanupAttrContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsBuildRelativeName (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN PFILE_NAME FileName,
|
|
IN OUT PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called for each parent directory up to the root. We
|
|
prepend the name of the current node to the ScopeContext as we walk
|
|
up. We terminate this walk when we hit the top of the scope or the
|
|
root.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - context of the call
|
|
|
|
Fcb - parent
|
|
|
|
FileName - FILE_NAME of self relative to parent
|
|
|
|
Context - Pointer to caller-private data passed to WalkUpFunction
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - if we're still walking up the tree
|
|
|
|
STATUS_NO_MORE_FILES - if we've found the specified scope
|
|
|
|
STATUS_OBJECT_PATH_NOT_FOUND - if we've reached the root and did not
|
|
hit the scope.
|
|
|
|
--*/
|
|
{
|
|
PSCOPE_CONTEXT ScopeContext = (PSCOPE_CONTEXT) Context;
|
|
ULONG SlashCount;
|
|
WCHAR *Name;
|
|
ULONG Count;
|
|
USHORT NewLength;
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
//
|
|
// If we've reached the passed-in scope then we're done - except if we haven't
|
|
// generated any name yet in which case add the \ for the root (this is the case
|
|
// where we're normalizing the root itself)
|
|
//
|
|
|
|
if (ScopeContext->Name.Length > 0) {
|
|
|
|
if (NtfsEqualMftRef( &ScopeContext->Scope, &Fcb->FileReference )) {
|
|
return STATUS_NO_MORE_FILES;
|
|
}
|
|
|
|
//
|
|
// If we've reached the root then we're totally outside the scope
|
|
//
|
|
|
|
if (NtfsEqualMftRef( &RootIndexFileReference, &Fcb->FileReference )) {
|
|
return STATUS_OBJECT_PATH_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set up Name from input. We take the shortcut to building the name
|
|
// only if we're looking from the root. Also, if we are starting at
|
|
// the root, then we should use the canned name as well.
|
|
//
|
|
|
|
if (
|
|
|
|
//
|
|
// No file name (i.e., root)
|
|
//
|
|
|
|
FileName == NULL ||
|
|
|
|
//
|
|
// We're searching to the root and
|
|
// we have an Scb and
|
|
// the Scb has a normalized name
|
|
//
|
|
|
|
(ScopeContext->IsRoot &&
|
|
(Scb != NULL) &&
|
|
(Scb->ScbType.Index.NormalizedName.Length != 0))) {
|
|
|
|
Name = Scb->ScbType.Index.NormalizedName.Buffer;
|
|
Count = Scb->ScbType.Index.NormalizedName.Length / sizeof( WCHAR );
|
|
SlashCount = 0;
|
|
|
|
} else {
|
|
Name = FileName->FileName;
|
|
Count = FileName->FileNameLength;
|
|
SlashCount = 1;
|
|
}
|
|
|
|
//
|
|
// If there's not enough room in the string to allow for prepending
|
|
//
|
|
|
|
NewLength = (USHORT) ((SlashCount + Count) * sizeof( WCHAR ) + ScopeContext->Name.Length);
|
|
if (NewLength > ScopeContext->Name.MaximumLength ) {
|
|
|
|
WCHAR *NewBuffer;
|
|
|
|
//
|
|
// Reallocate string. Adjust size of string for pool boundaries.
|
|
//
|
|
|
|
NewLength = ((NewLength + 8 + 0x40 - 1) & ~(0x40 - 1)) - 8;
|
|
NewBuffer = NtfsAllocatePool( PagedPool, NewLength );
|
|
|
|
//
|
|
// Copy over previous contents into new buffer
|
|
//
|
|
|
|
if (ScopeContext->Name.Length != 0) {
|
|
RtlCopyMemory( NewBuffer,
|
|
ScopeContext->Name.Buffer,
|
|
ScopeContext->Name.Length );
|
|
NtfsFreePool( ScopeContext->Name.Buffer );
|
|
}
|
|
|
|
ScopeContext->Name.Buffer = NewBuffer;
|
|
ScopeContext->Name.MaximumLength = NewLength;
|
|
}
|
|
|
|
//
|
|
// Shift string over to make new room
|
|
//
|
|
|
|
RtlMoveMemory( &ScopeContext->Name.Buffer[SlashCount + Count],
|
|
ScopeContext->Name.Buffer,
|
|
ScopeContext->Name.Length );
|
|
|
|
//
|
|
// copy name
|
|
//
|
|
|
|
RtlCopyMemory( &ScopeContext->Name.Buffer[SlashCount],
|
|
Name,
|
|
Count * sizeof( WCHAR ) );
|
|
|
|
//
|
|
// Stick in the slash
|
|
//
|
|
|
|
if (SlashCount != 0) {
|
|
ScopeContext->Name.Buffer[0] = L'\\';
|
|
}
|
|
|
|
ScopeContext->Name.Length += (USHORT)((SlashCount + Count) * sizeof( WCHAR ));
|
|
|
|
return SlashCount == 0 ? STATUS_NO_MORE_FILES : STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsBuildNormalizedName (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PSCB IndexScb OPTIONAL,
|
|
OUT PUNICODE_STRING PathName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to build a normalized name for an Fcb by looking
|
|
up the file name attributes up to the root directory.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - context of call
|
|
|
|
Fcb - Supplies the starting point.
|
|
|
|
IndexScb - Indicates that we are storing this name into an Scb so we
|
|
we need to serialize with the hash package and also generate a
|
|
hash for this.
|
|
|
|
PathName - location where full name is stored
|
|
|
|
Return Value:
|
|
|
|
None. This routine either succeeds or raises.
|
|
|
|
--*/
|
|
|
|
{
|
|
SCOPE_CONTEXT ScopeContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
RtlZeroMemory( &ScopeContext, sizeof( ScopeContext ));
|
|
ScopeContext.Scope = RootIndexFileReference;
|
|
ScopeContext.IsRoot = TRUE;
|
|
|
|
try {
|
|
|
|
NtfsWalkUpTree( IrpContext, Fcb, NtfsBuildRelativeName, &ScopeContext );
|
|
|
|
if (ARGUMENT_PRESENT( IndexScb )) {
|
|
|
|
NtfsAcquireHashTable( Fcb->Vcb );
|
|
*PathName = ScopeContext.Name;
|
|
IndexScb->ScbType.Index.HashValue = 0;
|
|
NtfsConvertNameToHash( PathName->Buffer,
|
|
PathName->Length,
|
|
IndexScb->Vcb->UpcaseTable,
|
|
&IndexScb->ScbType.Index.HashValue );
|
|
|
|
NtfsReleaseHashTable( Fcb->Vcb );
|
|
|
|
} else {
|
|
|
|
*PathName = ScopeContext.Name;
|
|
}
|
|
|
|
ScopeContext.Name.Buffer = NULL;
|
|
|
|
} finally {
|
|
if (ScopeContext.Name.Buffer != NULL) {
|
|
NtfsFreePool( ScopeContext.Name.Buffer );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsSnapshotScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine snapshots necessary Scb data, such as the Scb file sizes,
|
|
so that they may be correctly restored if the caller's I/O request is
|
|
aborted for any reason. The restoring of these values and the freeing
|
|
of any pool involved is automatic.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the current Scb
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB_SNAPSHOT ScbSnapshot;
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Scb ) );
|
|
|
|
ScbSnapshot = &IrpContext->ScbSnapshot;
|
|
|
|
//
|
|
// Only do the snapshot if the Scb is initialized, we have not done
|
|
// so already, and it is worth special-casing the bitmap, as it never changes!
|
|
// We will snapshot the volume bitmap if it is on the exclusive Fcb list however.
|
|
// This should only happen if we are extending the volume bitmap through the
|
|
// ExtendVolume fsctl.
|
|
//
|
|
|
|
if (FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED) &&
|
|
(Scb->ScbSnapshot == NULL) &&
|
|
((Scb != Scb->Vcb->BitmapScb) ||
|
|
(Scb->Fcb->ExclusiveFcbLinks.Flink != NULL))) {
|
|
|
|
//
|
|
// If the snapshot structure in the IrpContext is in use, then we have
|
|
// to allocate one and insert it in the list.
|
|
//
|
|
|
|
if (ScbSnapshot->Scb != NULL) {
|
|
|
|
ScbSnapshot = (PSCB_SNAPSHOT)ExAllocateFromNPagedLookasideList( &NtfsScbSnapshotLookasideList );
|
|
|
|
InsertTailList( &IrpContext->ScbSnapshot.SnapshotLinks,
|
|
&ScbSnapshot->SnapshotLinks );
|
|
|
|
}
|
|
|
|
//
|
|
// We should never be writing compressed if the file isn't compressed.
|
|
//
|
|
|
|
ASSERT( FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) ||
|
|
!FlagOn( Scb->ScbState, SCB_STATE_WRITE_COMPRESSED ) ||
|
|
(Scb->CompressionUnit != 0) );
|
|
|
|
//
|
|
// Snapshot the Scb values and point the Scb and snapshot structure
|
|
// at each other.
|
|
//
|
|
|
|
NtfsVerifySizes( &Scb->Header );
|
|
ScbSnapshot->AllocationSize = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
ScbSnapshot->FileSize = Scb->Header.FileSize.QuadPart;
|
|
ScbSnapshot->ValidDataLength = Scb->Header.ValidDataLength.QuadPart;
|
|
ScbSnapshot->ValidDataToDisk = Scb->ValidDataToDisk;
|
|
ScbSnapshot->Scb = Scb;
|
|
ScbSnapshot->LowestModifiedVcn = MAXLONGLONG;
|
|
ScbSnapshot->HighestModifiedVcn = 0;
|
|
|
|
ScbSnapshot->TotalAllocated = Scb->TotalAllocated;
|
|
|
|
ScbSnapshot->ScbState = FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
|
|
|
|
Scb->ScbSnapshot = ScbSnapshot;
|
|
NtfsVerifySizesLongLong( ScbSnapshot );
|
|
|
|
//
|
|
// If this is the Mft Scb then initialize the cluster Mcb structures.
|
|
//
|
|
|
|
if (Scb == Scb->Vcb->MftScb) {
|
|
|
|
FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.AddedClusters, 0 );
|
|
FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.RemovedClusters, 0 );
|
|
|
|
Scb->ScbType.Mft.FreeRecordChange = 0;
|
|
Scb->ScbType.Mft.HoleRecordChange = 0;
|
|
}
|
|
|
|
//
|
|
// Determine if we can use the snapshot for rollback of file sizes
|
|
// The 4 cases are we own the pagingio, we own io at eof, its being converted to non-res
|
|
// or its a mod-no write stream which we explicity control like the usn journal
|
|
//
|
|
|
|
if (NtfsSnapshotFileSizesTest( IrpContext, Scb )) {
|
|
Scb->ScbSnapshot->OwnerIrpContext = IrpContext;
|
|
} else {
|
|
Scb->ScbSnapshot->OwnerIrpContext = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsUpdateScbSnapshots (
|
|
IN PIRP_CONTEXT IrpContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine may be called to update the snapshot values for all Scbs,
|
|
after completing a transaction checkpoint.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB_SNAPSHOT ScbSnapshot;
|
|
PSCB Scb;
|
|
|
|
ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
|
|
|
|
PAGED_CODE();
|
|
|
|
ScbSnapshot = &IrpContext->ScbSnapshot;
|
|
|
|
//
|
|
// Loop to update first the Scb data from the snapshot in the
|
|
// IrpContext, and then 0 or more additional snapshots linked
|
|
// to the IrpContext.
|
|
//
|
|
|
|
do {
|
|
|
|
Scb = ScbSnapshot->Scb;
|
|
|
|
//
|
|
// Update the Scb values.
|
|
//
|
|
|
|
if (Scb != NULL) {
|
|
|
|
ScbSnapshot->AllocationSize = Scb->Header.AllocationSize.QuadPart;
|
|
|
|
//
|
|
// If this is the MftScb then clear out the added/removed
|
|
// cluster Mcbs.
|
|
//
|
|
|
|
if (Scb == Scb->Vcb->MftScb) {
|
|
|
|
FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.AddedClusters, (LONGLONG)0 );
|
|
FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.RemovedClusters, (LONGLONG)0 );
|
|
|
|
Scb->ScbType.Mft.FreeRecordChange = 0;
|
|
Scb->ScbType.Mft.HoleRecordChange = 0;
|
|
}
|
|
|
|
ScbSnapshot->FileSize = Scb->Header.FileSize.QuadPart;
|
|
ScbSnapshot->ValidDataLength = Scb->Header.ValidDataLength.QuadPart;
|
|
ScbSnapshot->ValidDataToDisk = Scb->ValidDataToDisk;
|
|
ScbSnapshot->TotalAllocated = Scb->TotalAllocated;
|
|
|
|
ScbSnapshot->ScbState = FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
|
|
NtfsVerifySizesLongLong( ScbSnapshot );
|
|
}
|
|
|
|
ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
|
|
|
|
} while (ScbSnapshot != &IrpContext->ScbSnapshot);
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsRestoreScbSnapshots (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN BOOLEAN Higher
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine restores snapshot Scb data in the event of an aborted request.
|
|
|
|
Arguments:
|
|
|
|
Higher - Specified as TRUE to restore only those Scb values which are
|
|
higher than current values. Specified as FALSE to restore
|
|
only those Scb values which are lower (or same!).
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN UpdateCc;
|
|
PSCB_SNAPSHOT ScbSnapshot;
|
|
PSCB Scb;
|
|
PVCB Vcb = IrpContext->Vcb;
|
|
|
|
ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
|
|
|
|
ScbSnapshot = &IrpContext->ScbSnapshot;
|
|
|
|
//
|
|
// Loop to retore first the Scb data from the snapshot in the
|
|
// IrpContext, and then 0 or more additional snapshots linked
|
|
// to the IrpContext.
|
|
//
|
|
|
|
do {
|
|
|
|
PSECTION_OBJECT_POINTERS SectionObjectPointer;
|
|
PFILE_OBJECT PseudoFileObject;
|
|
|
|
Scb = ScbSnapshot->Scb;
|
|
|
|
if (Scb == NULL) {
|
|
|
|
ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Increment the cleanup count so the Scb won't go away.
|
|
//
|
|
|
|
InterlockedIncrement( &Scb->CleanupCount );
|
|
|
|
//
|
|
// We update the Scb file size in the correct pass. We always do
|
|
// the extend/truncate pair.
|
|
//
|
|
// Only do sizes if our caller was changing these fields, which we marked
|
|
// by setting the irpcontext owner when we snapped
|
|
//
|
|
// The one unusual case is where we are converting a stream to
|
|
// nonresident when this is not the stream for the request. We
|
|
// must restore the Scb for this case as well.
|
|
//
|
|
|
|
UpdateCc = FALSE;
|
|
if ((ScbSnapshot->OwnerIrpContext == IrpContext) || (ScbSnapshot->OwnerIrpContext == IrpContext->TopLevelIrpContext)) {
|
|
|
|
//
|
|
// Proceed to restore all values which are in higher or not
|
|
// higher.
|
|
//
|
|
|
|
if (Higher == (ScbSnapshot->AllocationSize >= Scb->Header.AllocationSize.QuadPart)) {
|
|
|
|
//
|
|
// If this is the maximize pass, we want to extend the cache section.
|
|
// In all cases we restore the allocation size in the Scb and
|
|
// recover the resident bit.
|
|
//
|
|
|
|
Scb->Header.AllocationSize.QuadPart = ScbSnapshot->AllocationSize;
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
|
|
SetFlag( Scb->ScbState,
|
|
FlagOn( ScbSnapshot->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ));
|
|
|
|
//
|
|
// Calculate FastIoPossible
|
|
//
|
|
|
|
if (Scb->CompressionUnit != 0) {
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
}
|
|
}
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
if (Higher ?
|
|
(ScbSnapshot->FileSize > Scb->Header.FileSize.QuadPart) :
|
|
(ScbSnapshot->FileSize < Scb->Header.FileSize.QuadPart)) {
|
|
|
|
Scb->Header.FileSize.QuadPart = ScbSnapshot->FileSize;
|
|
|
|
//
|
|
// We really only need to update Cc if FileSize changes,
|
|
// since he does not look at ValidDataLength, and he
|
|
// only cares about successfully reached highwatermarks
|
|
// on AllocationSize (making section big enough).
|
|
//
|
|
// Note that setting this flag TRUE also implies we
|
|
// are correctly synchronized with FileSize!
|
|
//
|
|
|
|
UpdateCc = TRUE;
|
|
}
|
|
|
|
if (Higher == (ScbSnapshot->ValidDataLength >
|
|
Scb->Header.ValidDataLength.QuadPart)) {
|
|
|
|
Scb->Header.ValidDataLength.QuadPart = ScbSnapshot->ValidDataLength;
|
|
}
|
|
|
|
ASSERT( (Scb->Header.ValidDataLength.QuadPart <= Scb->Header.FileSize.QuadPart) ||
|
|
(Scb->Header.ValidDataLength.QuadPart == MAXLONGLONG) );
|
|
|
|
//
|
|
// If this is the unnamed data attribute, we have to update
|
|
// some Fcb fields for standard information as well.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
Scb->Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
}
|
|
|
|
if (!Higher) {
|
|
|
|
Scb->ValidDataToDisk = ScbSnapshot->ValidDataToDisk;
|
|
|
|
//
|
|
// We always truncate the Mcb to the original allocation size.
|
|
// If the Mcb has shrunk beyond this, this becomes a noop.
|
|
// If the file is resident, then we will uninitialize
|
|
// and reinitialize the Mcb.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
//
|
|
// Remove all of the mappings in the Mcb.
|
|
//
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb, (LONGLONG)0, MAXLONGLONG, FALSE, FALSE );
|
|
|
|
//
|
|
// If we attempted a convert a data attribute to non-
|
|
// resident and failed then need to nuke the pages in the
|
|
// section if this is not a user file. This is because for
|
|
// resident system attributes we always update the attribute
|
|
// directly and don't want to reference stale data in the
|
|
// section if we do a convert to non-resident later.
|
|
//
|
|
|
|
if (Scb->AttributeTypeCode != $DATA) {
|
|
|
|
if (Scb->NonpagedScb->SegmentObject.SharedCacheMap != NULL) {
|
|
|
|
//
|
|
// If we're not synchronized with the lazy writer, we shouldn't
|
|
// be attempting this purge. Otherwise there's a potential for
|
|
// deadlock when this thread waits on the active count, while the
|
|
// thread trying to get rid of his reference is waiting for the
|
|
// main resource on this scb.
|
|
//
|
|
|
|
ASSERT( (Scb->Header.PagingIoResource == NULL) ||
|
|
NtfsIsExclusiveScbPagingIo( Scb ) );
|
|
|
|
if (!CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
|
|
NULL,
|
|
0,
|
|
FALSE )) {
|
|
|
|
ASSERTMSG( "Failed to purge Scb during restore\n", FALSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the attribute is for non-user data
|
|
// (which is not opened explicitly by a user), then we
|
|
// want to modify this Scb so it won't be used again.
|
|
// Set the sizes to zero, mark it as being initialized
|
|
// and deleted and then change the attribute type code
|
|
// so we won't ever return it via NtfsCreateScb.
|
|
//
|
|
|
|
if (IsListEmpty( &Scb->CcbQueue )) {
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
Scb->Header.AllocationSize =
|
|
Scb->Header.FileSize =
|
|
Scb->Header.ValidDataLength = Li0;
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
Scb->ValidDataToDisk = 0;
|
|
|
|
SetFlag( Scb->ScbState,
|
|
SCB_STATE_FILE_SIZE_LOADED |
|
|
SCB_STATE_HEADER_INITIALIZED |
|
|
SCB_STATE_ATTRIBUTE_DELETED );
|
|
|
|
Scb->AttributeTypeCode = $UNUSED;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have modified this Mcb and want to back out any
|
|
// changes then truncate the Mcb. Don't do the Mft, because
|
|
// that is handled elsewhere.
|
|
//
|
|
|
|
} else if ((ScbSnapshot->LowestModifiedVcn != MAXLONGLONG) &&
|
|
(Scb != Vcb->MftScb)) {
|
|
|
|
//
|
|
// Truncate the Mcb.
|
|
//
|
|
|
|
NtfsUnloadNtfsMcbRange( &Scb->Mcb, ScbSnapshot->LowestModifiedVcn, ScbSnapshot->HighestModifiedVcn, FALSE, FALSE );
|
|
}
|
|
|
|
Scb->TotalAllocated = ScbSnapshot->TotalAllocated;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Set the flag to indicate that we're performing a restore on this
|
|
// Scb. We don't want to write any new log records as a result of
|
|
// this operation other than the abort records.
|
|
//
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY );
|
|
}
|
|
|
|
//
|
|
// Be sure to update Cache Manager. The interface here uses a file
|
|
// object but the routine itself only uses the section object pointers.
|
|
// We put a pointer to the segment object pointers on the stack and
|
|
// cast some prior value as a file object pointer.
|
|
//
|
|
|
|
PseudoFileObject = (PFILE_OBJECT) CONTAINING_RECORD( &SectionObjectPointer,
|
|
FILE_OBJECT,
|
|
SectionObjectPointer );
|
|
PseudoFileObject->SectionObjectPointer = &Scb->NonpagedScb->SegmentObject;
|
|
|
|
//
|
|
// Now tell the cache manager the sizes.
|
|
//
|
|
// If we fail in this call, we definitely want to charge on anyway.
|
|
// It should only fail if it tries to extend the section and cannot,
|
|
// in which case we do not care because we cannot need the extended
|
|
// part to the section anyway. (This is probably the very error that
|
|
// is causing us to clean up in the first place!)
|
|
//
|
|
// We don't need to make this call if the top level request is a
|
|
// paging Io write.
|
|
//
|
|
// We only do this if there is a shared cache map for this stream.
|
|
// Otherwise CC will cause a flush to happen which could mess up
|
|
// the transaction and abort logic.
|
|
//
|
|
|
|
if (UpdateCc &&
|
|
(IrpContext->OriginatingIrp == NULL ||
|
|
IrpContext->OriginatingIrp->Type != IO_TYPE_IRP ||
|
|
IrpContext->MajorFunction != IRP_MJ_WRITE ||
|
|
!FlagOn( IrpContext->OriginatingIrp->Flags, IRP_PAGING_IO ))) {
|
|
|
|
try {
|
|
|
|
NtfsSetBothCacheSizes( PseudoFileObject,
|
|
(PCC_FILE_SIZES)&Scb->Header.AllocationSize,
|
|
Scb );
|
|
|
|
} except(FsRtlIsNtstatusExpected(GetExceptionCode()) ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH) {
|
|
|
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is the unnamed data attribute, we have to update
|
|
// some Fcb fields for standard information as well.
|
|
//
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
|
|
|
|
Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
|
|
}
|
|
|
|
//
|
|
// We always clear the Scb deleted flag and the deleted flag in the Fcb
|
|
// unless this was a create new file operation which failed. We recognize
|
|
// this by looking for the major Irp code in the IrpContext, and the
|
|
// deleted bit in the Fcb.
|
|
//
|
|
|
|
if (Scb->AttributeTypeCode != $UNUSED &&
|
|
(IrpContext->MajorFunction != IRP_MJ_CREATE ||
|
|
!FlagOn( Scb->Fcb->FcbState, FCB_STATE_FILE_DELETED ))) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
ClearFlag( Scb->Fcb->FcbState, FCB_STATE_FILE_DELETED );
|
|
}
|
|
|
|
//
|
|
// Clear the flags in the Scb if this Scb is from a create
|
|
// that failed. We always clear our RESTORE_UNDERWAY flag.
|
|
//
|
|
// If this is an Index allocation or Mft bitmap, then we
|
|
// store MAXULONG in the record allocation context to indicate
|
|
// that we should reinitialize it.
|
|
//
|
|
|
|
if (!Higher) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY );
|
|
|
|
if (FlagOn( Scb->ScbState, SCB_STATE_UNINITIALIZE_ON_RESTORE )) {
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
|
|
SCB_STATE_HEADER_INITIALIZED |
|
|
SCB_STATE_UNINITIALIZE_ON_RESTORE );
|
|
}
|
|
|
|
//
|
|
// If this is the MftScb we have several jobs to do.
|
|
//
|
|
// - Force the record allocation context to be reinitialized
|
|
// - Back out the changes to the Vcb->MftFreeRecords field
|
|
// - Back changes to the Vcb->MftHoleRecords field
|
|
// - Clear the flag indicating we allocated file record 15
|
|
// - Clear the flag indicating we reserved a record
|
|
// - Remove any clusters added to the Scb Mcb
|
|
// - Restore any clusters removed from the Scb Mcb
|
|
//
|
|
|
|
if (Scb == Vcb->MftScb) {
|
|
|
|
ULONG RunIndex;
|
|
VCN Vcn;
|
|
LCN Lcn;
|
|
LONGLONG Clusters;
|
|
|
|
Scb->ScbType.Index.RecordAllocationContext.CurrentBitmapSize = MAXULONG;
|
|
(LONG) Vcb->MftFreeRecords -= Scb->ScbType.Mft.FreeRecordChange;
|
|
(LONG) Vcb->MftHoleRecords -= Scb->ScbType.Mft.HoleRecordChange;
|
|
|
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_MFT_REC_15_USED )) {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MFT_REC_15_USED );
|
|
ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_15_USED );
|
|
}
|
|
|
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_MFT_REC_RESERVED )) {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MFT_REC_RESERVED );
|
|
ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
|
|
|
|
Scb->ScbType.Mft.ReservedIndex = 0;
|
|
}
|
|
|
|
RunIndex = 0;
|
|
|
|
while (FsRtlGetNextLargeMcbEntry( &Scb->ScbType.Mft.AddedClusters,
|
|
RunIndex,
|
|
&Vcn,
|
|
&Lcn,
|
|
&Clusters )) {
|
|
|
|
if (Lcn != UNUSED_LCN) {
|
|
|
|
NtfsRemoveNtfsMcbEntry( &Scb->Mcb, Vcn, Clusters );
|
|
}
|
|
|
|
RunIndex += 1;
|
|
}
|
|
|
|
RunIndex = 0;
|
|
|
|
while (FsRtlGetNextLargeMcbEntry( &Scb->ScbType.Mft.RemovedClusters,
|
|
RunIndex,
|
|
&Vcn,
|
|
&Lcn,
|
|
&Clusters )) {
|
|
|
|
if (Lcn != UNUSED_LCN) {
|
|
|
|
NtfsAddNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, Clusters, FALSE );
|
|
}
|
|
|
|
RunIndex += 1;
|
|
}
|
|
|
|
} else if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
|
|
|
|
Scb->ScbType.Index.RecordAllocationContext.CurrentBitmapSize = MAXULONG;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Decrement the cleanup count to restore the previous value.
|
|
//
|
|
|
|
InterlockedDecrement( &Scb->CleanupCount );
|
|
|
|
ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
|
|
|
|
} while (ScbSnapshot != &IrpContext->ScbSnapshot);
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsMungeScbSnapshot (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN LONGLONG FileSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to modify the Scb snapshot when we need the snapshot to
|
|
have different values than the Scb when it was acquired. One case is when NtfsCommonWrite
|
|
updates the file size in the Scb for the duration of the transaction.
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb whose snapshot should be updated. There should always be a snapshot here.
|
|
|
|
FileSize - Value for file size to store in the snapshot. Also check that valid data and
|
|
ValidDataToDisk are not larger than this value.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// We should have a snapshot in most cases but if not build it now.
|
|
//
|
|
|
|
if (Scb->ScbSnapshot == NULL) {
|
|
|
|
if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
|
|
}
|
|
|
|
NtfsSnapshotScb( IrpContext, Scb );
|
|
|
|
ASSERT( Scb->ScbSnapshot != NULL );
|
|
}
|
|
|
|
NtfsAcquireFsrtlHeader( Scb );
|
|
|
|
Scb->ScbSnapshot->FileSize = FileSize;
|
|
|
|
if (Scb->ScbSnapshot->ValidDataLength > FileSize) {
|
|
Scb->ScbSnapshot->ValidDataLength = FileSize;
|
|
}
|
|
|
|
if (Scb->ScbSnapshot->ValidDataToDisk > FileSize) {
|
|
Scb->ScbSnapshot->ValidDataToDisk = FileSize;
|
|
}
|
|
|
|
NtfsVerifySizes( &Scb->Header );
|
|
|
|
NtfsReleaseFsrtlHeader( Scb );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsFreeSnapshotsForFcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine restores snapshot Scb data in the event of an aborted request.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Fcb for which all snapshots are to be freed, or NULL to free all
|
|
snapshots.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB_SNAPSHOT ScbSnapshot;
|
|
|
|
ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
|
|
|
|
ScbSnapshot = &IrpContext->ScbSnapshot;
|
|
|
|
//
|
|
// Loop to free first the Scb data from the snapshot in the
|
|
// IrpContext, and then 0 or more additional snapshots linked
|
|
// to the IrpContext.
|
|
//
|
|
|
|
do {
|
|
|
|
PSCB_SNAPSHOT NextScbSnapshot;
|
|
|
|
//
|
|
// Move to next snapshot before we delete the current one.
|
|
//
|
|
|
|
NextScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
|
|
|
|
//
|
|
// We are now at a snapshot in the snapshot list. We skip
|
|
// over this entry if it has an Scb and the Fcb for that
|
|
// Scb does not match the input Fcb. If there is no
|
|
// input Fcb we always deal with this snapshot.
|
|
//
|
|
|
|
if ((ScbSnapshot->Scb != NULL) &&
|
|
(Fcb != NULL) &&
|
|
(ScbSnapshot->Scb->Fcb != Fcb)) {
|
|
|
|
ScbSnapshot = NextScbSnapshot;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If there is an Scb, then clear its snapshot pointer.
|
|
// Always clear the UNINITIALIZE_ON_RESTORE flag, RESTORE_UNDERWAY, PROTECT_SPARSE_MCB and
|
|
// CONVERT_UNDERWAY flags.
|
|
//
|
|
|
|
if (ScbSnapshot->Scb != NULL) {
|
|
|
|
//
|
|
// Check if there is any special processing we need to do for the Scb based on the state.
|
|
// Do a single test now and then retest below to reduce the work in the mainline path.
|
|
//
|
|
|
|
if (FlagOn( ScbSnapshot->Scb->ScbState,
|
|
(SCB_STATE_UNINITIALIZE_ON_RESTORE |
|
|
SCB_STATE_RESTORE_UNDERWAY |
|
|
SCB_STATE_PROTECT_SPARSE_MCB |
|
|
SCB_STATE_CONVERT_UNDERWAY |
|
|
SCB_STATE_ATTRIBUTE_DELETED))) {
|
|
|
|
//
|
|
// If the attribute is deleted and the type is a user logged stream then
|
|
// mark the Scb as type $UNUSED to keep us from ever accessing it again.
|
|
//
|
|
|
|
if ((ScbSnapshot->Scb->AttributeTypeCode == $LOGGED_UTILITY_STREAM ) &&
|
|
FlagOn( ScbSnapshot->Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
|
|
|
|
ScbSnapshot->Scb->AttributeTypeCode = $UNUSED;
|
|
}
|
|
|
|
//
|
|
// Clear the state flags which indicate whether there is a transitional change
|
|
// underway.
|
|
//
|
|
|
|
if (FlagOn( ScbSnapshot->Scb->ScbState,
|
|
(SCB_STATE_UNINITIALIZE_ON_RESTORE |
|
|
SCB_STATE_RESTORE_UNDERWAY |
|
|
SCB_STATE_PROTECT_SPARSE_MCB |
|
|
SCB_STATE_CONVERT_UNDERWAY ))) {
|
|
|
|
NtfsAcquireFsrtlHeader( ScbSnapshot->Scb );
|
|
ClearFlag( ScbSnapshot->Scb->ScbState,
|
|
SCB_STATE_UNINITIALIZE_ON_RESTORE | SCB_STATE_RESTORE_UNDERWAY | SCB_STATE_PROTECT_SPARSE_MCB | SCB_STATE_CONVERT_UNDERWAY );
|
|
NtfsReleaseFsrtlHeader( ScbSnapshot->Scb );
|
|
}
|
|
}
|
|
|
|
ScbSnapshot->Scb->ScbSnapshot = NULL;
|
|
}
|
|
|
|
if (ScbSnapshot == &IrpContext->ScbSnapshot) {
|
|
|
|
IrpContext->ScbSnapshot.Scb = NULL;
|
|
|
|
//
|
|
// Else delete the snapshot structure
|
|
//
|
|
|
|
} else {
|
|
|
|
RemoveEntryList(&ScbSnapshot->SnapshotLinks);
|
|
ExFreeToNPagedLookasideList( &NtfsScbSnapshotLookasideList, ScbSnapshot );
|
|
}
|
|
|
|
ScbSnapshot = NextScbSnapshot;
|
|
|
|
} while (ScbSnapshot != &IrpContext->ScbSnapshot);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsCreateFileLock (
|
|
IN PSCB Scb,
|
|
IN BOOLEAN RaiseOnError
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create and initialize a file lock structure.
|
|
A try-except is used to catch allocation failures if the caller doesn't
|
|
want the exception raised.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb to attach the file lock to.
|
|
|
|
RaiseOnError - If TRUE then don't catch the allocation failure.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the lock is allocated and initialized. FALSE if there is an
|
|
error and the caller didn't specify RaiseOnError.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_LOCK FileLock = NULL;
|
|
BOOLEAN Success = TRUE;
|
|
|
|
PAGED_CODE();
|
|
|
|
FileLock = FsRtlAllocateFileLock( NULL, NULL );
|
|
|
|
if (FileLock != NULL) {
|
|
|
|
//
|
|
// Use the FsRtl header mutex to synchronize storing
|
|
// the lock structure, and only store it if no one
|
|
// else beat us.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader(Scb);
|
|
|
|
if (Scb->ScbType.Data.FileLock == NULL) {
|
|
Scb->ScbType.Data.FileLock = FileLock;
|
|
FileLock = NULL;
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader(Scb);
|
|
|
|
if (FileLock != NULL) {
|
|
FsRtlFreeFileLock( FileLock );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Fail appropriately.
|
|
//
|
|
|
|
if (RaiseOnError) {
|
|
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
|
|
Success = FALSE;
|
|
}
|
|
|
|
return Success;
|
|
}
|
|
|
|
|
|
PSCB
|
|
NtfsGetNextScb (
|
|
IN PSCB Scb,
|
|
IN PSCB TerminationScb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to iterate through Scbs in a tree.
|
|
|
|
The rules are:
|
|
|
|
. If you have a child, go to it, else
|
|
. If you have a next sibling, go to it, else
|
|
. Go to your parent's next sibling.
|
|
|
|
If this routine is called with in invalid TerminationScb it will fail,
|
|
badly.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the current Scb
|
|
|
|
TerminationScb - The Scb at which the enumeration should (non-inclusively)
|
|
stop. Assumed to be a directory.
|
|
|
|
Return Value:
|
|
|
|
The next Scb in the enumeration, or NULL if Scb was the final one.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB Results;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsGetNextScb, Scb = %08lx, TerminationScb = %08lx\n", Scb, TerminationScb) );
|
|
|
|
//
|
|
// If this is an index (i.e., not a file) and it has children then return
|
|
// the scb for the first child
|
|
//
|
|
// Scb
|
|
//
|
|
// / \.
|
|
// / \.
|
|
//
|
|
// ChildLcb
|
|
//
|
|
// |
|
|
// |
|
|
//
|
|
// ChildFcb
|
|
//
|
|
// / \.
|
|
// / \.
|
|
//
|
|
// Results
|
|
//
|
|
|
|
if (((SafeNodeType(Scb) == NTFS_NTC_SCB_INDEX) || (SafeNodeType(Scb) == NTFS_NTC_SCB_ROOT_INDEX))
|
|
|
|
&&
|
|
|
|
!IsListEmpty(&Scb->ScbType.Index.LcbQueue)) {
|
|
|
|
PLCB ChildLcb;
|
|
PFCB ChildFcb;
|
|
|
|
//
|
|
// locate the first lcb out of this scb and also the corresponding fcb
|
|
//
|
|
|
|
ChildLcb = NtfsGetNextChildLcb(Scb, NULL);
|
|
ChildFcb = ChildLcb->Fcb;
|
|
|
|
//
|
|
// Then as a bookkeeping means for ourselves we will move this
|
|
// lcb to the head of the fcb's lcb queue that way when we
|
|
// need to ask which link we went through to get here we will know
|
|
//
|
|
|
|
RemoveEntryList( &ChildLcb->FcbLinks );
|
|
InsertHeadList( &ChildFcb->LcbQueue, &ChildLcb->FcbLinks );
|
|
|
|
//
|
|
// And our return value is the first scb of this fcb
|
|
//
|
|
|
|
ASSERT( !IsListEmpty(&ChildFcb->ScbQueue) );
|
|
|
|
//
|
|
// Acquire and drop the Fcb in order to look at the Scb list.
|
|
//
|
|
|
|
ExAcquireResourceExclusiveLite( ChildFcb->Resource, TRUE );
|
|
Results = NtfsGetNextChildScb( ChildFcb, NULL );
|
|
ExReleaseResourceLite( ChildFcb->Resource );
|
|
|
|
//
|
|
// We could be processing an empty index
|
|
//
|
|
|
|
} else if ( Scb == TerminationScb ) {
|
|
|
|
Results = NULL;
|
|
|
|
} else {
|
|
|
|
PSCB SiblingScb;
|
|
PFCB ParentFcb;
|
|
PLCB ParentLcb;
|
|
PLCB SiblingLcb;
|
|
PFCB SiblingFcb;
|
|
|
|
//
|
|
// Acquire and drop the Fcb in order to look at the Scb list.
|
|
//
|
|
|
|
ExAcquireResourceExclusiveLite( Scb->Fcb->Resource, TRUE );
|
|
SiblingScb = NtfsGetNextChildScb( Scb->Fcb, Scb );
|
|
ExReleaseResourceLite( Scb->Fcb->Resource );
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// If there is a sibling scb to the input scb then return it
|
|
//
|
|
// Fcb
|
|
//
|
|
// / \.
|
|
// / \.
|
|
//
|
|
// Scb Sibling
|
|
// Scb
|
|
//
|
|
|
|
if (SiblingScb != NULL) {
|
|
|
|
Results = SiblingScb;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The scb doesn't have any more siblings. See if our fcb has a sibling
|
|
//
|
|
// S
|
|
//
|
|
// / \.
|
|
// / \.
|
|
//
|
|
// ParentLcb SiblingLcb
|
|
//
|
|
// | |
|
|
// | |
|
|
//
|
|
// ParentFcb SiblingFcb
|
|
//
|
|
// / / \.
|
|
// / / \.
|
|
//
|
|
// Scb Results
|
|
//
|
|
// It's possible that the SiblingFcb has already been traversed.
|
|
// Consider the case where there are multiple links between the
|
|
// same Scb and Fcb. We want to ignore this case or else face
|
|
// an infinite loop by moving the Lcb to the beginning of the
|
|
// Fcb queue and then later finding an Lcb that we have already
|
|
// traverse. We use the fact that we haven't modified the
|
|
// ordering of the Lcb off the parent Scb. When we find a
|
|
// candidate for the next Fcb, we walk backwards through the
|
|
// list of Lcb's off the Scb to make sure this is not a
|
|
// duplicate Fcb.
|
|
//
|
|
|
|
ParentFcb = Scb->Fcb;
|
|
|
|
ParentLcb = NtfsGetNextParentLcb(ParentFcb, NULL);
|
|
|
|
//
|
|
// Try to find a sibling Lcb which does not point to an Fcb
|
|
// we've already visited.
|
|
//
|
|
|
|
SiblingLcb = ParentLcb;
|
|
|
|
while ((SiblingLcb = NtfsGetNextChildLcb( ParentLcb->Scb, SiblingLcb)) != NULL) {
|
|
|
|
PLCB PrevChildLcb;
|
|
PFCB PotentialSiblingFcb;
|
|
|
|
//
|
|
// Now walk through the child Lcb's of the Scb which we have
|
|
// already visited.
|
|
//
|
|
|
|
PrevChildLcb = SiblingLcb;
|
|
PotentialSiblingFcb = SiblingLcb->Fcb;
|
|
|
|
//
|
|
// Skip this Lcb if the Fcb has no children.
|
|
//
|
|
|
|
if (IsListEmpty( &PotentialSiblingFcb->ScbQueue )) {
|
|
|
|
continue;
|
|
}
|
|
|
|
while ((PrevChildLcb = NtfsGetPrevChildLcb( ParentLcb->Scb, PrevChildLcb )) != NULL) {
|
|
|
|
//
|
|
// If the parent Fcb and the Fcb for this Lcb are the same,
|
|
// then we have already returned the Scb's for this Fcb.
|
|
//
|
|
|
|
if (PrevChildLcb->Fcb == PotentialSiblingFcb) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we don't have a PrevChildLcb, that means that we have a valid
|
|
// sibling Lcb. We will ignore any sibling Lcb's whose
|
|
// Fcb's don't have any Scb's.
|
|
//
|
|
|
|
if (PrevChildLcb == NULL) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SiblingLcb != NULL) {
|
|
|
|
SiblingFcb = SiblingLcb->Fcb;
|
|
|
|
//
|
|
// Then as a bookkeeping means for ourselves we will move this
|
|
// lcb to the head of the fcb's lcb queue that way when we
|
|
// need to ask which link we went through to get here we will know
|
|
//
|
|
|
|
RemoveEntryList( &SiblingLcb->FcbLinks );
|
|
InsertHeadList( &SiblingFcb->LcbQueue, &SiblingLcb->FcbLinks );
|
|
|
|
//
|
|
// And our return value is the first scb of this fcb
|
|
//
|
|
|
|
ASSERT( !IsListEmpty(&SiblingFcb->ScbQueue) );
|
|
|
|
//
|
|
// Acquire and drop the Fcb in order to look at the Scb list.
|
|
//
|
|
|
|
ExAcquireResourceExclusiveLite( SiblingFcb->Resource, TRUE );
|
|
Results = NtfsGetNextChildScb( SiblingFcb, NULL );
|
|
ExReleaseResourceLite( SiblingFcb->Resource );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The Fcb has no sibling so bounce up one and see if we
|
|
// have reached our termination scb yet
|
|
//
|
|
// NewScb
|
|
//
|
|
// /
|
|
// /
|
|
//
|
|
// ParentLcb
|
|
//
|
|
// |
|
|
// |
|
|
//
|
|
// ParentFcb
|
|
//
|
|
// /
|
|
// /
|
|
//
|
|
// Scb
|
|
//
|
|
//
|
|
|
|
Scb = ParentLcb->Scb;
|
|
|
|
if (Scb == TerminationScb) {
|
|
|
|
Results = NULL;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Acquire and drop the Fcb in order to look at the Scb list.
|
|
//
|
|
|
|
ExAcquireResourceExclusiveLite( Scb->Fcb->Resource, TRUE );
|
|
SiblingScb = NtfsGetNextChildScb( Scb->Fcb, Scb );
|
|
ExReleaseResourceLite( Scb->Fcb->Resource );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsGetNextScb -> %08lx\n", Results) );
|
|
|
|
return Results;
|
|
}
|
|
|
|
|
|
PLCB
|
|
NtfsCreateLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN PFCB Fcb,
|
|
IN UNICODE_STRING LastComponentFileName,
|
|
IN UCHAR FileNameFlags,
|
|
IN OUT PBOOLEAN ReturnedExistingLcb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates and creates a new lcb between an
|
|
existing scb and fcb. If a component of the exact
|
|
name already exists we return that one instead of creating
|
|
a new lcb
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the parent scb to use
|
|
|
|
Fcb - Supplies the child fcb to use
|
|
|
|
LastComponentFileName - Supplies the last component of the
|
|
path that this link represents
|
|
|
|
FileNameFlags - Indicates if this is an NTFS, DOS or hard link
|
|
|
|
ReturnedExistingLcb - Optionally tells the caller if the
|
|
lcb returned already existed. If specified and points to a
|
|
FALSE value on entry then we won't create the new Lcb.
|
|
|
|
Return Value:
|
|
|
|
LCB - returns a pointer the newly created lcb. NULL if our caller doesn't
|
|
want to create an Lcb and it didn't already exist.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb = NULL;
|
|
BOOLEAN LocalReturnedExistingLcb = TRUE;
|
|
|
|
//
|
|
// The following variables are only used for abnormal termination
|
|
//
|
|
|
|
PVOID UnwindStorage[2] = { NULL, NULL };
|
|
|
|
PAGED_CODE();
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_FCB( Fcb );
|
|
ASSERT(NodeType(Scb) != NTFS_NTC_SCB_DATA);
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateLcb...\n") );
|
|
|
|
if (!ARGUMENT_PRESENT(ReturnedExistingLcb)) { ReturnedExistingLcb = &LocalReturnedExistingLcb; }
|
|
|
|
//
|
|
// Search the lcb children of the input Scb to see if we have an Lcb that matches
|
|
// this one. We match if the Lcb points to the same fcb and the last component file name
|
|
// and flags match. We ignore any Lcb's that indicate links that have been
|
|
// removed.
|
|
//
|
|
|
|
Lcb = NULL;
|
|
|
|
while ((Lcb = NtfsGetNextParentLcb(Fcb, Lcb)) != NULL) {
|
|
|
|
ASSERT_LCB( Lcb );
|
|
|
|
if ((Scb == Lcb->Scb) &&
|
|
|
|
(!FlagOn( Lcb->LcbState, LCB_STATE_LINK_IS_GONE )) &&
|
|
|
|
(FileNameFlags == Lcb->FileNameAttr->Flags) &&
|
|
|
|
(LastComponentFileName.Length == Lcb->ExactCaseLink.LinkName.Length) &&
|
|
|
|
(RtlEqualMemory( LastComponentFileName.Buffer,
|
|
Lcb->ExactCaseLink.LinkName.Buffer,
|
|
LastComponentFileName.Length ) )) {
|
|
|
|
*ReturnedExistingLcb = TRUE;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateLcb -> %08lx\n", Lcb) );
|
|
|
|
return Lcb;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If our caller does not want us to create a new Lcb then return FALSE.
|
|
//
|
|
|
|
if (!(*ReturnedExistingLcb)) {
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateLcb -> %08lx\n", NULL) );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
*ReturnedExistingLcb = FALSE;
|
|
|
|
try {
|
|
|
|
UCHAR MaxNameLength;
|
|
|
|
//
|
|
// Allocate a new lcb, zero it out and set the node type information
|
|
// Check if we can allocate the Lcb out of a compound Fcb. Check here if
|
|
// we can use the embedded name as well.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_DATA) &&
|
|
(SafeNodeType( &((PFCB_DATA) Fcb)->Lcb ) == 0)) {
|
|
|
|
Lcb = (PLCB) &((PFCB_DATA) Fcb)->Lcb;
|
|
MaxNameLength = MAX_DATA_FILE_NAME;
|
|
|
|
} else if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
|
|
(SafeNodeType( &((PFCB_INDEX) Fcb)->Lcb ) == 0)) {
|
|
|
|
Lcb = (PLCB) &((PFCB_INDEX) Fcb)->Lcb;
|
|
MaxNameLength = MAX_INDEX_FILE_NAME;
|
|
|
|
} else {
|
|
|
|
Lcb = UnwindStorage[0] = ExAllocateFromPagedLookasideList( &NtfsLcbLookasideList );
|
|
MaxNameLength = 0;
|
|
}
|
|
|
|
RtlZeroMemory( Lcb, sizeof(LCB) );
|
|
|
|
Lcb->NodeTypeCode = NTFS_NTC_LCB;
|
|
Lcb->NodeByteSize = sizeof(LCB);
|
|
|
|
//
|
|
// Check if we will have to allocate a separate filename attr.
|
|
//
|
|
|
|
if (MaxNameLength < (USHORT) (LastComponentFileName.Length / sizeof( WCHAR ))) {
|
|
|
|
//
|
|
// Allocate the last component part of the lcb and copy over the data.
|
|
// Check if there is space in the Fcb for this.
|
|
//
|
|
|
|
Lcb->FileNameAttr =
|
|
UnwindStorage[1] = NtfsAllocatePool(PagedPool, LastComponentFileName.Length +
|
|
NtfsFileNameSizeFromLength( LastComponentFileName.Length ));
|
|
|
|
MaxNameLength = (UCHAR)(LastComponentFileName.Length / sizeof( WCHAR ));
|
|
|
|
} else {
|
|
|
|
Lcb->FileNameAttr = (PFILE_NAME) &Lcb->ParentDirectory;
|
|
}
|
|
|
|
Lcb->FileNameAttr->ParentDirectory = Scb->Fcb->FileReference;
|
|
Lcb->FileNameAttr->FileNameLength = (UCHAR) (LastComponentFileName.Length / sizeof( WCHAR ));
|
|
Lcb->FileNameAttr->Flags = FileNameFlags;
|
|
|
|
Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
|
|
|
|
Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( MaxNameLength * sizeof( WCHAR )));
|
|
|
|
Lcb->ExactCaseLink.LinkName.Length =
|
|
Lcb->IgnoreCaseLink.LinkName.Length = LastComponentFileName.Length;
|
|
|
|
Lcb->ExactCaseLink.LinkName.MaximumLength =
|
|
Lcb->IgnoreCaseLink.LinkName.MaximumLength = MaxNameLength * sizeof( WCHAR );
|
|
|
|
RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
|
|
LastComponentFileName.Buffer,
|
|
LastComponentFileName.Length );
|
|
|
|
RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
|
|
LastComponentFileName.Buffer,
|
|
LastComponentFileName.Length );
|
|
|
|
NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
|
|
IrpContext->Vcb->UpcaseTableSize,
|
|
&Lcb->IgnoreCaseLink.LinkName );
|
|
|
|
//
|
|
// Now put this Lcb into the queues for the scb and the fcb
|
|
//
|
|
|
|
InsertTailList( &Scb->ScbType.Index.LcbQueue, &Lcb->ScbLinks );
|
|
Lcb->Scb = Scb;
|
|
|
|
InsertTailList( &Fcb->LcbQueue, &Lcb->FcbLinks );
|
|
Lcb->Fcb = Fcb;
|
|
|
|
//
|
|
// Now initialize the ccb queue.
|
|
//
|
|
|
|
InitializeListHead( &Lcb->CcbQueue );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsCreateLcb );
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
if (UnwindStorage[0]) { NtfsFreePool( UnwindStorage[0] );
|
|
} else if (Lcb != NULL) { Lcb->NodeTypeCode = 0; }
|
|
if (UnwindStorage[1]) { NtfsFreePool( UnwindStorage[1] ); }
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateLcb -> %08lx\n", Lcb) );
|
|
|
|
return Lcb;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN OUT PLCB *Lcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deallocated and removes the lcb record from Ntfs's in-memory
|
|
data structures. It assumes that the ccb queue is empty. We also assume
|
|
that this is not the root lcb that we are trying to delete.
|
|
|
|
Arguments:
|
|
|
|
Lcb - Supplise the Lcb to be removed
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCCB Ccb;
|
|
PLIST_ENTRY Links;
|
|
|
|
PAGED_CODE();
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteLcb, *Lcb = %08lx\n", *Lcb) );
|
|
|
|
//
|
|
// Get rid of any prefixes that might still be attached to us
|
|
//
|
|
|
|
NtfsRemovePrefix( (*Lcb) );
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( *Lcb );
|
|
|
|
//
|
|
// Walk through the Ccb's for this link and clear the Lcb
|
|
// pointer. This can only be for Ccb's which there is no
|
|
// more user handle.
|
|
//
|
|
|
|
Links = (*Lcb)->CcbQueue.Flink;
|
|
|
|
while (Links != &(*Lcb)->CcbQueue) {
|
|
|
|
Ccb = CONTAINING_RECORD( Links,
|
|
CCB,
|
|
LcbLinks );
|
|
|
|
Links = Links->Flink;
|
|
NtfsUnlinkCcbFromLcb( IrpContext, (*Lcb)->Fcb, Ccb );
|
|
}
|
|
|
|
//
|
|
//
|
|
// Now remove ourselves from our scb and fcb
|
|
//
|
|
|
|
RemoveEntryList( &(*Lcb)->ScbLinks );
|
|
RemoveEntryList( &(*Lcb)->FcbLinks );
|
|
|
|
//
|
|
// Free up the last component part and then free ourselves
|
|
//
|
|
|
|
if ((*Lcb)->FileNameAttr != (PFILE_NAME) &(*Lcb)->ParentDirectory) {
|
|
|
|
NtfsFreePool( (*Lcb)->FileNameAttr );
|
|
DebugDoit( (*Lcb)->FileNameAttr = NULL );
|
|
}
|
|
|
|
//
|
|
// Check if we are part of an embedded structure otherwise free back to the
|
|
// lookaside list
|
|
//
|
|
|
|
if (((*Lcb) == (PLCB) &((PFCB_DATA) (*Lcb)->Fcb)->Lcb) ||
|
|
((*Lcb) == (PLCB) &((PFCB_INDEX) (*Lcb)->Fcb)->Lcb)) {
|
|
|
|
#ifdef KEITHKADBG
|
|
RtlZeroMemory( *Lcb, sizeof( LCB ) );
|
|
#endif
|
|
|
|
(*Lcb)->NodeTypeCode = 0;
|
|
|
|
} else {
|
|
|
|
#ifdef KEITHKADBG
|
|
RtlZeroMemory( *Lcb, sizeof( LCB ) );
|
|
#endif
|
|
|
|
ExFreeToPagedLookasideList( &NtfsLcbLookasideList, *Lcb );
|
|
}
|
|
|
|
//
|
|
// And for safety sake null out the pointer
|
|
//
|
|
|
|
*Lcb = NULL;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteLcb -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsMoveLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PLCB Lcb,
|
|
IN PSCB Scb,
|
|
IN PFCB Fcb,
|
|
IN PUNICODE_STRING TargetDirectoryName,
|
|
IN PUNICODE_STRING LastComponentName,
|
|
IN UCHAR FileNameFlags,
|
|
IN BOOLEAN CheckBufferSizeOnly
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine completely moves the input lcb to join different fcbs and
|
|
scbs. It hasIt uses the target directory
|
|
file object to supply the complete new name to use.
|
|
|
|
Arguments:
|
|
|
|
Lcb - Supplies the Lcb being moved.
|
|
|
|
Scb - Supplies the new parent scb
|
|
|
|
Fcb - Supplies the new child fcb
|
|
|
|
TargetDirectoryName - This is the path used to reach the new parent directory
|
|
for this Lcb. It will only be from the root.
|
|
|
|
LastComponentName - This is the last component name to store in this relocated Lcb.
|
|
|
|
FileNameFlags - Indicates if this is an NTFS, DOS or hard link
|
|
|
|
CheckBufferSizeOnly - If TRUE we just want to pass through and verify that
|
|
the buffer sizes of the various structures will be large enough for the
|
|
new name.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = Scb->Vcb;
|
|
ULONG BytesNeeded;
|
|
PVOID NewAllocation;
|
|
PCHAR NextChar;
|
|
|
|
PCCB Ccb;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_LCB( Lcb );
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_FCB( Fcb );
|
|
ASSERT( NodeType( Scb ) != NTFS_NTC_SCB_DATA );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsMoveLcb, Lcb = %08lx\n", Lcb) );
|
|
|
|
//
|
|
// If we're not just checking sizes then remove entries from the prefix table
|
|
// and the normalized name for descendents of the current scb.
|
|
//
|
|
|
|
if (!CheckBufferSizeOnly) {
|
|
|
|
NtfsClearRecursiveLcb ( Lcb );
|
|
}
|
|
|
|
//
|
|
// Remember the number of bytes needed for the last component.
|
|
//
|
|
|
|
BytesNeeded = LastComponentName->Length;
|
|
|
|
//
|
|
// Check if we need to allocate a new filename attribute. If so allocate
|
|
// it and store it into the new allocation buffer.
|
|
//
|
|
|
|
if (Lcb->ExactCaseLink.LinkName.MaximumLength < BytesNeeded) {
|
|
|
|
NewAllocation = NtfsAllocatePool( PagedPool,
|
|
BytesNeeded + NtfsFileNameSizeFromLength( BytesNeeded ));
|
|
|
|
//
|
|
// Set up the existing names into the new buffer. That way if we have an allocation
|
|
// failure below with the Ccb's the Lcb is still in a valid state.
|
|
//
|
|
|
|
RtlCopyMemory( NewAllocation,
|
|
Lcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( Lcb->ExactCaseLink.LinkName.MaximumLength ));
|
|
|
|
RtlCopyMemory( Add2Ptr( NewAllocation, NtfsFileNameSizeFromLength( BytesNeeded )),
|
|
Lcb->IgnoreCaseLink.LinkName.Buffer,
|
|
Lcb->IgnoreCaseLink.LinkName.MaximumLength );
|
|
|
|
if (Lcb->FileNameAttr != (PFILE_NAME) &Lcb->ParentDirectory) {
|
|
|
|
NtfsFreePool( Lcb->FileNameAttr );
|
|
}
|
|
|
|
Lcb->FileNameAttr = NewAllocation;
|
|
|
|
Lcb->ExactCaseLink.LinkName.MaximumLength =
|
|
Lcb->IgnoreCaseLink.LinkName.MaximumLength = (USHORT) BytesNeeded;
|
|
|
|
Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
|
|
Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( BytesNeeded ));
|
|
}
|
|
|
|
//
|
|
// Compute the full length of the name for the CCB, assume we will need a
|
|
// separator.
|
|
//
|
|
|
|
BytesNeeded = TargetDirectoryName->Length + sizeof( WCHAR );
|
|
|
|
//
|
|
// Now for every ccb attached to us we need to check if we need a new
|
|
// filename buffer.
|
|
//
|
|
|
|
NtfsReserveCcbNamesInLcb( IrpContext, Lcb, &BytesNeeded, LastComponentName->Length );
|
|
|
|
//
|
|
// Add back in the last component.
|
|
//
|
|
|
|
BytesNeeded += LastComponentName->Length;
|
|
|
|
//
|
|
// Now update the Lcb with the new values if we are to rewrite the buffers.
|
|
//
|
|
|
|
if (!CheckBufferSizeOnly) {
|
|
|
|
Lcb->FileNameAttr->ParentDirectory = Scb->Fcb->FileReference;
|
|
Lcb->FileNameAttr->FileNameLength = (UCHAR) (LastComponentName->Length / sizeof( WCHAR ));
|
|
Lcb->FileNameAttr->Flags = FileNameFlags;
|
|
|
|
Lcb->ExactCaseLink.LinkName.Length =
|
|
Lcb->IgnoreCaseLink.LinkName.Length = (USHORT) LastComponentName->Length;
|
|
|
|
RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
|
|
LastComponentName->Buffer,
|
|
LastComponentName->Length );
|
|
|
|
RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
|
|
LastComponentName->Buffer,
|
|
LastComponentName->Length );
|
|
|
|
NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
|
|
IrpContext->Vcb->UpcaseTableSize,
|
|
&Lcb->IgnoreCaseLink.LinkName );
|
|
|
|
//
|
|
// Now for every ccb attached to us we need to munge it file object name by
|
|
// copying over the entire new name
|
|
//
|
|
|
|
Ccb = NULL;
|
|
while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
|
|
|
|
//
|
|
// We ignore any Ccb's which are associated with open by File Id
|
|
// file objects or their file objects have gone through cleanup.
|
|
// Lock and unlock the Fcb to serialize access to the close flag.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLOSE )) {
|
|
|
|
Ccb->FullFileName.Length = (USHORT) BytesNeeded;
|
|
NextChar = (PCHAR) Ccb->FullFileName.Buffer;
|
|
|
|
RtlCopyMemory( NextChar,
|
|
TargetDirectoryName->Buffer,
|
|
TargetDirectoryName->Length );
|
|
|
|
NextChar += TargetDirectoryName->Length;
|
|
|
|
if (TargetDirectoryName->Length != sizeof( WCHAR )) {
|
|
|
|
*((PWCHAR) NextChar) = L'\\';
|
|
NextChar += sizeof( WCHAR );
|
|
|
|
} else {
|
|
|
|
Ccb->FullFileName.Length -= sizeof( WCHAR );
|
|
}
|
|
|
|
RtlCopyMemory( NextChar,
|
|
LastComponentName->Buffer,
|
|
LastComponentName->Length );
|
|
|
|
Ccb->LastFileNameOffset = (USHORT) (Ccb->FullFileName.Length - LastComponentName->Length);
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Now dequeue ourselves from our old scb and fcb and put us in the
|
|
// new fcb and scb queues.
|
|
//
|
|
|
|
RemoveEntryList( &Lcb->ScbLinks );
|
|
RemoveEntryList( &Lcb->FcbLinks );
|
|
|
|
InsertTailList( &Scb->ScbType.Index.LcbQueue, &Lcb->ScbLinks );
|
|
Lcb->Scb = Scb;
|
|
|
|
InsertTailList( &Fcb->LcbQueue, &Lcb->FcbLinks );
|
|
Lcb->Fcb = Fcb;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsRenameLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PLCB Lcb,
|
|
IN PUNICODE_STRING LastComponentFileName,
|
|
IN UCHAR FileNameFlags,
|
|
IN BOOLEAN CheckBufferSizeOnly
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine changes the last component name of the input lcb
|
|
It also walks through the opened ccb and munges their names and
|
|
also removes the lcb from the prefix table
|
|
|
|
Arguments:
|
|
|
|
Lcb - Supplies the Lcb being renamed
|
|
|
|
LastComponentFileName - Supplies the new last component to use
|
|
for the lcb name
|
|
|
|
FileNameFlags - Indicates if this is an NTFS, DOS or hard link
|
|
|
|
CheckBufferSizeOnly - If TRUE we just want to pass through and verify that
|
|
the buffer sizes of the various structures will be large enough for the
|
|
new name.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = Lcb->Fcb->Vcb;
|
|
ULONG BytesNeeded;
|
|
PVOID NewAllocation;
|
|
|
|
PCCB Ccb;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_LCB( Lcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If we're not just checking sizes then remove entries from the prefix table
|
|
// and the normalized name for descendents of the current scb.
|
|
//
|
|
|
|
if (!CheckBufferSizeOnly) {
|
|
|
|
NtfsClearRecursiveLcb ( Lcb );
|
|
}
|
|
|
|
//
|
|
// Remember the number of bytes needed for the last component.
|
|
//
|
|
|
|
BytesNeeded = LastComponentFileName->Length;
|
|
|
|
//
|
|
// Check if we need to allocate a new filename attribute. If so allocate
|
|
// it and store it into the new allocation buffer.
|
|
//
|
|
|
|
if (Lcb->ExactCaseLink.LinkName.MaximumLength < BytesNeeded) {
|
|
|
|
NewAllocation = NtfsAllocatePool( PagedPool,
|
|
BytesNeeded + NtfsFileNameSizeFromLength( BytesNeeded ));
|
|
|
|
//
|
|
// Set up the existing names into the new buffer. That way if we have an allocation
|
|
// failure below with the Ccb's the Lcb is still in a valid state.
|
|
//
|
|
|
|
RtlCopyMemory( NewAllocation,
|
|
Lcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( Lcb->ExactCaseLink.LinkName.MaximumLength ));
|
|
|
|
RtlCopyMemory( Add2Ptr( NewAllocation, NtfsFileNameSizeFromLength( BytesNeeded )),
|
|
Lcb->IgnoreCaseLink.LinkName.Buffer,
|
|
Lcb->IgnoreCaseLink.LinkName.MaximumLength );
|
|
|
|
if (Lcb->FileNameAttr != (PFILE_NAME) &Lcb->ParentDirectory) {
|
|
|
|
NtfsFreePool( Lcb->FileNameAttr );
|
|
}
|
|
|
|
Lcb->FileNameAttr = NewAllocation;
|
|
|
|
Lcb->ExactCaseLink.LinkName.MaximumLength =
|
|
Lcb->IgnoreCaseLink.LinkName.MaximumLength = (USHORT) BytesNeeded;
|
|
|
|
Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
|
|
Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
|
|
NtfsFileNameSizeFromLength( BytesNeeded ));
|
|
}
|
|
|
|
//
|
|
// Now for every ccb attached to us we need to check if we need a new
|
|
// filename buffer.
|
|
//
|
|
|
|
NtfsReserveCcbNamesInLcb( IrpContext, Lcb, NULL, BytesNeeded );
|
|
|
|
//
|
|
// Now update the Lcb and Ccb's with the new values if we are to rewrite the buffers.
|
|
//
|
|
|
|
if (!CheckBufferSizeOnly) {
|
|
|
|
BytesNeeded = LastComponentFileName->Length;
|
|
|
|
Lcb->FileNameAttr->FileNameLength = (UCHAR) (BytesNeeded / sizeof( WCHAR ));
|
|
Lcb->FileNameAttr->Flags = FileNameFlags;
|
|
|
|
Lcb->ExactCaseLink.LinkName.Length =
|
|
Lcb->IgnoreCaseLink.LinkName.Length = (USHORT) LastComponentFileName->Length;
|
|
|
|
RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
|
|
LastComponentFileName->Buffer,
|
|
BytesNeeded );
|
|
|
|
RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
|
|
LastComponentFileName->Buffer,
|
|
BytesNeeded );
|
|
|
|
NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
|
|
IrpContext->Vcb->UpcaseTableSize,
|
|
&Lcb->IgnoreCaseLink.LinkName );
|
|
|
|
//
|
|
// Now for every ccb attached to us we need to munge it file object name by
|
|
// copying over the entire new name
|
|
//
|
|
|
|
Ccb = NULL;
|
|
while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
|
|
|
|
//
|
|
// We ignore any Ccb's which are associated with open by File Id
|
|
// file objects. We also ignore any Ccb's which don't have a file
|
|
// object pointer. Lock and unlock the Fcb to serialize access
|
|
// to the close flag.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLOSE )) {
|
|
|
|
RtlCopyMemory( &Ccb->FullFileName.Buffer[ Ccb->LastFileNameOffset / sizeof( WCHAR ) ],
|
|
LastComponentFileName->Buffer,
|
|
BytesNeeded );
|
|
|
|
Ccb->FullFileName.Length = Ccb->LastFileNameOffset + (USHORT) BytesNeeded;
|
|
}
|
|
NtfsUnlockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsCombineLcbs (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PLCB PrimaryLcb,
|
|
IN PLCB AuxLcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called for the case where we have multiple Lcb's for a
|
|
file which connect to the same Scb. We are performing a link rename
|
|
operation which causes the links to be combined and we need to
|
|
move all of the Ccb's to the same Lcb. This routine will be called only
|
|
after the names have been munged so that they are identical.
|
|
(i.e. call NtfsRenameLcb first)
|
|
|
|
Arguments:
|
|
|
|
PrimaryLcb - Supplies the Lcb to receive all the Ccb's and Pcb's.
|
|
|
|
AuxLcb - Supplies the Lcb to strip.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY Links;
|
|
PCCB NextCcb;
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCombineLcbs: Entered\n") );
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_LCB( PrimaryLcb );
|
|
ASSERT_LCB( AuxLcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Move all of the Ccb's first.
|
|
//
|
|
|
|
for (Links = AuxLcb->CcbQueue.Flink;
|
|
Links != &AuxLcb->CcbQueue;
|
|
Links = AuxLcb->CcbQueue.Flink) {
|
|
|
|
NextCcb = CONTAINING_RECORD( Links, CCB, LcbLinks );
|
|
NtfsUnlinkCcbFromLcb( IrpContext, AuxLcb->Fcb, NextCcb );
|
|
NtfsLinkCcbToLcb( IrpContext, PrimaryLcb->Fcb, NextCcb, PrimaryLcb );
|
|
}
|
|
|
|
//
|
|
// Now do the prefix entries.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( AuxLcb->Scb ) );
|
|
NtfsRemovePrefix( AuxLcb );
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( AuxLcb );
|
|
|
|
//
|
|
// Finally we need to transfer the unclean counts from the
|
|
// Lcb being merged to the primary Lcb.
|
|
//
|
|
|
|
PrimaryLcb->CleanupCount += AuxLcb->CleanupCount;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCombineLcbs: Entered\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
PLCB
|
|
NtfsLookupLcbByFlags (
|
|
IN PFCB Fcb,
|
|
IN UCHAR FileNameFlags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to find a split primary link by the file flag
|
|
only.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file.
|
|
|
|
FileNameFlags - This is the file flag to search for. We will return
|
|
a link which matches this exactly.
|
|
|
|
Return Value:
|
|
|
|
PLCB - The Lcb which has the desired flag, NULL otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb;
|
|
|
|
PLIST_ENTRY Links;
|
|
PLCB ThisLcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsLookupLcbByFlags: Entered\n") );
|
|
|
|
Lcb = NULL;
|
|
|
|
//
|
|
// Walk through the Lcb's for the file, looking for an exact match.
|
|
//
|
|
|
|
for (Links = Fcb->LcbQueue.Flink; Links != &Fcb->LcbQueue; Links = Links->Flink) {
|
|
|
|
ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
|
|
|
|
if (ThisLcb->FileNameAttr->Flags == FileNameFlags) {
|
|
|
|
Lcb = ThisLcb;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsLookupLcbByFlags: Exit\n") );
|
|
|
|
return Lcb;
|
|
}
|
|
|
|
|
|
|
|
ULONG
|
|
NtfsLookupNameLengthViaLcb (
|
|
IN PFCB Fcb,
|
|
OUT PBOOLEAN LeadingBackslash
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to find the length of the file name by walking
|
|
backwards through the Lcb links.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file.
|
|
|
|
LeadingBackslash - On return, indicates whether this chain begins with a
|
|
backslash.
|
|
|
|
Return Value:
|
|
|
|
ULONG This is the length of the bytes found in the Lcb chain.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG NameLength;
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsLookupNameLengthViaLcb: Entered\n") );
|
|
|
|
//
|
|
// Initialize the return values.
|
|
//
|
|
|
|
NameLength = 0;
|
|
*LeadingBackslash = FALSE;
|
|
|
|
//
|
|
// If there is no Lcb we are done.
|
|
//
|
|
|
|
if (!IsListEmpty( &Fcb->LcbQueue )) {
|
|
|
|
PLCB ThisLcb;
|
|
BOOLEAN FirstComponent;
|
|
|
|
//
|
|
// Walk up the list of Lcb's and count the name elements.
|
|
//
|
|
|
|
FirstComponent = TRUE;
|
|
|
|
ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks );
|
|
|
|
//
|
|
// Loop until we have reached the root or there are no more Lcb's.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
if (ThisLcb == Fcb->Vcb->RootLcb) {
|
|
|
|
NameLength += sizeof( WCHAR );
|
|
*LeadingBackslash = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If this is not the first component, we add room for a separating
|
|
// forward slash.
|
|
//
|
|
|
|
if (!FirstComponent) {
|
|
|
|
NameLength += sizeof( WCHAR );
|
|
|
|
} else {
|
|
|
|
FirstComponent = FALSE;
|
|
}
|
|
|
|
NameLength += ThisLcb->ExactCaseLink.LinkName.Length;
|
|
|
|
//
|
|
// If the next Fcb has no Lcb we exit.
|
|
//
|
|
|
|
Fcb = ((PSCB) ThisLcb->Scb)->Fcb;
|
|
|
|
if (IsListEmpty( &Fcb->LcbQueue)) {
|
|
|
|
break;
|
|
}
|
|
|
|
ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks );
|
|
}
|
|
|
|
//
|
|
// If this is a system file we use the hard coded name.
|
|
//
|
|
|
|
} else if (NtfsSegmentNumber( &Fcb->FileReference ) <= UPCASE_TABLE_NUMBER) {
|
|
|
|
NameLength = NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length;
|
|
*LeadingBackslash = TRUE;
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsLookupNameLengthViaLcb: Exit - %08lx\n", NameLength) );
|
|
return NameLength;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsFileNameViaLcb (
|
|
IN PFCB Fcb,
|
|
IN PWCHAR FileName,
|
|
ULONG Length,
|
|
ULONG BytesToCopy
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to fill a buffer with the generated filename. The name
|
|
is constructed by walking backwards through the Lcb chain from the current Fcb.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file.
|
|
|
|
FileName - This is the buffer to fill with the name.
|
|
|
|
Length - This is the length of the name. Already calculated by calling
|
|
NtfsLookupNameLengthViaLcb.
|
|
|
|
BytesToCopy - This indicates the number of bytes we are to copy. We drop
|
|
any characters out of the trailing Lcb's to only insert the beginning
|
|
of the path.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG BytesToDrop;
|
|
|
|
PWCHAR ThisName;
|
|
DebugTrace( +1, Dbg, ("NtfsFileNameViaLcb: Entered\n") );
|
|
|
|
//
|
|
// If there is no Lcb or there are no bytes to copy we are done.
|
|
//
|
|
|
|
if (BytesToCopy) {
|
|
|
|
if (!IsListEmpty( &Fcb->LcbQueue )) {
|
|
|
|
PLCB ThisLcb;
|
|
BOOLEAN FirstComponent;
|
|
|
|
//
|
|
// Walk up the list of Lcb's and count the name elements.
|
|
//
|
|
|
|
FirstComponent = TRUE;
|
|
|
|
ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks );
|
|
|
|
//
|
|
// Loop until we have reached the root or there are no more Lcb's.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
if (ThisLcb == Fcb->Vcb->RootLcb) {
|
|
|
|
*FileName = L'\\';
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If this is not the first component, we add room for a separating
|
|
// forward slash.
|
|
//
|
|
|
|
if (!FirstComponent) {
|
|
|
|
Length -= sizeof( WCHAR );
|
|
ThisName = (PWCHAR) Add2Ptr( FileName,
|
|
Length );
|
|
|
|
if (Length < BytesToCopy) {
|
|
|
|
*ThisName = L'\\';
|
|
}
|
|
|
|
} else {
|
|
|
|
FirstComponent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Length is current pointing just beyond where the next
|
|
// copy will end. If we are beyond the number of bytes to copy
|
|
// then we will truncate the copy.
|
|
//
|
|
|
|
if (Length > BytesToCopy) {
|
|
|
|
BytesToDrop = Length - BytesToCopy;
|
|
|
|
} else {
|
|
|
|
BytesToDrop = 0;
|
|
}
|
|
|
|
Length -= ThisLcb->ExactCaseLink.LinkName.Length;
|
|
|
|
ThisName = (PWCHAR) Add2Ptr( FileName,
|
|
Length );
|
|
|
|
//
|
|
// Only perform the copy if we are in the range of bytes to copy.
|
|
//
|
|
|
|
if (Length < BytesToCopy) {
|
|
|
|
RtlCopyMemory( ThisName,
|
|
ThisLcb->ExactCaseLink.LinkName.Buffer,
|
|
ThisLcb->ExactCaseLink.LinkName.Length - BytesToDrop );
|
|
}
|
|
|
|
//
|
|
// If the next Fcb has no Lcb we exit.
|
|
//
|
|
|
|
Fcb = ((PSCB) ThisLcb->Scb)->Fcb;
|
|
|
|
if (IsListEmpty( &Fcb->LcbQueue)) {
|
|
|
|
break;
|
|
}
|
|
|
|
ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks );
|
|
}
|
|
|
|
//
|
|
// If this is a system file, we use the hard coded name.
|
|
//
|
|
|
|
} else if (NtfsSegmentNumber(&Fcb->FileReference) <= UPCASE_TABLE_NUMBER) {
|
|
|
|
if (BytesToCopy > NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length) {
|
|
|
|
BytesToCopy = NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length;
|
|
}
|
|
|
|
RtlCopyMemory( FileName,
|
|
NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Buffer,
|
|
BytesToCopy );
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsFileNameViaLcb: Exit\n") );
|
|
return;
|
|
}
|
|
|
|
|
|
PCCB
|
|
NtfsCreateCcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PSCB Scb,
|
|
IN BOOLEAN Indexed,
|
|
IN USHORT EaModificationCount,
|
|
IN ULONG Flags,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN ULONG LastFileNameOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a new CCB record
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file. We will check if we can allocate
|
|
the Ccb from an embedded structure.
|
|
|
|
Indexed - Indicates if we need an index Ccb.
|
|
|
|
EaModificationCount - This is the current modification count in the
|
|
Fcb for this file.
|
|
|
|
Flags - Informational flags for this Ccb.
|
|
|
|
FileObject - Object containing full path used to open this file.
|
|
|
|
LastFileNameOffset - Supplies the offset (in bytes) of the last component
|
|
for the name that the user is opening. If this is the root
|
|
directory it should denote "\" and all other ones should not
|
|
start with a backslash.
|
|
|
|
Return Value:
|
|
|
|
CCB - returns a pointer to the newly allocate CCB
|
|
|
|
--*/
|
|
|
|
{
|
|
PCCB Ccb;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsCreateCcb\n") );
|
|
|
|
//
|
|
// Allocate a new CCB Record. If the Fcb is nonpaged then we must allocate
|
|
// a non-paged ccb. Then test if we can allocate this out of the Fcb.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_NONPAGED )) {
|
|
|
|
if (Indexed) {
|
|
|
|
Ccb = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(CCB), 'CftN' );
|
|
|
|
} else {
|
|
|
|
Ccb = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(CCB_DATA), 'cftN' );
|
|
}
|
|
|
|
} else if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
|
|
(SafeNodeType( &((PFCB_INDEX) Fcb)->Ccb ) == 0)) {
|
|
|
|
Ccb = (PCCB) &((PFCB_INDEX) Fcb)->Ccb;
|
|
|
|
} else if (!Indexed &&
|
|
FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_DATA ) &&
|
|
(SafeNodeType( &((PFCB_DATA) Fcb)->Ccb ) == 0)) {
|
|
|
|
Ccb = (PCCB) &((PFCB_DATA) Fcb)->Ccb;
|
|
|
|
} else {
|
|
|
|
if (Indexed) {
|
|
|
|
Ccb = (PCCB)ExAllocateFromPagedLookasideList( &NtfsCcbLookasideList );
|
|
|
|
} else {
|
|
|
|
Ccb = (PCCB)ExAllocateFromPagedLookasideList( &NtfsCcbDataLookasideList );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Zero and initialize the correct structure.
|
|
//
|
|
|
|
if (Indexed) {
|
|
|
|
RtlZeroMemory( Ccb, sizeof(CCB) );
|
|
|
|
//
|
|
// Set the proper node type code and node byte size
|
|
//
|
|
|
|
Ccb->NodeTypeCode = NTFS_NTC_CCB_INDEX;
|
|
Ccb->NodeByteSize = sizeof(CCB);
|
|
|
|
} else {
|
|
|
|
RtlZeroMemory( Ccb, sizeof(CCB_DATA) );
|
|
|
|
//
|
|
// Set the proper node type code and node byte size
|
|
//
|
|
|
|
Ccb->NodeTypeCode = NTFS_NTC_CCB_DATA;
|
|
Ccb->NodeByteSize = sizeof(CCB_DATA);
|
|
}
|
|
|
|
//
|
|
// Copy the Ea modification count.
|
|
//
|
|
|
|
Ccb->EaModificationCount = EaModificationCount;
|
|
|
|
//
|
|
// Copy the flags field
|
|
//
|
|
|
|
Ccb->Flags = Flags;
|
|
|
|
//
|
|
// Set the file object and last file name offset fields
|
|
//
|
|
|
|
Ccb->FullFileName = FileObject->FileName;
|
|
Ccb->LastFileNameOffset = (USHORT)LastFileNameOffset;
|
|
|
|
//
|
|
// Initialize the Lcb queue.
|
|
//
|
|
|
|
InitializeListHead( &Ccb->LcbLinks );
|
|
|
|
//
|
|
// Add the Ccb onto the Scb
|
|
//
|
|
|
|
InsertTailList( &Scb->CcbQueue, &Ccb->CcbLinks );
|
|
|
|
#ifdef CCB_FILE_OBJECT
|
|
Ccb->FileObject = FileObject;
|
|
Ccb->Process = PsGetCurrentProcess();
|
|
#endif
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsCreateCcb -> %08lx\n", Ccb) );
|
|
|
|
return Ccb;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteCcb (
|
|
IN PFCB Fcb,
|
|
IN OUT PCCB *Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine deallocates the specified CCB record.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb for the file. We will check if we can allocate
|
|
the Ccb from an embedded structure.
|
|
|
|
Ccb - Supplies the CCB to remove
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_CCB( *Ccb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeleteCcb, Ccb = %08lx\n", Ccb) );
|
|
|
|
//
|
|
// Deallocate any structures the Ccb is pointing to. The following
|
|
// are only in index Ccb.
|
|
//
|
|
|
|
if (SafeNodeType( *Ccb ) == NTFS_NTC_CCB_INDEX) {
|
|
|
|
//
|
|
// Make sure we aren't deleting this with any waiters.
|
|
//
|
|
|
|
ASSERT( (*Ccb)->EnumQueue.Flink == NULL );
|
|
|
|
//
|
|
// If this Ccb was for a view index, we may need to
|
|
// free the read context used for directory enumeration.
|
|
//
|
|
|
|
if (FlagOn( (*Ccb)->Flags, CCB_FLAG_READ_CONTEXT_ALLOCATED )) {
|
|
|
|
NtOfsFreeReadContext( (*Ccb)->QueryBuffer );
|
|
|
|
} else if ((*Ccb)->QueryBuffer != NULL) {
|
|
|
|
NtfsFreePool( (*Ccb)->QueryBuffer );
|
|
}
|
|
|
|
if ((*Ccb)->IndexEntry != NULL) { NtfsFreePool( (*Ccb)->IndexEntry ); }
|
|
|
|
if ((*Ccb)->IndexContext != NULL) {
|
|
|
|
PINDEX_CONTEXT IndexContext;
|
|
|
|
if ((*Ccb)->IndexContext->Base != (*Ccb)->IndexContext->LookupStack) {
|
|
NtfsFreePool( (*Ccb)->IndexContext->Base );
|
|
}
|
|
|
|
//
|
|
// Copy the IndexContext pointer into the stack so we don't dereference the
|
|
// paged Ccb while holding a spinlock.
|
|
//
|
|
|
|
IndexContext = (*Ccb)->IndexContext;
|
|
ExFreeToPagedLookasideList( &NtfsIndexContextLookasideList, IndexContext );
|
|
}
|
|
}
|
|
|
|
if (FlagOn( (*Ccb)->Flags, CCB_FLAG_ALLOCATED_FILE_NAME )) {
|
|
|
|
NtfsFreePool( (*Ccb)->FullFileName.Buffer );
|
|
}
|
|
|
|
//
|
|
// Unhook Ccb from Scb list
|
|
//
|
|
|
|
RemoveEntryList( &(*Ccb)->CcbLinks );
|
|
|
|
//
|
|
// Deallocate the Ccb simply clear the flag in the Ccb header.
|
|
//
|
|
|
|
if ((*Ccb == (PCCB) &((PFCB_DATA) Fcb)->Ccb) ||
|
|
(*Ccb == (PCCB) &((PFCB_INDEX) Fcb)->Ccb)) {
|
|
|
|
(*Ccb)->NodeTypeCode = 0;
|
|
|
|
} else {
|
|
|
|
if (SafeNodeType( *Ccb ) == NTFS_NTC_CCB_INDEX) {
|
|
|
|
ExFreeToPagedLookasideList( &NtfsCcbLookasideList, *Ccb );
|
|
|
|
} else {
|
|
|
|
ExFreeToPagedLookasideList( &NtfsCcbDataLookasideList, *Ccb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Zero out the input pointer
|
|
//
|
|
|
|
*Ccb = NULL;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeleteCcb -> VOID\n") );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInitializeIrpContext (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN BOOLEAN Wait,
|
|
IN OUT PIRP_CONTEXT *IrpContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates and/or initializes a new IRP_CONTEXT record. The context
|
|
may be on the stack already or we might need to allocate it here.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the originating Irp. In many cases we won't be given an IrpContext for
|
|
operations where we are doing work for Ntfs not for the user.
|
|
operation.
|
|
|
|
Wait - Supplies the wait value to store in the context.
|
|
|
|
IrpContext - Address to store the IrpContext on return. If this initially points to
|
|
a non-NULL value then the IrpContext is on the stack.
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PVCB Vcb;
|
|
ULONG StateFlags = 0;
|
|
UCHAR MajorFunction;
|
|
UCHAR MinorFunction;
|
|
|
|
ASSERT_OPTIONAL_IRP( Irp );
|
|
|
|
//
|
|
// If the Irp is present then check that this is a legal operation for Ntfs.
|
|
//
|
|
// Also capture the Vcb, function codes and write-through state if we have
|
|
// a legal Irp.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( Irp )) {
|
|
|
|
ASSERT( (DWORD_PTR)(Irp->Tail.Overlay.AuxiliaryBuffer) != 0xFFFFFFFF );
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// If we were called with our file system device object instead of a
|
|
// volume device object and this is not a mount, the request is illegal.
|
|
//
|
|
|
|
if ((IrpSp->DeviceObject->Size == (USHORT)sizeof(DEVICE_OBJECT)) &&
|
|
(IrpSp->FileObject != NULL)) {
|
|
|
|
//
|
|
// Clear the IrpContext pointer so our caller knows the request failed.
|
|
//
|
|
|
|
*IrpContext = NULL;
|
|
ExRaiseStatus( STATUS_INVALID_DEVICE_REQUEST );
|
|
}
|
|
|
|
//
|
|
// Copy RealDevice for workque algorithms, and also set WriteThrough
|
|
// if there is a file object.
|
|
//
|
|
|
|
if (IrpSp->FileObject != NULL) {
|
|
|
|
//
|
|
// Locate the volume device object and Vcb that we are trying to access
|
|
// so we can see if the request is WriteThrough. We ignore the
|
|
// write-through flag for close and cleanup.
|
|
//
|
|
|
|
Vcb = &((PVOLUME_DEVICE_OBJECT) IrpSp->DeviceObject)->Vcb;
|
|
|
|
ASSERT( NodeType(Vcb) == NTFS_NTC_VCB );
|
|
|
|
ASSERTMSG( "No correspondence btwn file and device in irp",
|
|
((IrpSp->FileObject->Vpb == NULL) &&
|
|
((IrpSp->FileObject->DeviceObject != NULL) &&
|
|
(IrpSp->FileObject->DeviceObject->Vpb != NULL) &&
|
|
(IrpSp->DeviceObject == IrpSp->FileObject->DeviceObject->Vpb->DeviceObject))) ||
|
|
|
|
((IrpSp->FileObject->Vpb != NULL) &&
|
|
(IrpSp->DeviceObject == IrpSp->FileObject->Vpb->DeviceObject)) ||
|
|
|
|
(!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) );
|
|
|
|
if (IsFileWriteThrough( IrpSp->FileObject, Vcb )) {
|
|
|
|
StateFlags = IRP_CONTEXT_STATE_WRITE_THROUGH;
|
|
}
|
|
|
|
//
|
|
// We would still like to find out the Vcb in all cases except for
|
|
// mount.
|
|
//
|
|
|
|
} else if (IrpSp->DeviceObject != NULL) {
|
|
|
|
Vcb = &((PVOLUME_DEVICE_OBJECT) IrpSp->DeviceObject)->Vcb;
|
|
|
|
} else {
|
|
|
|
Vcb = NULL;
|
|
}
|
|
|
|
//
|
|
// Major/Minor Function codes
|
|
//
|
|
|
|
MajorFunction = IrpSp->MajorFunction;
|
|
MinorFunction = IrpSp->MinorFunction;
|
|
|
|
} else {
|
|
|
|
Vcb = NULL;
|
|
MajorFunction = 0;
|
|
MinorFunction = 0;
|
|
}
|
|
|
|
//
|
|
// Allocate an IrpContext from zone if available, otherwise from
|
|
// non-paged pool.
|
|
//
|
|
|
|
if (*IrpContext == NULL) {
|
|
|
|
*IrpContext = (PIRP_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIrpContextLookasideList );
|
|
SetFlag( StateFlags, IRP_CONTEXT_STATE_ALLOC_FROM_POOL );
|
|
}
|
|
|
|
DebugDoit( NtfsFsdEntryCount += 1);
|
|
|
|
RtlZeroMemory( *IrpContext, sizeof( IRP_CONTEXT ));
|
|
|
|
//
|
|
// Set the proper node type code and node byte size
|
|
//
|
|
|
|
(*IrpContext)->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
|
|
(*IrpContext)->NodeByteSize = sizeof(IRP_CONTEXT);
|
|
|
|
//
|
|
// Set the originating Irp field
|
|
//
|
|
|
|
(*IrpContext)->OriginatingIrp = Irp;
|
|
|
|
//
|
|
// Set the Vcb and function codes we found (or NULL).
|
|
//
|
|
|
|
(*IrpContext)->Vcb = Vcb;
|
|
(*IrpContext)->MajorFunction = MajorFunction;
|
|
(*IrpContext)->MinorFunction = MinorFunction;
|
|
|
|
//
|
|
// Set the wait and write through flags.
|
|
//
|
|
|
|
if (Wait) { SetFlag( (*IrpContext)->State, IRP_CONTEXT_STATE_WAIT ); }
|
|
SetFlag( (*IrpContext)->State, StateFlags );
|
|
|
|
//
|
|
// Initialize the recently deallocated record queue and exclusive Scb queue
|
|
//
|
|
|
|
InitializeListHead( &(*IrpContext)->RecentlyDeallocatedQueue );
|
|
InitializeListHead( &(*IrpContext)->ExclusiveFcbList );
|
|
|
|
//
|
|
// Always point to ourselves as the TopLevelIrpContext.
|
|
//
|
|
|
|
(*IrpContext)->TopLevelIrpContext = *IrpContext;
|
|
|
|
//
|
|
// Initialize the embedded scb snapshot
|
|
//
|
|
|
|
InitializeListHead( &(*IrpContext)->ScbSnapshot.SnapshotLinks );
|
|
|
|
//
|
|
// Set up LogFull testing
|
|
//
|
|
|
|
#ifdef NTFS_LOG_FULL_TEST
|
|
(*IrpContext)->CurrentFailCount = (*IrpContext)->NextFailCount = NtfsFailCheck;
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsCleanupIrpContext (
|
|
IN OUT PIRP_CONTEXT IrpContext,
|
|
IN ULONG Retry
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs cleanup on an IrpContext when we are finished using it in the current
|
|
thread. This can be because we are completing, retrying or posting a request. It may be
|
|
from the stack or allocated from pool.
|
|
|
|
This request can also be called after a transaction has committed to cleanup all of
|
|
the state information and resources held as part of the transaction. The user can set
|
|
the appropriate flags to prevent it from being deleted.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Supplies the IRP_CONTEXT to cleanup.
|
|
|
|
Retry - Indicates if we are retrying in the same thread or posting.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
|
|
//
|
|
// Start with the recently deallocated records.
|
|
//
|
|
|
|
if (!IsListEmpty( &IrpContext->RecentlyDeallocatedQueue )) {
|
|
|
|
NtfsDeallocateRecordsComplete( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Just in case we somehow get here with a transaction ID, clear
|
|
// it here so we do not loop forever.
|
|
//
|
|
|
|
ASSERT( IrpContext->TransactionId == 0 );
|
|
IrpContext->TransactionId = 0;
|
|
|
|
|
|
NtfsReleaseAllResources( IrpContext );
|
|
|
|
#ifdef MAPCOUNT_DBG
|
|
|
|
//
|
|
// Check all mapping are gone now that we cleaned out cache
|
|
//
|
|
|
|
ASSERT( IrpContext->MapCount == 0 );
|
|
|
|
#endif
|
|
|
|
//
|
|
// Make sure there are no Scb snapshots left. Most are freed above when the fcb's are released
|
|
// but preacquires from mm - for example doing a flushuserstream or deleted scbs will need to be
|
|
// cleaned up here
|
|
//
|
|
|
|
NtfsFreeSnapshotsForFcb( IrpContext, NULL );
|
|
|
|
//
|
|
// Make sure we don't need to deallocate a UsnFcb structure.
|
|
//
|
|
|
|
while (IrpContext->Usn.NextUsnFcb != NULL) {
|
|
|
|
PUSN_FCB ThisUsn;
|
|
|
|
ThisUsn = IrpContext->Usn.NextUsnFcb;
|
|
IrpContext->Usn.NextUsnFcb = ThisUsn->NextUsnFcb;
|
|
NtfsFreePool( ThisUsn );
|
|
}
|
|
|
|
//
|
|
// If we can delete this Irp Context do so now.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE ) &&
|
|
!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_PERSISTENT )) {
|
|
|
|
if (IrpContext->Union.NtfsIoContext != NULL) {
|
|
|
|
//
|
|
// If there is an Io context pointer in the irp context and it is not
|
|
// on the stack, then free it.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_IO_CONTEXT )) {
|
|
|
|
ExFreeToNPagedLookasideList( &NtfsIoContextLookasideList, IrpContext->Union.NtfsIoContext );
|
|
|
|
//
|
|
// If we have captured the subject context then free it now.
|
|
//
|
|
|
|
} else if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_SECURITY )) {
|
|
|
|
SeReleaseSubjectContext( IrpContext->Union.SubjectContext );
|
|
|
|
NtfsFreePool( IrpContext->Union.SubjectContext );
|
|
|
|
//
|
|
// Else if we locked the user buffer in a sep. mdl in ReadUsnFile
|
|
//
|
|
|
|
} else if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_MDL )) {
|
|
|
|
MmUnlockPages( IrpContext->Union.MdlToCleanup );
|
|
IoFreeMdl( IrpContext->Union.MdlToCleanup );
|
|
}
|
|
|
|
IrpContext->Union.NtfsIoContext = NULL;
|
|
}
|
|
|
|
//
|
|
// Restore the thread context pointer if associated with this IrpContext.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL )) {
|
|
|
|
NtfsRestoreTopLevelIrp();
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL );
|
|
}
|
|
|
|
//
|
|
// Return the IRP context record to the lookaside or to pool depending
|
|
// how much is currently in the lookaside
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_FROM_POOL )) {
|
|
|
|
ExFreeToNPagedLookasideList( &NtfsIrpContextLookasideList, IrpContext );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Do all any necessary to reinitialize IrpContext fields. We avoid doing
|
|
// these if the IrpContext is going away.
|
|
//
|
|
|
|
RtlZeroMemory( &IrpContext->ScbSnapshot, sizeof( SCB_SNAPSHOT ));
|
|
InitializeListHead( &IrpContext->ScbSnapshot.SnapshotLinks );
|
|
|
|
//
|
|
// Clear the appropriate flags unless our caller wanted to preserve them.
|
|
//
|
|
|
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_RETAIN_FLAGS )) {
|
|
|
|
//
|
|
// Set up the Irp Context for retry or post.
|
|
//
|
|
|
|
if (Retry) {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAGS_CLEAR_ON_RETRY );
|
|
|
|
} else {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAGS_CLEAR_ON_POST );
|
|
}
|
|
|
|
} else {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RETAIN_FLAGS | IRP_CONTEXT_FLAG_DONT_DELETE );
|
|
}
|
|
|
|
//
|
|
// Always clear the counts of free records and clusters.
|
|
//
|
|
|
|
IrpContext->DeallocatedClusters = 0;
|
|
IrpContext->FreeClusterChange = 0;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInitializeIoContext (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PNTFS_IO_CONTEXT IoContext,
|
|
IN BOOLEAN PagingIo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add a NTFS_IO_CONTEXT to the irpcontext and initializes it. If the request
|
|
is synchronous we'll try to use the IoContext passed in. If its asynch then
|
|
we'll allocate one from the lookaside list if its not already been done. Note:
|
|
we'll reuse a pool allocated io_context
|
|
|
|
However for an asynch request one must call NtfsSetIoContext Async afterwards
|
|
to fill in the additional parameters. Before that point its still marked synchronous
|
|
even if we allocated it from pool and the synch event is initialized for use
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Supplies the IRP_CONTEXT
|
|
|
|
IoContext - A local context to use if the the request is synchronous - can be
|
|
on the stack
|
|
|
|
PagingIo - Whether the operation is a paging operation
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
LOGICAL Wait = FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
if ((IrpContext->Union.NtfsIoContext == NULL) ||
|
|
!FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_IO_CONTEXT )) {
|
|
|
|
//
|
|
// If we can wait, use the context on the stack. Otherwise
|
|
// we need to allocate one.
|
|
//
|
|
|
|
if (Wait) {
|
|
|
|
IrpContext->Union.NtfsIoContext = IoContext;
|
|
ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_IO_CONTEXT );
|
|
|
|
} else {
|
|
|
|
IrpContext->Union.NtfsIoContext = (PNTFS_IO_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIoContextLookasideList );
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_IO_CONTEXT );
|
|
}
|
|
}
|
|
|
|
RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
|
|
|
|
//
|
|
// Store whether we allocated this context structure in the structure
|
|
// itself.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->State, IRP_CONTEXT_STATE_ALLOC_IO_CONTEXT )) {
|
|
SetFlag( IrpContext->Union.NtfsIoContext->Flags, NTFS_IO_CONTEXT_ALLOCATED );
|
|
}
|
|
|
|
if (PagingIo) {
|
|
SetFlag( IrpContext->Union.NtfsIoContext->Flags, NTFS_IO_CONTEXT_PAGING_IO );
|
|
}
|
|
|
|
IrpContext->Union.NtfsIoContext->MasterIrp = IrpContext->OriginatingIrp;
|
|
KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
|
|
NotificationEvent,
|
|
FALSE );
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsSetIoContextAsync (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PERESOURCE ResourceToRelease,
|
|
IN ULONG ByteCount
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets up the async field of an io context. Use this right before call
|
|
NtfsnonCachedIo for async requests. Because these fields are overloaded with
|
|
the event after calling this the syncrhonous event is not available
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Supplies the IRP_CONTEXT containing an io context
|
|
|
|
ResourceToRelease - resource to be released at async operations completion
|
|
|
|
ByteCount - Original requested bytecount of the transfer
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
IrpContext->Union.NtfsIoContext->Wait.Async.Resource = ResourceToRelease;
|
|
IrpContext->Union.NtfsIoContext->Wait.Async.ResourceThreadId = ExGetCurrentResourceThread();
|
|
IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount = ByteCount;
|
|
|
|
SetFlag( IrpContext->Union.NtfsIoContext->Flags, NTFS_IO_CONTEXT_ASYNC );
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
NtfsTeardownStructures (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVOID FcbOrScb,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN BOOLEAN CheckForAttributeTable,
|
|
IN ULONG AcquireFlags,
|
|
OUT PBOOLEAN RemovedFcb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to start the teardown process on a node in
|
|
the Fcb/Scb tree. We will attempt to remove this node and then
|
|
move up the tree removing any nodes held by this node.
|
|
|
|
This routine deals with the case where a single node may be holding
|
|
multiple parents in memory. If we are passed an input Lcb we will
|
|
use that to walk up the tree. If the Vcb is held exclusively we
|
|
will try to trim any nodes that have no open files on them.
|
|
|
|
This routine takes the following steps:
|
|
|
|
Remove as many Scb's and file objects from the starting
|
|
Fcb.
|
|
|
|
If the Fcb can't go away but has multiple links then remove
|
|
whatever links possible. If we have the Vcb we can
|
|
do all of them but we will leave a single link behind
|
|
to optimize prefix lookups. Otherwise we will traverse the
|
|
single link we were given.
|
|
|
|
If the Fcb can go away then we should have the Vcb if there are
|
|
multiple links to remove. Otherwise we only remove the link
|
|
we were given if there are multiple links. In the single link
|
|
case just remove that link.
|
|
|
|
Arguments:
|
|
|
|
FcbOrScb - Supplies either an Fcb or an Scb as the start of the
|
|
teardown point. The Fcb for this element must be held exclusively.
|
|
|
|
Lcb - If specified, this is the path up the tree to perform the
|
|
teardown.
|
|
|
|
CheckForAttributeTable - Indicates that we should not teardown an
|
|
Scb which is in the attribute table. Instead we will attempt
|
|
to put an entry on the async close queue. This will be TRUE
|
|
if we may need the Scb to abort the current transaction.
|
|
|
|
AcquireFlags - Indicates whether we should abort the teardown when
|
|
we can't acquire a parent. When called from some path where we may
|
|
hold the MftScb or another resource in another path up the tree.
|
|
|
|
ACQUIRE_NO_DELETE_CHECK
|
|
ACQUIRE_DONT_WAIT
|
|
ACQUIRE_HOLD_BITMAP
|
|
|
|
RemovedFcb - Address to store TRUE if we delete the starting Fcb.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB StartingScb = NULL;
|
|
PFCB Fcb;
|
|
BOOLEAN FcbCanBeRemoved;
|
|
BOOLEAN RemovedLcb;
|
|
BOOLEAN LocalRemovedFcb = FALSE;
|
|
PLIST_ENTRY Links;
|
|
PLIST_ENTRY NextLink;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If this is a recursive call to TearDownStructures we return immediately
|
|
// doing no operation.
|
|
//
|
|
|
|
if (FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_IN_TEARDOWN )) {
|
|
|
|
DebugTrace( 0, Dbg, ("Recursive teardown call\n") );
|
|
DebugTrace( -1, Dbg, ("NtfsTeardownStructures -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
if (SafeNodeType(FcbOrScb) == NTFS_NTC_FCB) {
|
|
|
|
Fcb = FcbOrScb;
|
|
|
|
} else {
|
|
|
|
StartingScb = FcbOrScb;
|
|
FcbOrScb = Fcb = StartingScb->Fcb;
|
|
}
|
|
|
|
SetFlag( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_IN_TEARDOWN );
|
|
|
|
//
|
|
// Use a try-finally to clear the top level irp field.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Use our local boolean if the caller didn't supply one.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( RemovedFcb )) {
|
|
|
|
RemovedFcb = &LocalRemovedFcb;
|
|
}
|
|
|
|
//
|
|
// Check this Fcb for removal. Remember if all of the Scb's
|
|
// and file objects are gone. We will try to remove the Fcb
|
|
// if the cleanup count is zero or if we are walking up
|
|
// one directory path of a mult-link file. If the Fcb has
|
|
// a non-zero cleanup count but the current Scb has a zero
|
|
// cleanup count then try to delete the Scb at the very least.
|
|
//
|
|
|
|
FcbCanBeRemoved = FALSE;
|
|
|
|
if (Fcb->CleanupCount == 0) {
|
|
|
|
FcbCanBeRemoved = NtfsPrepareFcbForRemoval( IrpContext,
|
|
Fcb,
|
|
StartingScb,
|
|
CheckForAttributeTable );
|
|
|
|
} else if (ARGUMENT_PRESENT( StartingScb ) &&
|
|
(StartingScb->CleanupCount == 0) &&
|
|
(StartingScb->AttributeTypeCode != $ATTRIBUTE_LIST)) {
|
|
|
|
NtfsRemoveScb( IrpContext, StartingScb, CheckForAttributeTable );
|
|
}
|
|
|
|
//
|
|
// There is a single link (typical case) we either try to
|
|
// remove that link or we simply return.
|
|
//
|
|
|
|
if (Fcb->LcbQueue.Flink == Fcb->LcbQueue.Blink) {
|
|
|
|
if (FcbCanBeRemoved) {
|
|
|
|
NtfsTeardownFromLcb( IrpContext,
|
|
Fcb->Vcb,
|
|
Fcb,
|
|
CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks ),
|
|
CheckForAttributeTable,
|
|
AcquireFlags,
|
|
&RemovedLcb,
|
|
RemovedFcb );
|
|
}
|
|
|
|
leave;
|
|
|
|
//
|
|
// If there are multiple links we will try to either remove
|
|
// them all or all but one (if the Fcb is not going away) if
|
|
// we own the Vcb. We will try to delete the one we were
|
|
// given otherwise.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we have the Vcb we will remove all if the Fcb can
|
|
// go away. Otherwise we will leave one.
|
|
//
|
|
|
|
if (NtfsIsExclusiveVcb( Fcb->Vcb )) {
|
|
|
|
Links = Fcb->LcbQueue.Flink;
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Remember the next entry in case the current link
|
|
// goes away.
|
|
//
|
|
|
|
NextLink = Links->Flink;
|
|
|
|
RemovedLcb = FALSE;
|
|
|
|
NtfsTeardownFromLcb( IrpContext,
|
|
Fcb->Vcb,
|
|
Fcb,
|
|
CONTAINING_RECORD( Links, LCB, FcbLinks ),
|
|
CheckForAttributeTable,
|
|
0,
|
|
&RemovedLcb,
|
|
RemovedFcb );
|
|
|
|
//
|
|
// If couldn't remove this link then munge the
|
|
// boolean indicating if the Fcb can be removed
|
|
// to make it appear we need to remove all of
|
|
// the Lcb's.
|
|
//
|
|
|
|
if (!RemovedLcb) {
|
|
|
|
FcbCanBeRemoved = TRUE;
|
|
}
|
|
|
|
//
|
|
// If the Fcb has been removed then we exit.
|
|
// If the next link is the beginning of the
|
|
// Lcb queue then we also exit.
|
|
// If the next link is the last entry and
|
|
// we want to leave a single entry then we
|
|
// exit.
|
|
//
|
|
|
|
if (*RemovedFcb ||
|
|
(NextLink == &Fcb->LcbQueue) ||
|
|
(!FcbCanBeRemoved &&
|
|
(NextLink->Flink == &Fcb->LcbQueue))) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Move to the next link.
|
|
//
|
|
|
|
Links = NextLink;
|
|
}
|
|
|
|
//
|
|
// If we have an Lcb just move up that path.
|
|
//
|
|
|
|
} else if (ARGUMENT_PRESENT( Lcb )) {
|
|
|
|
NtfsTeardownFromLcb( IrpContext,
|
|
Fcb->Vcb,
|
|
Fcb,
|
|
Lcb,
|
|
CheckForAttributeTable,
|
|
AcquireFlags,
|
|
&RemovedLcb,
|
|
RemovedFcb );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsTeardownStructures );
|
|
|
|
ClearFlag( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_IN_TEARDOWN );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
PVOID
|
|
NtfsAllocateCompressionSync (
|
|
IN POOL_TYPE PoolType,
|
|
IN SIZE_T NumberOfBytes,
|
|
IN ULONG Tag
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the lookaside package to allocation a new compression
|
|
sync structure. We have a dedicated routine in order to perform the resource
|
|
initialization if necessary. Otherwise the caller will need to defensively
|
|
test and initialize the resource.
|
|
|
|
Arguments:
|
|
|
|
PoolType - Type of pool associated with the lookaside list.
|
|
|
|
NumberOfBytes - Size of pool block to allocate.
|
|
|
|
Tag - Tag to associate with the block.
|
|
|
|
Return Value:
|
|
|
|
NULL if we are unable to allocate the pool. Otherwise a pointer to the block of
|
|
of pool is returned.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMPRESSION_SYNC CompressionSync;
|
|
|
|
PAGED_CODE();
|
|
|
|
CompressionSync = NtfsAllocatePoolWithTagNoRaise( PoolType,
|
|
NumberOfBytes,
|
|
Tag );
|
|
|
|
if (CompressionSync != NULL) {
|
|
|
|
ExInitializeResourceLite( &CompressionSync->Resource );
|
|
CompressionSync->ReferenceCount = 0;
|
|
}
|
|
|
|
return CompressionSync;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeallocateCompressionSync (
|
|
IN PVOID CompressionSync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to deallocate the pool for a single CompressionSync structure.
|
|
We have our own routine in order to unitialize the embedded resource.
|
|
|
|
Arguments:
|
|
|
|
CompressionSync - Structure to deallocate.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ExDeleteResourceLite( &((PCOMPRESSION_SYNC) CompressionSync)->Resource );
|
|
NtfsFreePool( CompressionSync );
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsIncrementCleanupCounts (
|
|
IN PSCB Scb,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN BOOLEAN NonCachedHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine increments the cleanup counts for the associated data structures
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb used in this operation
|
|
|
|
Lcb - Optionally supplies the Lcb used in this operation
|
|
|
|
NonCachedHandle - Indicates this handle is for a user non-cached handle.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = Scb->Vcb;
|
|
|
|
//
|
|
// This is really a pretty light weight procedure but having it be a procedure
|
|
// really helps in debugging the system and keeping track of who increments
|
|
// and decrements cleanup counts
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(Lcb)) { Lcb->CleanupCount += 1; }
|
|
|
|
InterlockedIncrement( &Scb->CleanupCount );
|
|
Scb->Fcb->CleanupCount += 1;
|
|
|
|
if (NonCachedHandle) {
|
|
|
|
Scb->NonCachedCleanupCount += 1;
|
|
}
|
|
|
|
InterlockedIncrement( &Vcb->CleanupCount );
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsIncrementCloseCounts (
|
|
IN PSCB Scb,
|
|
IN BOOLEAN SystemFile,
|
|
IN BOOLEAN ReadOnly
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine increments the close counts for the associated data structures
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb used in this operation
|
|
|
|
SystemFile - Indicates if the Scb is for a system file (if so then
|
|
the Vcb system file close count in also incremented)
|
|
|
|
ReadOnly - Indicates if the Scb is opened readonly. (if so then the
|
|
Vcb Read Only close count is also incremented)
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = Scb->Vcb;
|
|
|
|
//
|
|
// This is really a pretty light weight procedure but having it be a procedure
|
|
// really helps in debugging the system and keeping track of who increments
|
|
// and decrements close counts
|
|
//
|
|
|
|
//
|
|
// If this is someone other than the first open, remember that.
|
|
//
|
|
|
|
if (InterlockedIncrement( &Scb->CloseCount ) >= 2) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_MULTIPLE_OPENS );
|
|
}
|
|
|
|
InterlockedIncrement( &Scb->Fcb->CloseCount );
|
|
|
|
InterlockedIncrement( &Vcb->CloseCount );
|
|
|
|
if (SystemFile) {
|
|
|
|
InterlockedIncrement( &Vcb->SystemFileCloseCount );
|
|
}
|
|
|
|
if (ReadOnly) {
|
|
|
|
InterlockedIncrement( &Vcb->ReadOnlyCloseCount );
|
|
}
|
|
|
|
//
|
|
// We will always clear the delay close flag in this routine.
|
|
//
|
|
|
|
ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDecrementCleanupCounts (
|
|
IN PSCB Scb,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN BOOLEAN NonCachedHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This procedure decrements the cleanup counts for the associated data structures
|
|
and if necessary it also start to cleanup associated internal attribute streams
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb used in this operation
|
|
|
|
Lcb - Optionally supplies the Lcb used in this operation
|
|
|
|
NonCachedHandle - Indicates this handle is for a user non-cached handle.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = Scb->Vcb;
|
|
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_FCB( Scb->Fcb );
|
|
ASSERT_VCB( Scb->Fcb->Vcb );
|
|
ASSERT_OPTIONAL_LCB( Lcb );
|
|
|
|
//
|
|
// First we decrement the appropriate cleanup counts
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(Lcb)) { Lcb->CleanupCount -= 1; }
|
|
|
|
InterlockedDecrement( &Scb->CleanupCount );
|
|
Scb->Fcb->CleanupCount -= 1;
|
|
|
|
if (NonCachedHandle) {
|
|
|
|
Scb->NonCachedCleanupCount -= 1;
|
|
}
|
|
|
|
InterlockedDecrement( &Vcb->CleanupCount );
|
|
|
|
//
|
|
// Now if the Fcb's cleanup count is zero that indicates that we are
|
|
// done with this Fcb from a user handle standpoint and we should
|
|
// now scan through all of the Scb's that are opened under this
|
|
// Fcb and shutdown any internal attributes streams we have open.
|
|
// For example, EAs and ACLs. We only need to do one. The domino effect
|
|
// will take of the rest.
|
|
//
|
|
|
|
if (Scb->Fcb->CleanupCount == 0) {
|
|
|
|
PSCB NextScb;
|
|
|
|
//
|
|
// Remember if we are dealing with a system file and return immediately.
|
|
//
|
|
|
|
if (FlagOn(Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE) &&
|
|
NtfsSegmentNumber( &Scb->Fcb->FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
|
|
|
|
return;
|
|
}
|
|
|
|
for (NextScb = CONTAINING_RECORD(Scb->Fcb->ScbQueue.Flink, SCB, FcbLinks);
|
|
&NextScb->FcbLinks != &Scb->Fcb->ScbQueue;
|
|
NextScb = CONTAINING_RECORD( NextScb->FcbLinks.Flink, SCB, FcbLinks )) {
|
|
|
|
//
|
|
// Skip the root index on the volume. Also don't remove internal file objects
|
|
// for attribute lists. Defer that until close. Currently create continues to use
|
|
// attribute list mappings after dropping the resource.
|
|
//
|
|
|
|
if ((SafeNodeType( NextScb ) == NTFS_NTC_SCB_ROOT_INDEX) ||
|
|
(NextScb->AttributeTypeCode == $ATTRIBUTE_LIST)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// It is possible that someone has referenced this Scb to keep it from going away.
|
|
// We can treat this the same as if there was a cleanup count in the Fcb. Someone
|
|
// else is responsible for doing the cleanup.
|
|
//
|
|
// We can also break out if we have an index with children.
|
|
//
|
|
|
|
if ((NextScb->CleanupCount != 0) ||
|
|
((SafeNodeType( NextScb ) == NTFS_NTC_SCB_INDEX) &&
|
|
!IsListEmpty( &NextScb->ScbType.Index.LcbQueue ))) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If there is an internal stream then dereference it and get out.
|
|
//
|
|
|
|
if (NextScb->FileObject != NULL) {
|
|
|
|
NtfsDeleteInternalAttributeStream( NextScb,
|
|
(BOOLEAN) (Scb->Fcb->LinkCount == 0),
|
|
FALSE );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDecrementCloseCounts (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN PLCB Lcb OPTIONAL,
|
|
IN BOOLEAN SystemFile,
|
|
IN BOOLEAN ReadOnly,
|
|
IN BOOLEAN DecrementCountsOnly,
|
|
IN OUT PBOOLEAN RemovedFcb OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine decrements the close counts for the associated data structures
|
|
and if necessary it will teardown structures that are no longer in use
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb used in this operation
|
|
|
|
Lcb - Used if calling teardown to know which path to take.
|
|
|
|
SystemFile - Indicates if the Scb is for a system file
|
|
|
|
ReadOnly - Indicates if the Scb was opened readonly
|
|
|
|
DecrementCountsOnly - Indicates if this operation should only modify the
|
|
count fields.
|
|
|
|
RemovedFcb - Optionally indicates to the caller if the Fcb has been
|
|
deleted.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the fcb for the input scb was torndown
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB Fcb = Scb->Fcb;
|
|
PVCB Vcb = Scb->Vcb;
|
|
BOOLEAN Dummy;
|
|
|
|
ASSERT_SCB( Scb );
|
|
ASSERT_FCB( Fcb );
|
|
ASSERT_VCB( Fcb->Vcb );
|
|
|
|
if (RemovedFcb == NULL) {
|
|
|
|
RemovedFcb = &Dummy;
|
|
}
|
|
|
|
*RemovedFcb = FALSE;
|
|
|
|
//
|
|
// Decrement the close counts
|
|
//
|
|
|
|
InterlockedDecrement( &Scb->CloseCount );
|
|
InterlockedDecrement( &Fcb->CloseCount );
|
|
|
|
InterlockedDecrement( &Vcb->CloseCount );
|
|
|
|
if (SystemFile) {
|
|
|
|
InterlockedDecrement( &Vcb->SystemFileCloseCount );
|
|
}
|
|
|
|
if (ReadOnly) {
|
|
|
|
InterlockedDecrement( &Vcb->ReadOnlyCloseCount );
|
|
}
|
|
|
|
//
|
|
// Now if the scb's close count is zero then we are ready to tear
|
|
// it down
|
|
//
|
|
|
|
if (!DecrementCountsOnly) {
|
|
|
|
//
|
|
// We want to try to start a teardown from this Scb if
|
|
//
|
|
// - The close count is zero
|
|
//
|
|
// or the following are all true
|
|
//
|
|
// - The cleanup count is zero
|
|
// - There is a file object in the Scb
|
|
// - It is a data Scb or an empty index Scb
|
|
// - It is not an Ntfs system file
|
|
//
|
|
// The teardown will be noopted if this is a recursive call.
|
|
//
|
|
|
|
if (Scb->CloseCount == 0
|
|
|
|
||
|
|
|
|
(Scb->CleanupCount == 0
|
|
&& Scb->FileObject != NULL
|
|
&& !FlagOn(Fcb->FcbState, FCB_STATE_SYSTEM_FILE)
|
|
&& ((SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA)
|
|
|| (SafeNodeType( Scb ) == NTFS_NTC_SCB_MFT)
|
|
|| IsListEmpty( &Scb->ScbType.Index.LcbQueue )))) {
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
Scb,
|
|
Lcb,
|
|
FALSE,
|
|
0,
|
|
RemovedFcb );
|
|
}
|
|
}
|
|
}
|
|
|
|
PERESOURCE
|
|
NtfsAllocateEresource (
|
|
)
|
|
{
|
|
KIRQL _SavedIrql;
|
|
PERESOURCE Eresource;
|
|
|
|
_SavedIrql = KeAcquireQueuedSpinLock( LockQueueNtfsStructLock );
|
|
if (NtfsData.FreeEresourceSize > 0) {
|
|
Eresource = NtfsData.FreeEresourceArray[--NtfsData.FreeEresourceSize];
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
} else {
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
Eresource = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(ERESOURCE), 'rftN' );
|
|
ExInitializeResourceLite( Eresource );
|
|
NtfsData.FreeEresourceMiss += 1;
|
|
}
|
|
|
|
return Eresource;
|
|
}
|
|
|
|
VOID
|
|
NtfsFreeEresource (
|
|
IN PERESOURCE Eresource
|
|
)
|
|
{
|
|
KIRQL _SavedIrql;
|
|
|
|
//
|
|
// Do an unsafe test to see if we should put this on our list.
|
|
// We want to reinitialize this before adding to the list so
|
|
// we don't have a bunch of resources which appear to be held.
|
|
//
|
|
|
|
if (NtfsData.FreeEresourceSize < NtfsData.FreeEresourceTotal) {
|
|
|
|
ExReinitializeResourceLite( Eresource );
|
|
|
|
//
|
|
// Now acquire the spinlock and do a real test.
|
|
//
|
|
|
|
_SavedIrql = KeAcquireQueuedSpinLock( LockQueueNtfsStructLock );
|
|
if (NtfsData.FreeEresourceSize < NtfsData.FreeEresourceTotal) {
|
|
NtfsData.FreeEresourceArray[NtfsData.FreeEresourceSize++] = Eresource;
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
} else {
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
ExDeleteResourceLite( Eresource );
|
|
NtfsFreePool( Eresource );
|
|
}
|
|
|
|
} else {
|
|
|
|
ExDeleteResourceLite( Eresource );
|
|
NtfsFreePool( Eresource );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
PVOID
|
|
NtfsAllocateFcbTableEntry (
|
|
IN PRTL_GENERIC_TABLE FcbTable,
|
|
IN CLONG ByteSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine to allocate memory
|
|
|
|
Arguments:
|
|
|
|
FcbTable - Supplies the generic table being used
|
|
|
|
ByteSize - Supplies the number of bytes to allocate
|
|
|
|
Return Value:
|
|
|
|
PVOID - Returns a pointer to the allocated data
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL _SavedIrql;
|
|
PVOID FcbTableEntry;
|
|
|
|
UNREFERENCED_PARAMETER( FcbTable );
|
|
|
|
_SavedIrql = KeAcquireQueuedSpinLock( LockQueueNtfsStructLock );
|
|
if (NtfsData.FreeFcbTableSize > 0) {
|
|
FcbTableEntry = NtfsData.FreeFcbTableArray[--NtfsData.FreeFcbTableSize];
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
} else {
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
FcbTableEntry = NtfsAllocatePool( PagedPool, ByteSize );
|
|
}
|
|
|
|
return FcbTableEntry;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsFreeFcbTableEntry (
|
|
IN PRTL_GENERIC_TABLE FcbTable,
|
|
IN PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine that deallocates memory
|
|
|
|
Arguments:
|
|
|
|
FcbTable - Supplies the generic table being used
|
|
|
|
Buffer - Supplies the buffer being deallocated
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL _SavedIrql;
|
|
|
|
UNREFERENCED_PARAMETER( FcbTable );
|
|
|
|
_SavedIrql = KeAcquireQueuedSpinLock( LockQueueNtfsStructLock );
|
|
if (NtfsData.FreeFcbTableSize < FREE_FCB_TABLE_SIZE) {
|
|
NtfsData.FreeFcbTableArray[NtfsData.FreeFcbTableSize++] = Buffer;
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
} else {
|
|
KeReleaseQueuedSpinLock( LockQueueNtfsStructLock, _SavedIrql );
|
|
NtfsFreePool( Buffer );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsPostToNewLengthQueue (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to add an Scb to the queue of Scbs which have
|
|
waiters on extends. There is a single element embedded in the IrpContext.
|
|
Otherwise this field in the IrpContext will point to an array of elements.
|
|
|
|
Arguments:
|
|
|
|
Scb - This is the Scb to add to the queue.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Nothing to do if this Scb is in the IrpContext.
|
|
//
|
|
|
|
if (Scb != IrpContext->CheckNewLength) {
|
|
|
|
//
|
|
// If the IrpContext field is unused then stuff this into it.
|
|
//
|
|
|
|
if (IrpContext->CheckNewLength == NULL) {
|
|
|
|
IrpContext->CheckNewLength = Scb;
|
|
|
|
} else {
|
|
|
|
PULONG_PTR NewQueue;
|
|
|
|
//
|
|
// First case - there is an Scb in the IrpContext.
|
|
// Allocate a larger structure and put our element in it.
|
|
//
|
|
|
|
if (SafeNodeType( IrpContext->CheckNewLength ) == NTFS_NTC_SCB_DATA ) {
|
|
|
|
NewQueue = NtfsAllocatePool( PagedPool, sizeof( ULONG_PTR ) * 3 );
|
|
*NewQueue = (ULONG_PTR) IrpContext->CheckNewLength;
|
|
IrpContext->CheckNewLength = NewQueue;
|
|
*(NewQueue + 1) = (ULONG_PTR) Scb;
|
|
*(NewQueue + 2) = (ULONG_PTR) NULL;
|
|
|
|
//
|
|
// Second case - walk existing queue and look for an unused element or
|
|
// the current scb.
|
|
//
|
|
|
|
} else {
|
|
|
|
NewQueue = IrpContext->CheckNewLength;
|
|
|
|
do {
|
|
|
|
//
|
|
// Our scb is in the queue.
|
|
//
|
|
|
|
if (*NewQueue == (ULONG_PTR) Scb) { break; }
|
|
|
|
//
|
|
// The current position is unused.
|
|
//
|
|
|
|
if (*NewQueue == (ULONG_PTR) -1) {
|
|
|
|
*NewQueue = (ULONG_PTR) Scb;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We are at the end of the list.
|
|
//
|
|
|
|
if (*NewQueue == (ULONG_PTR) NULL) {
|
|
|
|
ULONG CurrentLength;
|
|
|
|
CurrentLength = PtrOffset( IrpContext->CheckNewLength, NewQueue );
|
|
|
|
NewQueue = NtfsAllocatePool( PagedPool,
|
|
CurrentLength + (4 * sizeof( ULONG_PTR )) );
|
|
|
|
RtlCopyMemory( NewQueue,
|
|
IrpContext->CheckNewLength,
|
|
CurrentLength );
|
|
|
|
NewQueue = Add2Ptr( NewQueue, CurrentLength );
|
|
*NewQueue = (ULONG_PTR) Scb;
|
|
*(NewQueue + 1) = -1;
|
|
*(NewQueue + 2) = -1;
|
|
*(NewQueue + 3) = (ULONG_PTR) NULL;
|
|
|
|
NtfsFreePool( IrpContext->CheckNewLength );
|
|
IrpContext->CheckNewLength = NewQueue;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Go to the next element.
|
|
//
|
|
|
|
NewQueue += 1;
|
|
|
|
} while (TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsProcessNewLengthQueue (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN BOOLEAN CleanupOnly
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when there is at least one Scb in the IrpContext
|
|
queue of streams which have waiters on the new length. We will call
|
|
NtOfsPostNewLength for each element unless we are cleaning up only.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Has a non-empty queue of Scbs for the current transaction.
|
|
|
|
CleanupOnly - Indicates if we only want to clean up the queue, not
|
|
alert any waiters (this is the error path).
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PULONG_PTR NextScb;
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Check if the only entry is resident in the IrpContext.
|
|
//
|
|
|
|
if (SafeNodeType( IrpContext->CheckNewLength ) == NTFS_NTC_SCB_DATA) {
|
|
|
|
if (!CleanupOnly) {
|
|
|
|
NtOfsPostNewLength( IrpContext, (PSCB) IrpContext->CheckNewLength, FALSE );
|
|
}
|
|
|
|
//
|
|
// Otherwise we want to walk through the external entries.
|
|
//
|
|
|
|
} else {
|
|
|
|
if (!CleanupOnly) {
|
|
|
|
NextScb = IrpContext->CheckNewLength;
|
|
|
|
//
|
|
// Continue until we run out of entries. The end of the block has a NULL, any unused entries
|
|
// will have a -1.
|
|
//
|
|
|
|
while ((*NextScb != (ULONG_PTR) -1) && (*NextScb != (ULONG_PTR) NULL)) {
|
|
|
|
ASSERT( SafeNodeType( *NextScb ) == NTFS_NTC_SCB_DATA );
|
|
NtOfsPostNewLength( IrpContext, (PSCB) *NextScb, FALSE );
|
|
|
|
NextScb += 1;
|
|
}
|
|
}
|
|
NtfsFreePool( IrpContext->CheckNewLength );
|
|
}
|
|
|
|
IrpContext->CheckNewLength = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsTestStatusProc (
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is to catch specific status codes in the running system. It
|
|
is called only when NtfsTestStatus is TRUE and the current request is completing
|
|
with NtfsTestStatusCode.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT( FALSE );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsCheckScbForCache (
|
|
IN OUT PSCB Scb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks if the Scb has blocks contining
|
|
Lsn's or Update sequence arrays and set the appropriate
|
|
bit in the Scb state word.
|
|
|
|
The Scb is Update sequence aware if it the Data Attribute for the
|
|
Mft or the Data Attribute for the log file or any index allocation
|
|
stream.
|
|
|
|
The Lsn aware Scb's are the ones above without the Log file.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the current Scb
|
|
|
|
Return Value:
|
|
|
|
The next Scb in the enumeration, or NULL if Scb was the final one.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Temporarily either sequence 0 or 1 is ok.
|
|
//
|
|
|
|
FILE_REFERENCE MftTemp = {0,0,1};
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Check for Update Sequence Array files first.
|
|
//
|
|
|
|
if ((Scb->AttributeTypeCode == $INDEX_ALLOCATION)
|
|
|
|
||
|
|
|
|
(Scb->AttributeTypeCode == $DATA
|
|
&& Scb->AttributeName.Length == 0
|
|
&& (NtfsEqualMftRef( &Scb->Fcb->FileReference, &MftFileReference )
|
|
|| NtfsEqualMftRef( &Scb->Fcb->FileReference, &MftTemp )
|
|
|| NtfsEqualMftRef( &Scb->Fcb->FileReference, &Mft2FileReference )
|
|
|| NtfsEqualMftRef( &Scb->Fcb->FileReference, &LogFileReference )))) {
|
|
|
|
SetFlag( Scb->ScbState, SCB_STATE_USA_PRESENT );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsRemoveScb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PSCB Scb,
|
|
IN BOOLEAN CheckForAttributeTable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will try to remove an Scb from the Fcb/Scb tree.
|
|
It deals with the case where we can make no attempt to remove
|
|
the Scb, the case where we start the process but can't complete
|
|
it, and finally the case where we remove the Scb entirely.
|
|
|
|
The following conditions prevent us from removing the Scb at all.
|
|
|
|
The open count is greater than 1.
|
|
It is the root directory.
|
|
It is an index Scb with no stream file and an outstanding close.
|
|
It is a data file with a non-zero close count.
|
|
|
|
We start the teardown under the following conditions.
|
|
|
|
It is an index with an open count of 1, and a stream file object.
|
|
|
|
We totally remove the Scb when the open count is zero.
|
|
|
|
Arguments:
|
|
|
|
Scb - Supplies the Scb to test
|
|
|
|
CheckForAttributeTable - Indicates that we don't want to remove this
|
|
Scb in this thread if it is in the open attribute table. We will
|
|
queue an async close in this case. This is to prevent us from
|
|
deleting an Scb which may be needed in the abort path.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the Scb was removed, FALSE otherwise. We return FALSE for
|
|
the case where we start the process but don't finish.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN ScbRemoved;
|
|
|
|
ASSERT_SCB( Scb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsRemoveScb: Entered\n") );
|
|
DebugTrace( 0, Dbg, ("Scb -> %08lx\n", Scb) );
|
|
|
|
ScbRemoved = FALSE;
|
|
|
|
//
|
|
// If the Scb is not the root Scb and the count is less than two,
|
|
// then this Scb is a candidate for removal.
|
|
//
|
|
|
|
if ((SafeNodeType( Scb ) != NTFS_NTC_SCB_ROOT_INDEX) && (Scb->CleanupCount == 0)) {
|
|
|
|
//
|
|
//
|
|
// If this is a data file or it is an index without children,
|
|
// we can get rid of the Scb if there are no children. If
|
|
// there is one open count and it is the file object, we
|
|
// can start the cleanup on the file object.
|
|
//
|
|
|
|
if ((SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA) ||
|
|
(SafeNodeType( Scb ) == NTFS_NTC_SCB_MFT) ||
|
|
IsListEmpty( &Scb->ScbType.Index.LcbQueue )) {
|
|
|
|
//
|
|
// Check if we need to post a request to the async queue.
|
|
//
|
|
|
|
if (CheckForAttributeTable &&
|
|
(Scb->NonpagedScb->OpenAttributeTableIndex != 0)) {
|
|
|
|
NtfsAddScbToFspClose( IrpContext, Scb, FALSE );
|
|
|
|
} else {
|
|
|
|
if (Scb->CloseCount == 0) {
|
|
|
|
NtfsDeleteScb( IrpContext, &Scb );
|
|
ScbRemoved = TRUE;
|
|
|
|
//
|
|
// Else we know the open count is 1 or 2. If there is a stream
|
|
// file, we discard it (but not for the special system
|
|
// files) that get removed on dismount
|
|
//
|
|
|
|
} else if (((Scb->FileObject != NULL) ||
|
|
#ifdef COMPRESS_ON_WIRE
|
|
(Scb->Header.FileObjectC != NULL)
|
|
#else
|
|
FALSE
|
|
#endif
|
|
|
|
) &&
|
|
|
|
!FlagOn(Scb->Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
|
|
|
|
NtfsDeleteInternalAttributeStream( Scb, (BOOLEAN) (Scb->Fcb->LinkCount == 0), FALSE );
|
|
|
|
//
|
|
// If the close count went to zero then remove the Scb.
|
|
//
|
|
|
|
if (Scb->CloseCount == 0) {
|
|
|
|
NtfsDeleteScb( IrpContext, &Scb );
|
|
ScbRemoved = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsRemoveScb: Exit -> %04x\n", ScbRemoved) );
|
|
|
|
return ScbRemoved;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsPrepareFcbForRemoval (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PSCB StartingScb OPTIONAL,
|
|
IN BOOLEAN CheckForAttributeTable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will attempt to prepare the Fcb for removal from the Fcb/Scb
|
|
tree. It will try to remove all of the Scb's and test finally if
|
|
all of the close count has gone to zero. NOTE the close count is incremented
|
|
by routines to reference this Fcb to keep it from being torn down. An empty
|
|
Scb list isn't enough to insure that the Fcb can be removed.
|
|
|
|
Arguments:
|
|
|
|
Fcb - This is the Fcb to remove.
|
|
|
|
StartingScb - This is the Scb to remove first.
|
|
|
|
CheckForAttributeTable - Indicates that we should not teardown an
|
|
Scb which is in the attribute table. Instead we will attempt
|
|
to put an entry on the async close queue. This will be TRUE
|
|
if we may need the Scb to abort the current transaction.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the Fcb can be removed, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB Scb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Try to remove each Scb in the Fcb queue.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
if (IsListEmpty( &Fcb->ScbQueue )) {
|
|
|
|
if (Fcb->CloseCount == 0) {
|
|
|
|
return TRUE;
|
|
|
|
} else {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT( StartingScb )) {
|
|
|
|
Scb = StartingScb;
|
|
StartingScb = NULL;
|
|
|
|
} else {
|
|
|
|
Scb = CONTAINING_RECORD( Fcb->ScbQueue.Flink,
|
|
SCB,
|
|
FcbLinks );
|
|
}
|
|
|
|
//
|
|
// Another thread along the create path could be active on
|
|
// one of these Scbs. If we try to remove the Attribute List Scb and
|
|
// somebody else has an index pinned, we'll wait on an VacbActiveCount
|
|
// forever. So, we want to skip the AttributeList Scb,
|
|
// unless it's the only Scb around. (This'll get cleaned up, eventually).
|
|
//
|
|
|
|
if ((Scb->AttributeTypeCode == $ATTRIBUTE_LIST) &&
|
|
(Fcb->ScbQueue.Flink != Fcb->ScbQueue.Blink)) {
|
|
|
|
RemoveEntryList( &Scb->FcbLinks );
|
|
InsertTailList( &Fcb->ScbQueue, &Scb->FcbLinks );
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Try to remove this Scb. If the call to remove didn't succeed
|
|
// but the close count has gone to zero, it means that a recursive
|
|
// close was generated which removed a stream file. In that
|
|
// case we can delete the Scb now.
|
|
//
|
|
|
|
if (!NtfsRemoveScb( IrpContext, Scb, CheckForAttributeTable )) {
|
|
|
|
//
|
|
// Return FALSE to indicate the Fcb can't go away.
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsTeardownFromLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFCB StartingFcb,
|
|
IN PLCB StartingLcb,
|
|
IN BOOLEAN CheckForAttributeTable,
|
|
IN ULONG AcquireFlags,
|
|
OUT PBOOLEAN RemovedStartingLcb,
|
|
OUT PBOOLEAN RemovedStartingFcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to remove a link and continue moving up the
|
|
tree looking for more elements to remove. We will check that the
|
|
link is unreferenced. NOTE this Lcb must point up to a directory
|
|
so that other than our starting Lcb no Lcb we encounter will
|
|
have multiple parents.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for this volume.
|
|
|
|
StartingFcb - This is the Fcb whose link we are trying to remove.
|
|
|
|
StartingLcb - This is the Lcb to walk up through. Note that
|
|
this may be a bogus pointer. It is only valid if there
|
|
is at least one Fcb in the queue.
|
|
|
|
CheckForAttributeTable - Indicates that we should not teardown an
|
|
Scb which is in the attribute table. Instead we will attempt
|
|
to put an entry on the async close queue. This will be TRUE
|
|
if we may need the Scb to abort the current transaction.
|
|
|
|
AcquireFlags - Indicates whether we should abort the teardown when
|
|
we can't acquire a parent. When called from some path where we may
|
|
hold the MftScb or another resource in another path up the tree.
|
|
|
|
RemovedStartingLcb - Address to store TRUE if we remove the
|
|
starting Lcb.
|
|
|
|
RemovedStartingFcb - Address to store TRUE if we remove the
|
|
starting Fcb.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB ParentScb;
|
|
BOOLEAN AcquiredParentScb = FALSE;
|
|
BOOLEAN AcquiredFcb = FALSE;
|
|
BOOLEAN UpdateStandardInfo;
|
|
BOOLEAN AcquiredFcbTable = FALSE;
|
|
BOOLEAN StandardInfoUpdateAllowed = FALSE;
|
|
BOOLEAN AcquiredParentExclusive;
|
|
BOOLEAN EmptyParentQueue;
|
|
|
|
PLCB Lcb;
|
|
PFCB Fcb = StartingFcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Use a try-finally to free any resources held.
|
|
//
|
|
|
|
try {
|
|
|
|
if (FlagOn( Fcb->Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) &&
|
|
(IrpContext->TopLevelIrpContext->ExceptionStatus == STATUS_SUCCESS)) {
|
|
|
|
StandardInfoUpdateAllowed = TRUE;
|
|
}
|
|
|
|
while (TRUE) {
|
|
|
|
ParentScb = NULL;
|
|
EmptyParentQueue = FALSE;
|
|
|
|
//
|
|
// Check if we need to update the standard information for this file.
|
|
//
|
|
|
|
if (StandardInfoUpdateAllowed &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED | FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
UpdateStandardInfo = TRUE;
|
|
|
|
} else {
|
|
|
|
UpdateStandardInfo = FALSE;
|
|
}
|
|
|
|
//
|
|
// Look through all of the Lcb's for this Fcb.
|
|
//
|
|
|
|
while (!IsListEmpty( &Fcb->LcbQueue )) {
|
|
|
|
if (Fcb == StartingFcb) {
|
|
|
|
Lcb = StartingLcb;
|
|
|
|
} else {
|
|
|
|
Lcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
|
|
LCB,
|
|
FcbLinks );
|
|
}
|
|
|
|
//
|
|
// Get out if not the last handle on this Lcb.
|
|
//
|
|
|
|
if (Lcb->CleanupCount != 0) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Acquire the parent if not already acquired.
|
|
//
|
|
|
|
if (ParentScb == NULL) {
|
|
|
|
ParentScb = Lcb->Scb;
|
|
|
|
//
|
|
// Do an unsafe test to see if we want the parent
|
|
// shared or exclusive. We want it exclusive
|
|
// if we will be walking up the tree because we are at the last Lcb.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.LcbQueue.Flink == ParentScb->ScbType.Index.LcbQueue.Blink) {
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
ACQUIRE_NO_DELETE_CHECK | AcquireFlags )) {
|
|
|
|
leave;
|
|
}
|
|
|
|
if (FlagOn( ParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, ParentScb );
|
|
}
|
|
|
|
AcquiredParentExclusive = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Try to acquire the parent but check whether we
|
|
// should wait.
|
|
//
|
|
|
|
if (!NtfsAcquireSharedFcbCheckWait( IrpContext,
|
|
ParentScb->Fcb,
|
|
AcquireFlags )) {
|
|
|
|
leave;
|
|
}
|
|
|
|
AcquiredParentExclusive = FALSE;
|
|
}
|
|
|
|
AcquiredParentScb = TRUE;
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
} else {
|
|
|
|
//
|
|
// We better be looking at another Lcb to the same parent.
|
|
//
|
|
|
|
ASSERT( ParentScb == Lcb->Scb );
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Check if we collide with a create moving down the tree.
|
|
//
|
|
|
|
if (Lcb->ReferenceCount != 0) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Now remove the Lcb. Remember if this is our original
|
|
// Lcb.
|
|
//
|
|
|
|
if (Lcb == StartingLcb) {
|
|
|
|
*RemovedStartingLcb = TRUE;
|
|
}
|
|
|
|
//
|
|
// We may only have the parent shared at this point. We need
|
|
// to serialize using the parent shared plus the fast
|
|
// mutex to remove the Lcb. We could test whether we need
|
|
// to do this but hopefully the typical case is that we
|
|
// have it shared and it won't be very expensive to acquire
|
|
// it exclusively at this point.
|
|
//
|
|
|
|
NtfsAcquireFsrtlHeader( ParentScb );
|
|
NtfsDeleteLcb( IrpContext, &Lcb );
|
|
|
|
//
|
|
// Remember if the parent Lcb queue is now empty.
|
|
//
|
|
|
|
if (IsListEmpty( &ParentScb->ScbType.Index.LcbQueue )) {
|
|
|
|
EmptyParentQueue = TRUE;
|
|
}
|
|
|
|
NtfsReleaseFsrtlHeader( ParentScb );
|
|
|
|
//
|
|
// If this is the first Fcb then exit the loop.
|
|
//
|
|
|
|
if (Fcb == StartingFcb) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we get here it means we removed all of the Lcb's we
|
|
// could for the current Fcb. If the list is empty we
|
|
// can remove the Fcb itself.
|
|
//
|
|
|
|
if (IsListEmpty( &Fcb->LcbQueue )) {
|
|
|
|
//
|
|
// If this is a directory that was opened by Id it is
|
|
// possible that we still have an update to perform
|
|
// for the duplicate information and possibly for
|
|
// standard information.
|
|
//
|
|
|
|
if (UpdateStandardInfo &&
|
|
(FlagOn( Fcb->InfoFlags, FCB_INFO_UPDATE_LAST_ACCESS ) ||
|
|
FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ))) {
|
|
|
|
//
|
|
// Use a try-except, we ignore errors here.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsUpdateStandardInformation( IrpContext, Fcb );
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Our worst nightmare has come true. We had to create an Scb
|
|
// and a stream in order to write out the duplicate information.
|
|
// This will happen if we have a non-resident attribute list.
|
|
//
|
|
|
|
if (!IsListEmpty( &Fcb->ScbQueue)) {
|
|
|
|
//
|
|
// Dereference any file object and delete the Scb if possible.
|
|
//
|
|
|
|
NtfsRemoveScb( IrpContext,
|
|
CONTAINING_RECORD( Fcb->ScbQueue.Flink,
|
|
SCB,
|
|
FcbLinks ),
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// If the list is now empty then check the reference count.
|
|
//
|
|
|
|
if (IsListEmpty( &Fcb->ScbQueue)) {
|
|
|
|
//
|
|
// Now we are ready to remove the current Fcb. We need to
|
|
// do a final check of the reference count to make sure
|
|
// it isn't being referenced in an open somewhere.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = TRUE;
|
|
|
|
if (Fcb->ReferenceCount == 0) {
|
|
|
|
if (Fcb == StartingFcb) {
|
|
|
|
*RemovedStartingFcb = TRUE;
|
|
}
|
|
|
|
NtfsDeleteFcb( IrpContext, &Fcb, &AcquiredFcbTable );
|
|
AcquiredFcb = FALSE;
|
|
|
|
} else {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
AcquiredFcbTable = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move to the Fcb for the ParentScb. Break out if no parent
|
|
// or there are no more entries on the parent.
|
|
//
|
|
|
|
if ((ParentScb == NULL) || !EmptyParentQueue) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If we have a parent Scb then we might have it
|
|
// either shared or exclusive. We can now do
|
|
// a thorough test to see if we need it exclusive.
|
|
//
|
|
|
|
if (!AcquiredParentExclusive) {
|
|
|
|
//
|
|
// We need to acquire the Fcb table, reference the
|
|
// parent, drop the parent and reacquire exclusively.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ParentScb->Fcb->ReferenceCount += 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsReleaseFcb( IrpContext, ParentScb->Fcb );
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext,
|
|
ParentScb->Fcb,
|
|
ParentScb,
|
|
ACQUIRE_NO_DELETE_CHECK | AcquireFlags )) {
|
|
|
|
//
|
|
// We couldn't get the parent. No problem, someone
|
|
// else will do any necessary teardown.
|
|
//
|
|
|
|
AcquiredParentScb = FALSE;
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ParentScb->Fcb->ReferenceCount -= 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
|
|
leave;
|
|
|
|
} else {
|
|
|
|
if (FlagOn( ParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
|
|
|
|
NtfsSnapshotScb( IrpContext, ParentScb );
|
|
}
|
|
|
|
AcquiredParentExclusive = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now decrement the parent reference.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ParentScb->Fcb->ReferenceCount -= 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
Fcb = ParentScb->Fcb;
|
|
AcquiredFcb = TRUE;
|
|
AcquiredParentScb = FALSE;
|
|
|
|
//
|
|
// Check if this Fcb can be removed.
|
|
//
|
|
|
|
if (!NtfsPrepareFcbForRemoval( IrpContext, Fcb, NULL, CheckForAttributeTable )) {
|
|
|
|
leave;
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsTeardownFromLcb );
|
|
|
|
if (AcquiredFcbTable) {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
if (AcquiredFcb) {
|
|
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
}
|
|
|
|
if (AcquiredParentScb) {
|
|
|
|
NtfsReleaseScb( IrpContext, ParentScb );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS
|
|
NtfsFcbTableCompare (
|
|
IN PRTL_GENERIC_TABLE FcbTable,
|
|
IN PVOID FirstStruct,
|
|
IN PVOID SecondStruct
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine to compare two fcb table elements
|
|
|
|
Arguments:
|
|
|
|
FcbTable - Supplies the generic table being queried
|
|
|
|
FirstStruct - Supplies the first fcb table element to compare
|
|
|
|
SecondStruct - Supplies the second fcb table element to compare
|
|
|
|
Return Value:
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS - The results of comparing the two
|
|
input structures
|
|
|
|
--*/
|
|
|
|
{
|
|
FILE_REFERENCE FirstRef = *((PFILE_REFERENCE) FirstStruct);
|
|
FILE_REFERENCE SecondRef = *((PFILE_REFERENCE) SecondStruct);
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Use also the sequence number for all compares so file references in the
|
|
// fcb table are unique over time and space. If we want to ignore sequence
|
|
// numbers we can zero out the sequence number field, but then we will also
|
|
// need to delete the Fcbs from the table during cleanup and not when the
|
|
// fcb really gets deleted. Otherwise we cannot reuse file records.
|
|
//
|
|
|
|
if (NtfsFullSegmentNumber( &FirstRef ) < NtfsFullSegmentNumber( &SecondRef )) {
|
|
|
|
return GenericLessThan;
|
|
|
|
} else if (NtfsFullSegmentNumber( &FirstRef ) > NtfsFullSegmentNumber( &SecondRef )) {
|
|
|
|
return GenericGreaterThan;
|
|
|
|
} else {
|
|
|
|
//
|
|
// SequenceNumber comparison now
|
|
//
|
|
|
|
if (FirstRef.SequenceNumber < SecondRef.SequenceNumber) {
|
|
return GenericLessThan;
|
|
} else if (FirstRef.SequenceNumber > SecondRef.SequenceNumber) {
|
|
return GenericGreaterThan;
|
|
} else {
|
|
return GenericEqual;
|
|
}
|
|
|
|
}
|
|
|
|
UNREFERENCED_PARAMETER( FcbTable );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsReserveCcbNamesInLcb (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PLCB Lcb,
|
|
IN PULONG ParentNameLength OPTIONAL,
|
|
IN ULONG LastComponentNameLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks through a list of Ccbs and grows the name buffer as
|
|
necessary.
|
|
|
|
Arguments:
|
|
|
|
Lcb - Lcb with links of Ccbs to check.
|
|
|
|
ParentNameLength - If specified then this is the full length of the new name
|
|
to the parent directory. Otherwise we use the existing parent name in
|
|
each Ccb. The separator is implied.
|
|
|
|
LastComponentNameLength - Number of bytes needed for the last component of the name.
|
|
|
|
Return Value:
|
|
|
|
None - This routine will raise on an allocation failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCCB Ccb;
|
|
PVOID NewAllocation;
|
|
ULONG BytesNeeded;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Now for every ccb attached to us we need to check if we need a new
|
|
// filename buffer. Protect the Ccb with the Fcb mutex to serialize access to
|
|
// the flags field with close.
|
|
//
|
|
|
|
Ccb = NULL;
|
|
while ((Ccb = NtfsGetNextCcb( Lcb, Ccb )) != NULL) {
|
|
|
|
//
|
|
// If the Ccb last component length is zero, this Ccb is for a
|
|
// file object that was opened by File Id. We won't to any
|
|
// work for the name in the fileobject for this. Otherwise we
|
|
// compute the length of the new name and see if we have enough space
|
|
// The CLOSE flag indicates whether this had gone through the close path or not.
|
|
// We use the LockFcb command above to serialize with the setting of the close
|
|
// flag.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
|
|
if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLOSE )) {
|
|
|
|
if (ARGUMENT_PRESENT( ParentNameLength )) {
|
|
|
|
BytesNeeded = *ParentNameLength + LastComponentNameLength;
|
|
|
|
} else {
|
|
|
|
BytesNeeded = Ccb->LastFileNameOffset + LastComponentNameLength;
|
|
}
|
|
|
|
if (Ccb->FullFileName.MaximumLength < BytesNeeded) {
|
|
|
|
//
|
|
// Allocate a new file name buffer and copy the existing data back into it.
|
|
//
|
|
|
|
NewAllocation = NtfsAllocatePoolNoRaise( PagedPool, BytesNeeded );
|
|
|
|
if (NewAllocation == NULL) {
|
|
|
|
NtfsUnlockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
|
|
}
|
|
|
|
RtlCopyMemory( NewAllocation,
|
|
Ccb->FullFileName.Buffer,
|
|
Ccb->FullFileName.Length );
|
|
|
|
if (FlagOn( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME )) {
|
|
|
|
NtfsFreePool( Ccb->FullFileName.Buffer );
|
|
}
|
|
|
|
Ccb->FullFileName.Buffer = NewAllocation;
|
|
Ccb->FullFileName.MaximumLength = (USHORT) BytesNeeded;
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME );
|
|
}
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, Ccb->Lcb->Fcb );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsClearRecursiveLcb (
|
|
IN PLCB Lcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to clear all of the normalized names, prefix entries and hash entries in
|
|
a subtree starting from a given Lcb. Typically this is used as part of a rename when a parent rename
|
|
affects the full name of all of the children.
|
|
|
|
Arguments:
|
|
|
|
Lcb - Lcb which is root of rename.
|
|
|
|
Return Value:
|
|
|
|
None - This routine will raise on an allocation failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSCB ChildScb;
|
|
PSCB NextScb;
|
|
PLCB NextLcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Clear the index offset pointer so we will look this up again.
|
|
//
|
|
|
|
Lcb->QuickIndex.BufferOffset = 0;
|
|
|
|
//
|
|
// Get rid of any prefixes that might still be attached to us
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveScb( Lcb->Scb ) );
|
|
NtfsRemovePrefix( Lcb );
|
|
|
|
//
|
|
// Remove any hash table entries for this Lcb.
|
|
//
|
|
|
|
NtfsRemoveHashEntriesForLcb( Lcb );
|
|
|
|
//
|
|
// And then traverse the graph underneath our fcb and remove all prefixes
|
|
// also used there. For each child scb under the fcb we will traverse all of
|
|
// its descendant Scb children and for each lcb we encounter we will remove its prefixes.
|
|
//
|
|
|
|
ChildScb = NULL;
|
|
while ((ChildScb = NtfsGetNextChildScb( Lcb->Fcb, ChildScb )) != NULL) {
|
|
|
|
//
|
|
// Now we have to descend into this Scb subtree, if it exists.
|
|
// Then remove the prefix entries on all of the links found.
|
|
// Do this as a do-while so we can use common code to handle the top-level
|
|
// Scb as well.
|
|
//
|
|
|
|
NextScb = ChildScb;
|
|
do {
|
|
|
|
//
|
|
// Walk through the Lcbs of any index Scb and remove the prefix and
|
|
// hash entries.
|
|
//
|
|
|
|
if (SafeNodeType( NextScb ) == NTFS_NTC_SCB_INDEX) {
|
|
|
|
//
|
|
// We better have the Vcb exclusive to descend down the tree.
|
|
//
|
|
|
|
ASSERT( NtfsIsExclusiveVcb( Lcb->Fcb->Vcb ));
|
|
|
|
NextLcb = NULL;
|
|
while ((NextLcb = NtfsGetNextChildLcb( NextScb, NextLcb )) != NULL) {
|
|
|
|
//
|
|
// Remove any hash table and prefix entries for this Lcb.
|
|
// We can be unsynchronized here because we own the Vcb
|
|
// exclusive and there are no open handles on either of these.
|
|
//
|
|
|
|
NtfsRemovePrefix( NextLcb );
|
|
NtfsRemoveHashEntriesForLcb( NextLcb );
|
|
}
|
|
|
|
//
|
|
// If this is an index Scb with a normalized name, then free
|
|
// the normalized name.
|
|
//
|
|
|
|
if ((NextScb != ChildScb) &&
|
|
(NextScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
|
|
|
|
NtfsDeleteNormalizedName( NextScb );
|
|
}
|
|
}
|
|
|
|
} while ((NextScb = NtfsGetNextScb( NextScb, ChildScb )) != NULL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
PDEALLOCATED_CLUSTERS
|
|
NtfsGetDeallocatedClusters (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add an entry if possible and neccessary to recently deallocated list and return the head of the list.
|
|
If there isn't enough memory this routine just returns the old head
|
|
We determine whether to add the entry based on the threshold for the mapping size
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb to add entry to
|
|
|
|
Return Value:
|
|
|
|
The new head of the list
|
|
|
|
--*/
|
|
|
|
{
|
|
PDEALLOCATED_CLUSTERS CurrentClusters;
|
|
PDEALLOCATED_CLUSTERS NewClusters;
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
PAGED_CODE();
|
|
|
|
CurrentClusters = (PDEALLOCATED_CLUSTERS) Vcb->DeallocatedClusterListHead.Flink;
|
|
|
|
if (FsRtlNumberOfRunsInLargeMcb( &CurrentClusters->Mcb ) > NTFS_DEALLOCATED_MCB_LIMIT) {
|
|
|
|
//
|
|
// Find a new deallocated cluster. Use the preallocated ones if they
|
|
// are not in use. If we fail to allocate memory continue to use the old one
|
|
//
|
|
|
|
if (Vcb->DeallocatedClusters1.Link.Flink == NULL) {
|
|
|
|
NewClusters = &Vcb->DeallocatedClusters1;
|
|
NewClusters->Lsn.QuadPart = 0;
|
|
|
|
} else if (Vcb->DeallocatedClusters2.Link.Flink == NULL) {
|
|
|
|
NewClusters = &Vcb->DeallocatedClusters2;
|
|
NewClusters->Lsn.QuadPart = 0;
|
|
|
|
} else {
|
|
|
|
NewClusters = NtfsAllocatePoolNoRaise( PagedPool, sizeof( DEALLOCATED_CLUSTERS ) );
|
|
if (NewClusters != NULL) {
|
|
RtlZeroMemory( NewClusters, sizeof( DEALLOCATED_CLUSTERS ) );
|
|
FsRtlInitializeLargeMcb( &NewClusters->Mcb, PagedPool );
|
|
}
|
|
}
|
|
|
|
if (NewClusters != NULL) {
|
|
ASSERT( NewClusters->ClusterCount == 0 );
|
|
|
|
CurrentClusters->Lsn = LfsQueryLastLsn( Vcb->LogHandle );
|
|
InsertHeadList( &Vcb->DeallocatedClusterListHead, &NewClusters->Link );
|
|
CurrentClusters = NewClusters;
|
|
}
|
|
}
|
|
|
|
return CurrentClusters;
|
|
}
|
|
|
|
|
|
#ifdef SYSCACHE_DEBUG
|
|
|
|
#define ENTRIES_PER_PAGE (PAGE_SIZE / sizeof( ON_DISK_SYSCACHE_LOG ))
|
|
|
|
ULONG
|
|
FsRtlLogSyscacheEvent (
|
|
IN PSCB Scb,
|
|
IN ULONG Event,
|
|
IN ULONG Flags,
|
|
IN LONGLONG Start,
|
|
IN LONGLONG Range,
|
|
IN LONGLONG Result
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Logging routine for syscache tracking
|
|
|
|
Arguments:
|
|
|
|
Scb - Scb being tracked
|
|
|
|
Event - SCE Event being record
|
|
|
|
Flags -Flag for the event
|
|
|
|
Start - starting offset
|
|
|
|
Range - range of the action
|
|
|
|
Result - result
|
|
|
|
Return Value:
|
|
|
|
Sequence number for this log entry
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG TempEntry;
|
|
#ifdef SYSCACHE_DEBUG_ON_DISK
|
|
LONG TempDiskEntry;
|
|
LONGLONG Offset;
|
|
PON_DISK_SYSCACHE_LOG Entry;
|
|
PBCB Bcb;
|
|
#endif
|
|
|
|
TempEntry = InterlockedIncrement( &(Scb->CurrentSyscacheLogEntry) );
|
|
TempEntry = TempEntry % Scb->SyscacheLogEntryCount;
|
|
Scb->SyscacheLog[TempEntry].Event = Event;
|
|
Scb->SyscacheLog[TempEntry].Flags = Flags;
|
|
Scb->SyscacheLog[TempEntry].Start = Start;
|
|
Scb->SyscacheLog[TempEntry].Range = Range;
|
|
Scb->SyscacheLog[TempEntry].Result = Result;
|
|
|
|
#ifdef SYSCACHE_DEBUG_ON_DISK
|
|
|
|
if ((Scb->Vcb->SyscacheScb != NULL) &&
|
|
(Scb->Vcb->SyscacheScb->Header.FileSize.QuadPart > 0 )) {
|
|
|
|
TempDiskEntry = InterlockedIncrement( &NtfsCurrentSyscacheOnDiskEntry );
|
|
Offset = (((TempDiskEntry / ENTRIES_PER_PAGE) * PAGE_SIZE) +
|
|
((TempDiskEntry % ENTRIES_PER_PAGE) * sizeof( ON_DISK_SYSCACHE_LOG )));
|
|
|
|
Offset = Offset % Scb->Vcb->SyscacheScb->Header.FileSize.QuadPart;
|
|
|
|
try {
|
|
|
|
CcPreparePinWrite( Scb->Vcb->SyscacheScb->FileObject,
|
|
(PLARGE_INTEGER)&Offset,
|
|
sizeof( ON_DISK_SYSCACHE_LOG ),
|
|
FALSE,
|
|
TRUE,
|
|
&Bcb,
|
|
&Entry );
|
|
|
|
Entry->SegmentNumberUnsafe = Scb->Fcb->FileReference.SegmentNumberLowPart;
|
|
Entry->Event = Event;
|
|
Entry->Flags = Flags;
|
|
Entry->Start = Start;
|
|
Entry->Range = Range;
|
|
Entry->Result = Result;
|
|
|
|
CcUnpinData( Bcb );
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
ASSERT( FALSE );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return TempEntry;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlUpdateSyscacheEvent (
|
|
IN PSCB Scb,
|
|
IN ULONG EntryNumber,
|
|
IN LONGLONG Result,
|
|
IN ULONG NewFlag
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Logging routine for syscache tracking - updates a prev. written record
|
|
|
|
Arguments:
|
|
|
|
Scb -
|
|
|
|
EntryNumber -
|
|
|
|
Result -
|
|
|
|
NewFlag -
|
|
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
Scb->SyscacheLog[EntryNumber].Flags |= NewFlag;
|
|
Scb->SyscacheLog[EntryNumber].Result = Result;
|
|
}
|
|
#endif
|
|
|