5554 lines
164 KiB
C
5554 lines
164 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1991 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
LogSup.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module implements the Ntfs interfaces to the Log File Service (LFS).
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Tom Miller [TomM] 24-Jul-1991
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "NtfsProc.h"
|
|||
|
|
|||
|
//
|
|||
|
// The local debug trace level
|
|||
|
//
|
|||
|
|
|||
|
#define Dbg (DEBUG_TRACE_LOGSUP)
|
|||
|
|
|||
|
//
|
|||
|
// Define a tag for general pool allocations from this module
|
|||
|
//
|
|||
|
|
|||
|
#undef MODULE_POOL_TAG
|
|||
|
#define MODULE_POOL_TAG ('LFtN')
|
|||
|
|
|||
|
#ifdef NTFSDBG
|
|||
|
|
|||
|
#define ASSERT_RESTART_TABLE(T) { \
|
|||
|
PULONG _p = (PULONG)(((PCHAR)(T)) + sizeof(RESTART_TABLE)); \
|
|||
|
ULONG _Count = ((T)->EntrySize/4) * (T)->NumberEntries; \
|
|||
|
ULONG _i; \
|
|||
|
for (_i = 0; _i < _Count; _i += 1) { \
|
|||
|
if (_p[_i] == 0xDAADF00D) { \
|
|||
|
DbgPrint("DaadFood for table %08lx, At %08lx\n", (T), &_p[_i]); \
|
|||
|
ASSERTMSG("ASSERT_RESTART_TABLE ", FALSE); \
|
|||
|
} \
|
|||
|
} \
|
|||
|
}
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
#define ASSERT_RESTART_TABLE(T) {NOTHING;}
|
|||
|
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Local structure for use in DirtyPageRoutine
|
|||
|
//
|
|||
|
|
|||
|
typedef struct {
|
|||
|
|
|||
|
PRESTART_POINTERS DirtyPageTable;
|
|||
|
ULONG DirtyPageIndex;
|
|||
|
PFILE_OBJECT OldestFileObject;
|
|||
|
LSN OldestLsn;
|
|||
|
BOOLEAN Overflow;
|
|||
|
|
|||
|
} DIRTY_PAGE_CONTEXT, *PDIRTY_PAGE_CONTEXT;
|
|||
|
|
|||
|
//
|
|||
|
// Local procedure prototypes
|
|||
|
//
|
|||
|
|
|||
|
typedef LCN UNALIGNED *PLCN_UNALIGNED;
|
|||
|
|
|||
|
VOID
|
|||
|
DirtyPageRoutine (
|
|||
|
IN PFILE_OBJECT FileObject,
|
|||
|
IN PLARGE_INTEGER FileOffset,
|
|||
|
IN ULONG Length,
|
|||
|
IN PLSN OldestLsn,
|
|||
|
IN PLSN NewestLsn,
|
|||
|
IN PVOID Context1,
|
|||
|
IN PVOID Context2
|
|||
|
);
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
LookupLcns (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PSCB Scb,
|
|||
|
IN VCN Vcn,
|
|||
|
IN ULONG ClusterCount,
|
|||
|
IN BOOLEAN MustBeAllocated,
|
|||
|
OUT PLCN_UNALIGNED FirstLcn
|
|||
|
);
|
|||
|
|
|||
|
ULONG
|
|||
|
NtfsCalculateNamedBytes (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PVCB Vcb
|
|||
|
);
|
|||
|
|
|||
|
LONG
|
|||
|
NtfsCatchOutOfMemoryExceptionFilter (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PEXCEPTION_POINTERS ExceptionPointer
|
|||
|
);
|
|||
|
|
|||
|
LONG
|
|||
|
NtfsCheckpointExceptionFilter (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PEXCEPTION_POINTERS ExceptionPointer,
|
|||
|
IN NTSTATUS ExceptionCode
|
|||
|
);
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text(PAGE, LookupLcns)
|
|||
|
#pragma alloc_text(PAGE, NtfsCheckpointCurrentTransaction)
|
|||
|
#pragma alloc_text(PAGE, NtfsCheckpointForLogFileFull)
|
|||
|
#pragma alloc_text(PAGE, NtfsCheckpointVolume)
|
|||
|
#pragma alloc_text(PAGE, NtfsCleanCheckpoint)
|
|||
|
#pragma alloc_text(PAGE, NtfsCleanupFailedTransaction)
|
|||
|
#pragma alloc_text(PAGE, NtfsCommitCurrentTransaction)
|
|||
|
#pragma alloc_text(PAGE, NtfsFreeRecentlyDeallocated)
|
|||
|
#pragma alloc_text(PAGE, NtfsFreeRestartTable)
|
|||
|
#pragma alloc_text(PAGE, NtfsGetFirstRestartTable)
|
|||
|
#pragma alloc_text(PAGE, NtfsGetNextRestartTable)
|
|||
|
#pragma alloc_text(PAGE, NtfsInitializeLogging)
|
|||
|
#pragma alloc_text(PAGE, NtfsInitializeRestartTable)
|
|||
|
#pragma alloc_text(PAGE, NtfsStartLogFile)
|
|||
|
#pragma alloc_text(PAGE, NtfsStopLogFile)
|
|||
|
#pragma alloc_text(PAGE, NtfsUpdateOatVersion)
|
|||
|
#pragma alloc_text(PAGE, NtfsWriteLog)
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
LSN
|
|||
|
NtfsWriteLog (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PSCB Scb,
|
|||
|
IN PBCB Bcb OPTIONAL,
|
|||
|
IN NTFS_LOG_OPERATION RedoOperation,
|
|||
|
IN PVOID RedoBuffer OPTIONAL,
|
|||
|
IN ULONG RedoLength,
|
|||
|
IN NTFS_LOG_OPERATION UndoOperation,
|
|||
|
IN PVOID UndoBuffer OPTIONAL,
|
|||
|
IN ULONG UndoLength,
|
|||
|
IN LONGLONG StreamOffset,
|
|||
|
IN ULONG RecordOffset,
|
|||
|
IN ULONG AttributeOffset,
|
|||
|
IN ULONG StructureSize
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine implements an Ntfs-specific interface to LFS for the
|
|||
|
purpose of logging updates to file record segments and resident
|
|||
|
attributes.
|
|||
|
|
|||
|
The caller creates one of the predefined log record formats as
|
|||
|
determined by the given LogOperation, and calls this routine with
|
|||
|
this log record and pointers to the respective file and attribute
|
|||
|
records. The list of log operations along with the respective structure
|
|||
|
expected for the Log Buffer is present in ntfslog.h.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Scb - Pointer to the Scb for the respective file or Mft. The caller must
|
|||
|
have at least shared access to this Scb.
|
|||
|
|
|||
|
Bcb - If specified, this Bcb will be set dirty specifying the Lsn of
|
|||
|
the log record written.
|
|||
|
|
|||
|
RedoOperation - One of the log operation codes defined in ntfslog.h.
|
|||
|
|
|||
|
RedoBuffer - A pointer to the structure expected for the given Redo operation,
|
|||
|
as summarized in ntfslog.h. This pointer should only be
|
|||
|
omitted if and only if the table in ntfslog.h does not show
|
|||
|
a log record for this log operation.
|
|||
|
|
|||
|
RedoLength - Length of the Redo buffer in bytes.
|
|||
|
|
|||
|
UndoOperation - One of the log operation codes defined in ntfslog.h.
|
|||
|
|
|||
|
Must be CompensationLogRecord if logging the Undo of
|
|||
|
a previous operation, such as during transaction abort.
|
|||
|
In this case, of course, the Redo information is from
|
|||
|
the Undo information of the record being undone. See
|
|||
|
next parameter.
|
|||
|
|
|||
|
UndoBuffer - A pointer to the structure expected for the given Undo operation,
|
|||
|
as summarized in ntfslog.h. This pointer should only be
|
|||
|
omitted if and only if the table in ntfslog.h does not show
|
|||
|
a log record for this log operation. If this pointer is
|
|||
|
identical to RedoBuffer, then UndoLength is ignored and
|
|||
|
only a single copy of the RedoBuffer is made, but described
|
|||
|
by both the Redo and Undo portions of the log record.
|
|||
|
|
|||
|
For a compensation log record (UndoOperation ==
|
|||
|
CompensationLogRecord), this argument must point to the
|
|||
|
UndoNextLsn of the log record being compensated.
|
|||
|
|
|||
|
UndoLength - Length of the Undo buffer in bytes. Ignored if RedoBuffer ==
|
|||
|
UndoBuffer.
|
|||
|
|
|||
|
For a compensation log record, this argument must be the length
|
|||
|
of the original redo record. (Used during restart).
|
|||
|
|
|||
|
StreamOffset - Offset within the stream for the start of the structure being
|
|||
|
modified (Mft or Index), or simply the stream offset for the start
|
|||
|
of the update.
|
|||
|
|
|||
|
RecordOffset - Byte offset from StreamOffset above to update reference
|
|||
|
|
|||
|
AttributeOffset - Offset within a value to which an update applies, if relevant.
|
|||
|
|
|||
|
StructureSize - Size of the entire structure being logged.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The Lsn of the log record written. For most callers, this status may be ignored,
|
|||
|
because the Lsn is also correctly recorded in the transaction context.
|
|||
|
|
|||
|
If an error occurs this procedure will raise.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LFS_WRITE_ENTRY WriteEntries[3];
|
|||
|
|
|||
|
struct {
|
|||
|
|
|||
|
NTFS_LOG_RECORD_HEADER LogRecordHeader;
|
|||
|
LCN Runs[PAGE_SIZE/512 - 1];
|
|||
|
|
|||
|
} LocalHeader;
|
|||
|
|
|||
|
PNTFS_LOG_RECORD_HEADER MyHeader;
|
|||
|
PVCB Vcb;
|
|||
|
|
|||
|
LSN UndoNextLsn;
|
|||
|
LSN ReturnLsn;
|
|||
|
PLSN DirtyLsn = NULL;
|
|||
|
|
|||
|
ULONG WriteIndex = 0;
|
|||
|
ULONG UndoIndex = 0;
|
|||
|
ULONG RedoIndex = 0;
|
|||
|
LONG UndoBytes = 0;
|
|||
|
LONG UndoAdjustmentForLfs = 0;
|
|||
|
LONG UndoRecords = 0;
|
|||
|
|
|||
|
PTRANSACTION_ENTRY TransactionEntry;
|
|||
|
ULONG OpenAttributeIndex = 0;
|
|||
|
ULONG OnDiskAttributeIndex = 0;
|
|||
|
POPEN_ATTRIBUTE_DATA AttributeData = NULL;
|
|||
|
BOOLEAN AttributeTableAcquired = FALSE;
|
|||
|
BOOLEAN TransactionTableAcquired = FALSE;
|
|||
|
|
|||
|
ULONG LogClusterCount = ClustersFromBytes( Scb->Vcb, StructureSize );
|
|||
|
VCN LogVcn = LlClustersFromBytesTruncate( Scb->Vcb, StreamOffset );
|
|||
|
|
|||
|
BOOLEAN DecrementLastTransactionLsnCount = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
Vcb = Scb->Vcb;
|
|||
|
|
|||
|
//
|
|||
|
// If the log handle is gone, then we noop this call.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE )) {
|
|||
|
|
|||
|
return Li0; //**** LfsZeroLsn;
|
|||
|
}
|
|||
|
|
|||
|
if (FlagOn( Vcb->VcbState, VCB_STATE_MOUNT_READ_ONLY )) {
|
|||
|
|
|||
|
//
|
|||
|
// We'd like to have a chat with whoever sent the log write.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(!FlagOn( Vcb->VcbState, VCB_STATE_MOUNT_READ_ONLY ));
|
|||
|
return Li0;
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsWriteLog:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
|
|||
|
DebugTrace( 0, Dbg, ("Bcb = %08lx\n", Bcb) );
|
|||
|
DebugTrace( 0, Dbg, ("RedoOperation = %08lx\n", RedoOperation) );
|
|||
|
DebugTrace( 0, Dbg, ("RedoBuffer = %08lx\n", RedoBuffer) );
|
|||
|
DebugTrace( 0, Dbg, ("RedoLength = %08lx\n", RedoLength) );
|
|||
|
DebugTrace( 0, Dbg, ("UndoOperation = %08lx\n", UndoOperation) );
|
|||
|
DebugTrace( 0, Dbg, ("UndoBuffer = %08lx\n", UndoBuffer) );
|
|||
|
DebugTrace( 0, Dbg, ("UndoLength = %08lx\n", UndoLength) );
|
|||
|
DebugTrace( 0, Dbg, ("StreamOffset = %016I64x\n", StreamOffset) );
|
|||
|
DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
|
|||
|
DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) );
|
|||
|
DebugTrace( 0, Dbg, ("StructureSize = %08lx\n", StructureSize) );
|
|||
|
|
|||
|
//
|
|||
|
// Check Redo and Undo lengths
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( ((RedoOperation == UpdateNonresidentValue) && (RedoLength <= PAGE_SIZE)) ||
|
|||
|
|
|||
|
!ARGUMENT_PRESENT( Scb ) ||
|
|||
|
|
|||
|
!ARGUMENT_PRESENT( Bcb ) ||
|
|||
|
|
|||
|
((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
|
|||
|
(RedoLength <= Scb->ScbType.Index.BytesPerIndexBuffer)) ||
|
|||
|
|
|||
|
(RedoLength <= Scb->Vcb->BytesPerFileRecordSegment) );
|
|||
|
|
|||
|
ASSERT( ((UndoOperation == UpdateNonresidentValue) && (UndoLength <= PAGE_SIZE)) ||
|
|||
|
|
|||
|
!ARGUMENT_PRESENT( Scb ) ||
|
|||
|
|
|||
|
!ARGUMENT_PRESENT( Bcb ) ||
|
|||
|
|
|||
|
((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
|
|||
|
(UndoLength <= Scb->ScbType.Index.BytesPerIndexBuffer)) ||
|
|||
|
|
|||
|
(UndoLength <= Scb->Vcb->BytesPerFileRecordSegment) ||
|
|||
|
|
|||
|
(UndoOperation == CompensationLogRecord) );
|
|||
|
|
|||
|
//
|
|||
|
// Initialize local pointers.
|
|||
|
//
|
|||
|
|
|||
|
MyHeader = (PNTFS_LOG_RECORD_HEADER)&LocalHeader;
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// If the structure size is nonzero, then create an open attribute table
|
|||
|
// entry.
|
|||
|
//
|
|||
|
|
|||
|
if (StructureSize != 0) {
|
|||
|
|
|||
|
//
|
|||
|
// Allocate an entry in the open attribute table and initialize it,
|
|||
|
// if it does not already exist. If we subsequently fail, we do
|
|||
|
// not have to clean this up. It will go away on the next checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
if (Scb->NonpagedScb->OpenAttributeTableIndex == 0) {
|
|||
|
|
|||
|
OPEN_ATTRIBUTE_ENTRY_V0 LocalOpenEntry;
|
|||
|
POPEN_ATTRIBUTE_ENTRY OpenAttributeEntry;
|
|||
|
POPEN_ATTRIBUTE_ENTRY_V0 OnDiskAttributeEntry;
|
|||
|
ULONG EntrySize;
|
|||
|
|
|||
|
ASSERT( sizeof( OPEN_ATTRIBUTE_ENTRY_V0 ) >= sizeof( OPEN_ATTRIBUTE_ENTRY ));
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
AttributeTableAcquired = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Check for a drain pending
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->OpenAttributeTable.DrainPending) {
|
|||
|
|
|||
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ONLY_SYNCH_CHECKPOINT );
|
|||
|
#ifdef PERF_STATS
|
|||
|
IrpContext->LogFullReason = LF_TRANSACTION_DRAIN;
|
|||
|
#endif
|
|||
|
|
|||
|
NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Only proceed if the OpenAttributeTableIndex is still 0.
|
|||
|
// We may reach this point for the MftScb. It may not be
|
|||
|
// acquired when logging changes to file records. We will
|
|||
|
// use the OpenAttributeTable for final synchronization
|
|||
|
// for the Mft open attribute table entry.
|
|||
|
//
|
|||
|
|
|||
|
if (Scb->NonpagedScb->OpenAttributeTableIndex == 0) {
|
|||
|
|
|||
|
//
|
|||
|
// Our structures require tables to stay within 64KB, since
|
|||
|
// we use USHORT offsets. Things are getting out of hand
|
|||
|
// at this point anyway. Raise log file full to reset the
|
|||
|
// table sizes if we get to this point.
|
|||
|
//
|
|||
|
|
|||
|
if (AllocatedSizeOfRestartTable( Vcb->OnDiskOat ) > MAX_RESTART_TABLE_SIZE) {
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
IrpContext->LogFullReason = LF_OPEN_ATTRIBUTES;
|
|||
|
#endif
|
|||
|
NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the indexes and then the Attribute data structure. The
|
|||
|
// try-finally will handle any failures.
|
|||
|
//
|
|||
|
|
|||
|
OpenAttributeIndex = NtfsAllocateRestartTableIndex( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
AttributeData = NtfsAllocatePool( PagedPool, sizeof( OPEN_ATTRIBUTE_DATA ) );
|
|||
|
OpenAttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
|
|||
|
OpenAttributeIndex );
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the entry and auxiliary data.
|
|||
|
//
|
|||
|
|
|||
|
if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
|
|||
|
|
|||
|
OpenAttributeEntry->BytesPerIndexBuffer = Scb->ScbType.Index.BytesPerIndexBuffer;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
OpenAttributeEntry->BytesPerIndexBuffer = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Its good enough to use the last lsn for the lsnofopenrecord
|
|||
|
// since we're serialized on create attributes within a file
|
|||
|
//
|
|||
|
|
|||
|
OpenAttributeEntry->AttributeTypeCode = Scb->AttributeTypeCode;
|
|||
|
OpenAttributeEntry->FileReference = Scb->Fcb->FileReference;
|
|||
|
OpenAttributeEntry->LsnOfOpenRecord = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
|
|||
|
AttributeData->Overlay.Scb = Scb;
|
|||
|
AttributeData->AttributeName = Scb->AttributeName;
|
|||
|
AttributeData->AttributeNamePresent = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Use the open attribute entry as the default table entry.
|
|||
|
//
|
|||
|
|
|||
|
Scb->NonpagedScb->OnDiskOatIndex = OpenAttributeIndex;
|
|||
|
|
|||
|
//
|
|||
|
// If the on-disk structure is needed then get it now.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( Vcb->OnDiskOat, TRUE );
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
OnDiskAttributeIndex = NtfsAllocateRestartTableIndex( Vcb->OnDiskOat, TRUE );
|
|||
|
OnDiskAttributeEntry = GetRestartEntryFromIndex( Vcb->OnDiskOat,
|
|||
|
OnDiskAttributeIndex );
|
|||
|
|
|||
|
OnDiskAttributeEntry->OatIndex = OpenAttributeIndex;
|
|||
|
OnDiskAttributeEntry->FileReference = Scb->Fcb->FileReference;
|
|||
|
OnDiskAttributeEntry->LsnOfOpenRecord.QuadPart = 0;
|
|||
|
OnDiskAttributeEntry->AttributeTypeCode = Scb->AttributeTypeCode;
|
|||
|
OnDiskAttributeEntry->BytesPerIndexBuffer = OpenAttributeEntry->BytesPerIndexBuffer;
|
|||
|
OnDiskAttributeEntry->LsnOfOpenRecord.QuadPart = OpenAttributeEntry->LsnOfOpenRecord.QuadPart;
|
|||
|
|
|||
|
//
|
|||
|
// Use this new index.
|
|||
|
//
|
|||
|
|
|||
|
Scb->NonpagedScb->OnDiskOatIndex = OnDiskAttributeIndex;
|
|||
|
|
|||
|
} finally {
|
|||
|
NtfsReleaseRestartTable( Vcb->OnDiskOat );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We need to log this so store a copy in our local.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
OnDiskAttributeIndex = OpenAttributeIndex;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now store the table indexes.
|
|||
|
//
|
|||
|
|
|||
|
AttributeData->OnDiskAttributeIndex = OnDiskAttributeIndex;
|
|||
|
Scb->NonpagedScb->OpenAttributeTableIndex = OpenAttributeIndex;
|
|||
|
|
|||
|
//
|
|||
|
// Now connect the attribute data to the table entry and the Vcb.
|
|||
|
//
|
|||
|
|
|||
|
OpenAttributeEntry->OatData = AttributeData;
|
|||
|
InsertTailList( &Vcb->OpenAttributeData, &AttributeData->Links );
|
|||
|
|
|||
|
RtlCopyMemory( &LocalOpenEntry,
|
|||
|
GetRestartEntryFromIndex( Vcb->OnDiskOat, OnDiskAttributeIndex ),
|
|||
|
EntrySize = Vcb->OnDiskOat->Table->EntrySize );
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
AttributeTableAcquired = FALSE;
|
|||
|
OpenAttributeIndex = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Now log the new open attribute table entry before going on,
|
|||
|
// to insure that the application of the caller's log record
|
|||
|
// will have the information he needs on the attribute. We will
|
|||
|
// use the Undo buffer to convey the attribute name. We will
|
|||
|
// not infinitely recurse, because now this Scb already has an
|
|||
|
// open attribute table index.
|
|||
|
//
|
|||
|
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Scb,
|
|||
|
NULL,
|
|||
|
OpenNonresidentAttribute,
|
|||
|
&LocalOpenEntry,
|
|||
|
EntrySize,
|
|||
|
Noop,
|
|||
|
Scb->AttributeName.Length != 0 ?
|
|||
|
Scb->AttributeName.Buffer : NULL,
|
|||
|
Scb->AttributeName.Length,
|
|||
|
(LONGLONG)0,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
AttributeTableAcquired = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a transaction ID and initialize it, if it does not already exist.
|
|||
|
// If we subsequently fail, we clean it up when the current request is
|
|||
|
// completed.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->TransactionId == 0) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
TransactionTableAcquired = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Our structures require tables to stay within 64KB, since
|
|||
|
// we use USHORT offsets. Things are getting out of hand
|
|||
|
// at this point anyway. Raise log file full to reset the
|
|||
|
// table sizes if we get to this point.
|
|||
|
//
|
|||
|
// Also raise if we're synchronizing to wait for all transactions to
|
|||
|
// finish
|
|||
|
//
|
|||
|
|
|||
|
if ((SizeOfRestartTable( &Vcb->TransactionTable ) > MAX_RESTART_TABLE_SIZE) ||
|
|||
|
Vcb->TransactionTable.DrainPending) {
|
|||
|
|
|||
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ONLY_SYNCH_CHECKPOINT );
|
|||
|
#ifdef PERF_STATS
|
|||
|
IrpContext->LogFullReason = LF_TRANSACTION_DRAIN;
|
|||
|
#endif
|
|||
|
|
|||
|
NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
|
|||
|
}
|
|||
|
|
|||
|
IrpContext->TransactionId =
|
|||
|
NtfsAllocateRestartTableIndex( &Vcb->TransactionTable, TRUE );
|
|||
|
|
|||
|
//
|
|||
|
// Obtain the lsn now so that checkpoint code can calculate the BaseLsn correctly
|
|||
|
// before we update the FirstLsn of this transaction after calling LfsWrite below.
|
|||
|
// This closes the window where we started the transaction with an invalid Lsn till
|
|||
|
// we actually write out the transaction and update the Lsn.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->LastTransactionLsnCount == 0) {
|
|||
|
|
|||
|
//
|
|||
|
// Since nobody should be updating LastTransactionLsn, just write out the last lsn
|
|||
|
//
|
|||
|
|
|||
|
Vcb->LastTransactionLsn = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Since LastTransactionLsnCount is non-zero, LastTransactionLsn should also be non-zero.
|
|||
|
// We should also be moving forward if someone is already ahead of us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( (Vcb->LastTransactionLsnCount != 0) &&
|
|||
|
(Vcb->LastTransactionLsn.QuadPart != 0) &&
|
|||
|
(Vcb->LastTransactionLsn.QuadPart <= LfsQueryLastLsn( Vcb->LogHandle ).QuadPart) );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Bump the reference count by one and decrement it after we update the FirstLsn below
|
|||
|
//
|
|||
|
|
|||
|
Vcb->LastTransactionLsnCount += 1;
|
|||
|
DecrementLastTransactionLsnCount = TRUE;
|
|||
|
|
|||
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG );
|
|||
|
|
|||
|
TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
|
|||
|
&Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
TransactionEntry->TransactionState = TransactionActive;
|
|||
|
TransactionEntry->FirstLsn =
|
|||
|
TransactionEntry->PreviousLsn =
|
|||
|
TransactionEntry->UndoNextLsn = Li0; //**** LfsZeroLsn;
|
|||
|
|
|||
|
//
|
|||
|
// Remember that we will need a commit record even if we abort
|
|||
|
// the transaction.
|
|||
|
//
|
|||
|
|
|||
|
TransactionEntry->UndoBytes = QuadAlign( sizeof( NTFS_LOG_RECORD_HEADER ));
|
|||
|
TransactionEntry->UndoRecords = 1;
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
TransactionTableAcquired = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Remember the space for the commit record in our Lfs adjustment.
|
|||
|
//
|
|||
|
|
|||
|
UndoAdjustmentForLfs += QuadAlign( sizeof( NTFS_LOG_RECORD_HEADER ));
|
|||
|
|
|||
|
//
|
|||
|
// If there is an undo operation for this log record, we reserve
|
|||
|
// the space for another Lfs log record.
|
|||
|
//
|
|||
|
|
|||
|
if (UndoOperation != Noop) {
|
|||
|
UndoAdjustmentForLfs += Vcb->LogHeaderReservation;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// At least for now, assume update is contained in one physical page.
|
|||
|
//
|
|||
|
|
|||
|
//ASSERT( (StructureSize == 0) || (StructureSize <= PAGE_SIZE) );
|
|||
|
|
|||
|
//
|
|||
|
// If there isn't enough room for this structure on the stack, we
|
|||
|
// need to allocate an auxilary buffer.
|
|||
|
//
|
|||
|
|
|||
|
if (LogClusterCount > (PAGE_SIZE / 512)) {
|
|||
|
|
|||
|
MyHeader = (PNTFS_LOG_RECORD_HEADER)
|
|||
|
NtfsAllocatePool(PagedPool, sizeof( NTFS_LOG_RECORD_HEADER )
|
|||
|
+ (LogClusterCount - 1) * sizeof( LCN ));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now fill in the WriteEntries array and the log record header.
|
|||
|
//
|
|||
|
|
|||
|
WriteEntries[0].Buffer = (PVOID)MyHeader;
|
|||
|
WriteEntries[0].ByteLength = sizeof(NTFS_LOG_RECORD_HEADER);
|
|||
|
WriteIndex += 1;
|
|||
|
|
|||
|
//
|
|||
|
// Lookup the Runs for this log record
|
|||
|
//
|
|||
|
|
|||
|
MyHeader->LcnsToFollow = (USHORT)LogClusterCount;
|
|||
|
|
|||
|
if (LogClusterCount != 0) {
|
|||
|
|
|||
|
if (!LookupLcns( IrpContext,
|
|||
|
Scb,
|
|||
|
LogVcn,
|
|||
|
LogClusterCount,
|
|||
|
TRUE,
|
|||
|
&MyHeader->LcnsForPage[0] )) {
|
|||
|
|
|||
|
//
|
|||
|
// It is possible that the allocation for this range is not allocated.
|
|||
|
// This may happen in cases where a stream which descibes itself is
|
|||
|
// being hotfixed (perhaps MoveFile in a later release). In the
|
|||
|
// hotfix case we will not write this log record. Hotfix will mark
|
|||
|
// the volume dirty so we know that the system will verify the volume
|
|||
|
// at some point.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( NtfsGetTopLevelHotFixScb() != NULL );
|
|||
|
|
|||
|
//
|
|||
|
// Cleanup the transaction entry if allocated here.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG ) &&
|
|||
|
(IrpContext->TransactionId != 0)) {
|
|||
|
|
|||
|
NtfsCleanupFailedTransaction( IrpContext );
|
|||
|
}
|
|||
|
|
|||
|
ReturnLsn = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
DirtyLsn = &ReturnLsn;
|
|||
|
leave;
|
|||
|
}
|
|||
|
|
|||
|
WriteEntries[0].ByteLength += (LogClusterCount - 1) * sizeof(LCN);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If there is a Redo buffer, fill in its write entry.
|
|||
|
//
|
|||
|
|
|||
|
if (RedoLength != 0) {
|
|||
|
|
|||
|
WriteEntries[1].Buffer = RedoBuffer;
|
|||
|
WriteEntries[1].ByteLength = RedoLength;
|
|||
|
UndoIndex = RedoIndex = WriteIndex;
|
|||
|
WriteIndex += 1;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If there is an undo buffer, and it is at a different address than
|
|||
|
// the redo buffer, then fill in its write entry.
|
|||
|
//
|
|||
|
|
|||
|
if ((RedoBuffer != UndoBuffer) && (UndoLength != 0) &&
|
|||
|
(UndoOperation != CompensationLogRecord)) {
|
|||
|
|
|||
|
WriteEntries[WriteIndex].Buffer = UndoBuffer;
|
|||
|
WriteEntries[WriteIndex].ByteLength = UndoLength;
|
|||
|
UndoIndex = WriteIndex;
|
|||
|
WriteIndex += 1;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now fill in the rest of the header. Assume Redo and Undo buffer is
|
|||
|
// the same, then fix them up if they are not.
|
|||
|
//
|
|||
|
|
|||
|
MyHeader->RedoOperation = (USHORT)RedoOperation;
|
|||
|
MyHeader->UndoOperation = (USHORT)UndoOperation;
|
|||
|
MyHeader->RedoOffset = (USHORT)WriteEntries[0].ByteLength;
|
|||
|
MyHeader->RedoLength = (USHORT)RedoLength;
|
|||
|
MyHeader->UndoOffset = MyHeader->RedoOffset;
|
|||
|
if (RedoBuffer != UndoBuffer) {
|
|||
|
MyHeader->UndoOffset += (USHORT)QuadAlign(MyHeader->RedoLength);
|
|||
|
}
|
|||
|
MyHeader->UndoLength = (USHORT)UndoLength;
|
|||
|
|
|||
|
MyHeader->TargetAttribute = (USHORT)Scb->NonpagedScb->OnDiskOatIndex;
|
|||
|
MyHeader->RecordOffset = (USHORT)RecordOffset;
|
|||
|
MyHeader->AttributeOffset = (USHORT)AttributeOffset;
|
|||
|
MyHeader->Reserved = 0;
|
|||
|
|
|||
|
MyHeader->TargetVcn = LogVcn;
|
|||
|
MyHeader->ClusterBlockOffset = (USHORT) LogBlocksFromBytesTruncate( ClusterOffset( Vcb, StreamOffset ));
|
|||
|
|
|||
|
//
|
|||
|
// Finally, get our current transaction entry and call Lfs. We acquire
|
|||
|
// the transaction table exclusive both to synchronize the Lsn updates
|
|||
|
// on return from Lfs, and also to mark the Bcb dirty before any more
|
|||
|
// log records are written.
|
|||
|
//
|
|||
|
// If we do not do serialize the LfsWrite and CcSetDirtyPinnedData, here is
|
|||
|
// what can happen:
|
|||
|
//
|
|||
|
// We log an update for a page and get an Lsn back
|
|||
|
//
|
|||
|
// Another thread writes a start of checkpoint record
|
|||
|
// This thread then collects all of the dirty pages at that time
|
|||
|
// Sometime it writes the dirty page table
|
|||
|
//
|
|||
|
// The former thread which had been preempted, now sets the Bcb dirty
|
|||
|
//
|
|||
|
// If we crash at this time, the page we updated is not in the dirty page
|
|||
|
// table of the checkpoint, and it its update record is also not seen since
|
|||
|
// it was written before the start of the checkpoint!
|
|||
|
//
|
|||
|
// Note however, since the page being updated is pinned and cannot be written,
|
|||
|
// updating the Lsn in the page may simply be considered part of the update.
|
|||
|
// Whoever is doing this update (to the Mft or an Index buffer), must have the
|
|||
|
// Mft or Index acquired exclusive anyway.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireSharedStarveExRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
TransactionTableAcquired = TRUE;
|
|||
|
|
|||
|
TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
|
|||
|
&Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
|
|||
|
//
|
|||
|
// Set up the UndoNextLsn. If this is a normal log record, then use
|
|||
|
// the UndoNextLsn stored in the transaction entry; otherwise, use
|
|||
|
// the one passed in as the Undo buffer.
|
|||
|
//
|
|||
|
|
|||
|
if (UndoOperation != CompensationLogRecord) {
|
|||
|
|
|||
|
UndoNextLsn = TransactionEntry->UndoNextLsn;
|
|||
|
|
|||
|
//
|
|||
|
// If there is undo information, calculate the number to pass to Lfs
|
|||
|
// for undo bytes to reserve.
|
|||
|
//
|
|||
|
|
|||
|
if (UndoOperation != Noop) {
|
|||
|
|
|||
|
UndoBytes += QuadAlign(WriteEntries[0].ByteLength);
|
|||
|
|
|||
|
if (UndoIndex != 0) {
|
|||
|
|
|||
|
UndoBytes += QuadAlign(WriteEntries[UndoIndex].ByteLength);
|
|||
|
}
|
|||
|
|
|||
|
UndoRecords += 1;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
UndoNextLsn = *(PLSN)UndoBuffer;
|
|||
|
|
|||
|
//
|
|||
|
// We can reduce our Undo requirements, by the Redo data being
|
|||
|
// logged. This is either an abort record for a previous action
|
|||
|
// or a commit record. If it is a commit record we accounted
|
|||
|
// for it above on the first NtfsWriteLog, and NtfsCommitTransaction
|
|||
|
// will adjust for the rest.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
|
|||
|
|
|||
|
UndoBytes -= QuadAlign(WriteEntries[0].ByteLength);
|
|||
|
|
|||
|
if (RedoIndex != 0) {
|
|||
|
|
|||
|
UndoBytes -= QuadAlign(WriteEntries[RedoIndex].ByteLength);
|
|||
|
}
|
|||
|
|
|||
|
UndoRecords -= 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#ifdef NTFS_LOG_FULL_TEST
|
|||
|
//
|
|||
|
// Perform log-file-full fail checking. We do not perform this check if
|
|||
|
// we are writing an undo record (since we are guaranteed space to undo
|
|||
|
// things).
|
|||
|
//
|
|||
|
|
|||
|
if (UndoOperation != CompensationLogRecord &&
|
|||
|
(IrpContext->MajorFunction != IRP_MJ_FILE_SYSTEM_CONTROL ||
|
|||
|
IrpContext->MinorFunction != IRP_MN_MOUNT_VOLUME)) {
|
|||
|
|
|||
|
LogFileFullFailCheck( IrpContext );
|
|||
|
|
|||
|
if (NtfsFailFrequency != 0 &&
|
|||
|
(++NtfsPeriodicFail % NtfsFailFrequency) == 0) {
|
|||
|
|
|||
|
ExRaiseStatus( STATUS_LOG_FILE_FULL );
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Call Lfs to write the record.
|
|||
|
//
|
|||
|
|
|||
|
LfsWrite( Vcb->LogHandle,
|
|||
|
WriteIndex,
|
|||
|
&WriteEntries[0],
|
|||
|
LfsClientRecord,
|
|||
|
&IrpContext->TransactionId,
|
|||
|
UndoNextLsn,
|
|||
|
TransactionEntry->PreviousLsn,
|
|||
|
UndoBytes + UndoAdjustmentForLfs,
|
|||
|
0,
|
|||
|
&ReturnLsn );
|
|||
|
|
|||
|
//
|
|||
|
// Now that we are successful, update the transaction entry appropriately.
|
|||
|
//
|
|||
|
|
|||
|
TransactionEntry->UndoBytes += UndoBytes;
|
|||
|
TransactionEntry->UndoRecords += UndoRecords;
|
|||
|
TransactionEntry->PreviousLsn = ReturnLsn;
|
|||
|
|
|||
|
//
|
|||
|
// The UndoNextLsn for the transaction depends on whether we are
|
|||
|
// doing a compensation log record or not.
|
|||
|
//
|
|||
|
|
|||
|
if (UndoOperation != CompensationLogRecord) {
|
|||
|
TransactionEntry->UndoNextLsn = ReturnLsn;
|
|||
|
} else {
|
|||
|
TransactionEntry->UndoNextLsn = UndoNextLsn;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If this is the first Lsn, then we have to update that as
|
|||
|
// well.
|
|||
|
//
|
|||
|
|
|||
|
if (TransactionEntry->FirstLsn.QuadPart == 0) {
|
|||
|
|
|||
|
TransactionEntry->FirstLsn = ReturnLsn;
|
|||
|
|
|||
|
//
|
|||
|
// Only decrement the LastTransactionLsnCount if we incremented it earlier as
|
|||
|
// it is possible for the FirstLsn to be zero during restart or some other code path.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( DecrementLastTransactionLsnCount ||
|
|||
|
FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) );
|
|||
|
|
|||
|
if (DecrementLastTransactionLsnCount) {
|
|||
|
|
|||
|
//
|
|||
|
// We cannot safely assert because we are acquiring the TransactionTable shared.
|
|||
|
// Another NtfsWriteLog could be changing these values.
|
|||
|
// That's why we have to use the InterlockedDecrement down below on LastTransactionLsnCount.
|
|||
|
//
|
|||
|
|
|||
|
// ASSERT( (Vcb->LastTransactionLsnCount != 0) &&
|
|||
|
// (Vcb->LastTransactionLsn.QuadPart != 0)
|
|||
|
// (Vcb->LastTransactionLsn.QuadPart <= ReturnLsn.QuadPart) );
|
|||
|
|
|||
|
InterlockedDecrement(&Vcb->LastTransactionLsnCount);
|
|||
|
DecrementLastTransactionLsnCount = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set to use this Lsn when marking dirty below
|
|||
|
//
|
|||
|
|
|||
|
DirtyLsn = &ReturnLsn;
|
|||
|
|
|||
|
//
|
|||
|
// Set the flag in the Irp Context which indicates we wrote
|
|||
|
// a log record to disk.
|
|||
|
//
|
|||
|
|
|||
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG );
|
|||
|
|
|||
|
//
|
|||
|
// Now set the Bcb dirty if specified. We want to set it no matter
|
|||
|
// what happens, because our caller has modified the buffer and is
|
|||
|
// counting on us to call the Cache Manager.
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT( Bcb )) {
|
|||
|
|
|||
|
TIMER_STATUS TimerStatus;
|
|||
|
|
|||
|
CcSetDirtyPinnedData( Bcb, DirtyLsn );
|
|||
|
|
|||
|
//
|
|||
|
// Synchronize with the checkpoint timer and other instances of this routine.
|
|||
|
//
|
|||
|
// Perform an interlocked exchange to indicate that a timer is being set.
|
|||
|
//
|
|||
|
// If the previous value indicates that no timer was set, then we
|
|||
|
// enable the volume checkpoint timer. This will guarantee that a checkpoint
|
|||
|
// will occur to flush out the dirty Bcb data.
|
|||
|
//
|
|||
|
// If the timer was set previously, then it is guaranteed that a checkpoint
|
|||
|
// will occur without this routine having to reenable the timer.
|
|||
|
//
|
|||
|
// If the timer and checkpoint occurred between the dirtying of the Bcb and
|
|||
|
// the setting of the timer status, then we will be queueing a single extra
|
|||
|
// checkpoint on a clean volume. This is not considered harmful.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Atomically set the timer status to indicate a timer is being set and
|
|||
|
// retrieve the previous value.
|
|||
|
//
|
|||
|
|
|||
|
TimerStatus = InterlockedExchange( (PLONG)&NtfsData.TimerStatus, TIMER_SET );
|
|||
|
|
|||
|
//
|
|||
|
// If the timer is not currently set then we must start the checkpoint timer
|
|||
|
// to make sure the above dirtying is flushed out.
|
|||
|
//
|
|||
|
|
|||
|
if (TimerStatus == TIMER_NOT_SET) {
|
|||
|
|
|||
|
LONGLONG FiveSecondsFromNow = -5*1000*1000*10;
|
|||
|
|
|||
|
KeSetTimer( &NtfsData.VolumeCheckpointTimer,
|
|||
|
*(PLARGE_INTEGER)&FiveSecondsFromNow,
|
|||
|
&NtfsData.VolumeCheckpointDpc );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
DebugUnwind( NtfsWriteLog );
|
|||
|
|
|||
|
if (DecrementLastTransactionLsnCount && !TransactionTableAcquired) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
|
|||
|
TRUE );
|
|||
|
TransactionTableAcquired = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
if (TransactionTableAcquired) {
|
|||
|
|
|||
|
if (DecrementLastTransactionLsnCount) {
|
|||
|
|
|||
|
//
|
|||
|
// The TransacationTable could be acquired shared/exclusive at this point.
|
|||
|
// That's why we need to use InterlockedDecrement.
|
|||
|
//
|
|||
|
|
|||
|
InterlockedDecrement(&Vcb->LastTransactionLsnCount);
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Lets cleanup any failed attempt to allocate an attribute entry.
|
|||
|
// We only need to check the OpenAttributeIndex if the operation
|
|||
|
// was successful.
|
|||
|
//
|
|||
|
|
|||
|
if (OpenAttributeIndex != 0) {
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable, OpenAttributeIndex );
|
|||
|
|
|||
|
if (AttributeData != NULL) {
|
|||
|
|
|||
|
NtfsFreePool( AttributeData );
|
|||
|
}
|
|||
|
|
|||
|
if (OnDiskAttributeIndex != 0) {
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( Vcb->OnDiskOat, OnDiskAttributeIndex );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (AttributeTableAcquired) {
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
}
|
|||
|
|
|||
|
if (MyHeader != (PNTFS_LOG_RECORD_HEADER)&LocalHeader) {
|
|||
|
|
|||
|
NtfsFreePool( MyHeader );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsWriteLog -> %016I64x\n", ReturnLsn ) );
|
|||
|
|
|||
|
return ReturnLsn;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCheckpointVolume (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PVCB Vcb,
|
|||
|
IN BOOLEAN OwnsCheckpoint,
|
|||
|
IN BOOLEAN CleanVolume,
|
|||
|
IN BOOLEAN FlushVolume,
|
|||
|
IN ULONG LfsFlags,
|
|||
|
IN LSN LastKnownLsn
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called periodically to perform a checkpoint on the volume
|
|||
|
with respect to the log file. The checkpoint dumps a bunch of log file
|
|||
|
state information to the log file, and finally writes a summary of the
|
|||
|
dumped information in its Restart Area.
|
|||
|
|
|||
|
This checkpoint dumps the following:
|
|||
|
|
|||
|
Open Attribute Table
|
|||
|
(all of the attribute names for the Attribute Table)
|
|||
|
Dirty Pages Table
|
|||
|
Transaction Table
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Vcb - Pointer to the Vcb on which the checkpoint is to occur.
|
|||
|
|
|||
|
OwnsCheckpoint - TRUE if the caller has already taken steps to insure
|
|||
|
that he may proceed with the checkpointing. In this case we
|
|||
|
don't do any checks for other checkpoints and don't clear the
|
|||
|
checkpoint flag or notify any waiting checkpoint threads.
|
|||
|
|
|||
|
CleanVolume - TRUE if the caller wishes to clean the volume before doing
|
|||
|
the checkpoint, or FALSE for a normal periodic checkpoint.
|
|||
|
|
|||
|
FlushVolume - Applies only if CleanVolume is TRUE. This indicates if we should
|
|||
|
should flush the volume or only Lsn streams. Only the shutdown thread
|
|||
|
can do a clean and flush checkpoint and avoid deadlocks between
|
|||
|
pagingio and main resources.
|
|||
|
|
|||
|
LfsFlags - flags to pass to lfs when writing the restart areas
|
|||
|
|
|||
|
LastKnownLsn - Applies only if CleanVolume is TRUE. Only perform the
|
|||
|
clean checkpoint if this value is the same as the last restart area
|
|||
|
in the Vcb. This will prevent us from doing unecesary clean
|
|||
|
checkpoints.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
RESTART_AREA RestartArea;
|
|||
|
RESTART_POINTERS DirtyPages;
|
|||
|
RESTART_POINTERS Pointers;
|
|||
|
PRESTART_POINTERS NewTable = NULL;
|
|||
|
LSN BaseLsn;
|
|||
|
PATTRIBUTE_NAME_ENTRY NamesBuffer = NULL;
|
|||
|
PTRANSACTION_ENTRY TransactionEntry;
|
|||
|
LSN OldestDirtyPageLsn = Li0;
|
|||
|
KPRIORITY PreviousPriority;
|
|||
|
PSCB UsnJournal = NULL;
|
|||
|
LOGICAL LfsCleanShutdown = 0;
|
|||
|
USN LowestOpenUsn;
|
|||
|
volatile LARGE_INTEGER StartTime;
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
BOOLEAN Tracking = CleanVolume;
|
|||
|
#endif
|
|||
|
|
|||
|
BOOLEAN DirtyPageTableInitialized = FALSE;
|
|||
|
BOOLEAN OpenAttributeTableAcquired = FALSE;
|
|||
|
BOOLEAN TransactionTableAcquired = FALSE;
|
|||
|
BOOLEAN AcquireFiles = FALSE;
|
|||
|
BOOLEAN PostDefrag = FALSE;
|
|||
|
BOOLEAN RestorePreviousPriority = FALSE;
|
|||
|
BOOLEAN AcquiredVcb = FALSE;
|
|||
|
LOGICAL CheckpointInProgress = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsCheckpointVolume:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
|
|||
|
|
|||
|
//
|
|||
|
// No checkpointing on readonly volumes.
|
|||
|
//
|
|||
|
|
|||
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!OwnsCheckpoint) {
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the checkpoint event.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// We will want to post a defrag if defragging is permitted and enabled
|
|||
|
// and we have begun the defrag operation or have excess mapping.
|
|||
|
// If the defrag hasn't been triggered then check the Mft free
|
|||
|
// space. We can skip defragging if a defrag operation is
|
|||
|
// currently active.
|
|||
|
//
|
|||
|
|
|||
|
if (!CleanVolume &&
|
|||
|
(FlagOn( Vcb->MftDefragState,
|
|||
|
VCB_MFT_DEFRAG_PERMITTED | VCB_MFT_DEFRAG_ENABLED | VCB_MFT_DEFRAG_ACTIVE ) ==
|
|||
|
(VCB_MFT_DEFRAG_PERMITTED | VCB_MFT_DEFRAG_ENABLED))) {
|
|||
|
|
|||
|
if (FlagOn( Vcb->MftDefragState,
|
|||
|
VCB_MFT_DEFRAG_TRIGGERED | VCB_MFT_DEFRAG_EXCESS_MAP )) {
|
|||
|
|
|||
|
PostDefrag = TRUE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtfsCheckForDefrag( Vcb );
|
|||
|
|
|||
|
if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
|
|||
|
|
|||
|
PostDefrag = TRUE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If a checkpoint is already active, we either have to get out,
|
|||
|
// or wait for it.
|
|||
|
//
|
|||
|
|
|||
|
while (FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_SYNC_FLAGS )) {
|
|||
|
|
|||
|
CheckpointInProgress = FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
|
|||
|
|
|||
|
//
|
|||
|
// Release the checkpoint event because we cannot checkpoint now.
|
|||
|
//
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
if (CleanVolume) {
|
|||
|
|
|||
|
NtfsWaitOnCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// If there prev. was a checkpoint in progress and the last one was
|
|||
|
// clean then we don't need to do this clean checkpoint
|
|||
|
//
|
|||
|
|
|||
|
if (CheckpointInProgress && FlagOn( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN )) {
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the log file is gone then simply exit.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE )) {
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We now have the checkpoint event. Check if there is still
|
|||
|
// a need to perform the checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
if (CleanVolume &&
|
|||
|
(LastKnownLsn.QuadPart != Vcb->LastRestartArea.QuadPart)) {
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_SYNC_FLAGS );
|
|||
|
NtfsResetCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// If this is a clean volume checkpoint then boost the priority of
|
|||
|
// this thread.
|
|||
|
//
|
|||
|
|
|||
|
if (CleanVolume) {
|
|||
|
|
|||
|
PreviousPriority = KeSetPriorityThread( (PKTHREAD)PsGetCurrentThread(),
|
|||
|
LOW_REALTIME_PRIORITY );
|
|||
|
|
|||
|
if (PreviousPriority != LOW_REALTIME_PRIORITY) {
|
|||
|
|
|||
|
RestorePreviousPriority = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
RtlZeroMemory( &RestartArea, sizeof( RESTART_AREA ) );
|
|||
|
RtlZeroMemory( &DirtyPages, sizeof( RESTART_POINTERS ) );
|
|||
|
|
|||
|
//
|
|||
|
// Remember if our caller wants to tell Lfs that this is a
|
|||
|
// clean shutdown. We will use the combination of the OwnsCheckpoint and
|
|||
|
// CleanCheckpoint flags. This will cover system shutdown and volume
|
|||
|
// snapshot cases. Both of these want the volume not to need any restart.
|
|||
|
//
|
|||
|
|
|||
|
if (OwnsCheckpoint && CleanVolume) {
|
|||
|
|
|||
|
LfsCleanShutdown = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Record the start time
|
|||
|
//
|
|||
|
|
|||
|
KeQueryTickCount( &StartTime );
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
if (Tracking) {
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].StartTime = StartTime.QuadPart;
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].RestartArea = LastKnownLsn;
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].Reason = IrpContext->LogFullReason;
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].LogFileFulls = Vcb->UnhandledLogFileFullCount;
|
|||
|
|
|||
|
// ASSERT( IrpContext->LogFullReason != 0 );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Insure cleanup on the way out
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
POPEN_ATTRIBUTE_ENTRY AttributeEntry;
|
|||
|
ULONG NameBytes = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Capture the lowest usn - we have checkpoint synchronization which keeps
|
|||
|
// the journal from going away. This value may change but it will only monotically
|
|||
|
// increase
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->UsnJournal != NULL) {
|
|||
|
|
|||
|
NtfsAcquireResourceShared( IrpContext, Vcb->UsnJournal, TRUE );
|
|||
|
NtfsLockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|||
|
|
|||
|
//
|
|||
|
// Now we will correctly synchronize, test the list again and capture
|
|||
|
// the LowestUsn.
|
|||
|
//
|
|||
|
|
|||
|
if (!IsListEmpty(&Vcb->ModifiedOpenFiles)) {
|
|||
|
LowestOpenUsn = ((PFCB_USN_RECORD)CONTAINING_RECORD( Vcb->ModifiedOpenFiles.Flink,
|
|||
|
FCB_USN_RECORD,
|
|||
|
ModifiedOpenFilesLinks ))->Fcb->Usn;
|
|||
|
|
|||
|
//
|
|||
|
// If the list is empty, then use FileSize
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
LowestOpenUsn = Vcb->UsnJournal->Header.FileSize.QuadPart;
|
|||
|
}
|
|||
|
|
|||
|
NtfsUnlockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|||
|
NtfsReleaseResource( IrpContext, Vcb->UsnJournal );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now remember the current "last Lsn" value as the start of
|
|||
|
// our checkpoint. We acquire the transaction table to capture
|
|||
|
// this value to synchronize with threads who are writing log
|
|||
|
// records and setting pages dirty as atomic actions.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( IrpContext->TransactionId == 0 );
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
//
|
|||
|
// If LfsFlags == LFS_WRITE_FLAG_WRITE_AT_FRONT then produce
|
|||
|
// the dummy log record that resets the log. This allows us to
|
|||
|
// keep the log in use only at the front so chkdsk can shrink it
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( LfsFlags, LFS_WRITE_FLAG_WRITE_AT_FRONT )) {
|
|||
|
LSN Lsn;
|
|||
|
LFS_WRITE_ENTRY WriteEntry;
|
|||
|
UCHAR Buffer[ sizeof( NTFS_LOG_RECORD_HEADER ) + 2 * sizeof( LSN )];
|
|||
|
TRANSACTION_ID TransactionId;
|
|||
|
|
|||
|
RtlZeroMemory( Buffer, sizeof( Buffer ));
|
|||
|
|
|||
|
WriteEntry.Buffer = Buffer;
|
|||
|
WriteEntry.ByteLength = sizeof( Buffer );
|
|||
|
|
|||
|
TransactionId = NtfsAllocateRestartTableIndex( &Vcb->TransactionTable, TRUE );
|
|||
|
|
|||
|
Lsn.QuadPart = 0;
|
|||
|
|
|||
|
LfsGetActiveLsnRange( Vcb->LogHandle,
|
|||
|
Add2Ptr( Buffer, sizeof( NTFS_LOG_RECORD_HEADER )),
|
|||
|
Add2Ptr( Buffer, sizeof( NTFS_LOG_RECORD_HEADER ) + sizeof( LSN )) );
|
|||
|
|
|||
|
LfsWrite( Vcb->LogHandle,
|
|||
|
1,
|
|||
|
&WriteEntry,
|
|||
|
LfsClientRecord,
|
|||
|
&TransactionId,
|
|||
|
Lsn,
|
|||
|
Lsn,
|
|||
|
0,
|
|||
|
LfsFlags,
|
|||
|
&Lsn );
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->TransactionTable, TransactionId );
|
|||
|
|
|||
|
//
|
|||
|
// Commit the transaction so that we can release resources
|
|||
|
//
|
|||
|
|
|||
|
NtfsCommitCurrentTransaction( IrpContext );
|
|||
|
}
|
|||
|
|
|||
|
BaseLsn =
|
|||
|
RestartArea.StartOfCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
//
|
|||
|
// Flush any dangling dirty pages from before the last restart.
|
|||
|
// Note that it is arbitrary what Lsn we flush to here, and, in fact,
|
|||
|
// it is not absolutely required that we flush anywhere at all - we
|
|||
|
// could actually rely on the Lazy Writer. All we are trying to do
|
|||
|
// is reduce the amount of work that we will have to do at Restart,
|
|||
|
// by not forcing ourselves to have to go too far back in the log.
|
|||
|
// Presumably this can only happen for some reason the system is
|
|||
|
// starting to produce dirty pages faster than the lazy writer is
|
|||
|
// writing them.
|
|||
|
//
|
|||
|
// (We may wish to play with taking this call out...)
|
|||
|
//
|
|||
|
// This may be an appropriate place to worry about this, but, then
|
|||
|
// again, the Lazy Writer is using (currently) five threads. It may
|
|||
|
// not be appropriate to hold up this one thread doing the checkpoint
|
|||
|
// if the Lazy Writer is getting behind. How many dirty pages we
|
|||
|
// can even have is limited by the size of memory, so if the log file
|
|||
|
// is large enough, this may not be an issue. It seems kind of nice
|
|||
|
// to just let the Lazy Writer keep writing dirty pages as he does
|
|||
|
// now.
|
|||
|
//
|
|||
|
// if (!FlagOn(Vcb->VcbState, VCB_STATE_LAST_CHECKPOINT_CLEAN)) {
|
|||
|
// CcFlushPagesToLsn( Vcb->LogHandle, &Vcb->LastRestartArea );
|
|||
|
// }
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Now we must clean the volume here if that is what the caller wants.
|
|||
|
//
|
|||
|
|
|||
|
if (CleanVolume) {
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
if (Tracking) {
|
|||
|
SetFlag( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_TRACK_IOS );
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
#ifdef BENL_DBG
|
|||
|
KdPrint(( "NTFS: clean checkpoint %x started %I64x\n", Vcb, StartTime ));
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Update stats
|
|||
|
//
|
|||
|
|
|||
|
NtfsCleanCheckpoints += 1;
|
|||
|
|
|||
|
//
|
|||
|
// We don't want to clear the pseudo clean bit because we don't want
|
|||
|
// another pseudo clean checkpoint after a clean checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_PSEUDO_CLEAN );
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Lock down the volume if this is a clean checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
if (FlushVolume) {
|
|||
|
|
|||
|
NtfsAcquireAllFiles( IrpContext, Vcb, FlushVolume, FALSE, FALSE );
|
|||
|
AcquireFiles = TRUE;
|
|||
|
|
|||
|
#ifdef NTFSDBG
|
|||
|
ASSERT( !FlagOn( IrpContext->State, IRP_CONTEXT_STATE_CHECKPOINT_ACTIVE ));
|
|||
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_CHECKPOINT_ACTIVE );
|
|||
|
#endif // NTFSDBG
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
BOOLEAN WaitOnTransactions = FALSE;
|
|||
|
|
|||
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|||
|
AcquiredVcb = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Set the flag indicating we're waiting for all transactions to finish and
|
|||
|
// then wait if necc.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
|
|||
|
Vcb->TransactionTable.DrainPending = TRUE;
|
|||
|
|
|||
|
ASSERT( IrpContext->TransactionId == 0 );
|
|||
|
|
|||
|
if (Vcb->TransactionTable.Table->NumberAllocated > 0) {
|
|||
|
KeClearEvent( &Vcb->TransactionsDoneEvent );
|
|||
|
WaitOnTransactions = TRUE;
|
|||
|
}
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
if (WaitOnTransactions) {
|
|||
|
KeWaitForSingleObject( &Vcb->TransactionsDoneEvent, Executive, KernelMode, FALSE, NULL );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set the flag to disallow new open attributes as well
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
Vcb->OpenAttributeTable.DrainPending = TRUE;
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
if (Tracking) {
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].NumAttributes =
|
|||
|
Vcb->OpenAttributeTable.Table->NumberAllocated;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// It isn't safe to checkpoint a dismounted volume, and
|
|||
|
// it doesn't make much sense, either.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|||
|
|
|||
|
leave;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now we will acquire the Open Attribute Table exclusive to delete
|
|||
|
// all of the entries, since we want to write a clean checkpoint.
|
|||
|
// This is OK, since we have the global resource and nothing else
|
|||
|
// can be going on. (Similarly we are writing an empty transaction
|
|||
|
// table, while in fact we will be the only transaction, but there
|
|||
|
// is no need to capture our guy, nor explicitly empty this table.)
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
OpenAttributeTableAcquired = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// First reclaim the page we have reserved in the undo total, to
|
|||
|
// guarantee that we can flush the log file.
|
|||
|
//
|
|||
|
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle, 1, -(LONG)(2 * PAGE_SIZE) );
|
|||
|
|
|||
|
if (FlushVolume) {
|
|||
|
|
|||
|
(VOID)NtfsFlushVolume( IrpContext, Vcb, TRUE, FALSE, FALSE, FALSE );
|
|||
|
|
|||
|
//
|
|||
|
// Loop through to deallocate all of the open attribute entries. Any
|
|||
|
// that point to an Scb need to get the index in the Scb zeroed. If
|
|||
|
// they do not point to an Scb, we have to see if there is a name to
|
|||
|
// free.
|
|||
|
//
|
|||
|
|
|||
|
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
while (AttributeEntry != NULL) {
|
|||
|
|
|||
|
NtfsFreeAttributeEntry( Vcb, AttributeEntry );
|
|||
|
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
|
|||
|
AttributeEntry );
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// If we're only flushing out the open attributes rather than the
|
|||
|
// whole volume we own the vcb shared at this point. We've set the
|
|||
|
// drain pending flag to prevent new transactions from being opened. Now
|
|||
|
// start a cycle of finding an entry in the table / flushing it and removing
|
|||
|
// it from the table - We must drop the table before acquiring any file since its
|
|||
|
// an end resource. This will also free the attribute entries
|
|||
|
//
|
|||
|
|
|||
|
NtfsFlushLsnStreams( IrpContext, Vcb, TRUE, FALSE );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
SetFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
|
|||
|
|
|||
|
//
|
|||
|
// In a rare reuse path there may still be entries in the open attribute data
|
|||
|
// list. This can happen when we reuse a slot in the open attribute table
|
|||
|
// during restart.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFreeAllOpenAttributeData( Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// Initialize first in case we get an allocation failure.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( IsRestartTableEmpty( &Vcb->OpenAttributeTable ));
|
|||
|
ASSERT( IsListEmpty( &Vcb->OpenAttributeData ));
|
|||
|
|
|||
|
InitializeNewTable( sizeof( OPEN_ATTRIBUTE_ENTRY ),
|
|||
|
INITIAL_NUMBER_ATTRIBUTES,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->OpenAttributeTable.Table );
|
|||
|
Vcb->OpenAttributeTable.Table = Pointers.Table;
|
|||
|
|
|||
|
//
|
|||
|
// Since we are doing a clean checkpoint we may be able to discard the
|
|||
|
// second open attribute table. We have three cases to consider.
|
|||
|
//
|
|||
|
// 1 - We want to use Version 0 on-disk but currently aren't.
|
|||
|
// 2 - We are currently using Version 0 but can free some space.
|
|||
|
// 3 - We are currently using Version 0 but don't want to.
|
|||
|
//
|
|||
|
|
|||
|
if (NtfsDefaultRestartVersion != Vcb->RestartVersion) {
|
|||
|
|
|||
|
NtfsUpdateOatVersion( Vcb, NtfsDefaultRestartVersion );
|
|||
|
|
|||
|
} else if (NtfsDefaultRestartVersion == 0) {
|
|||
|
|
|||
|
InitializeNewTable( sizeof( OPEN_ATTRIBUTE_ENTRY_V0 ),
|
|||
|
INITIAL_NUMBER_ATTRIBUTES,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->OnDiskOat->Table );
|
|||
|
Vcb->OnDiskOat->Table = Pointers.Table;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize first in case we get an allocation failure.
|
|||
|
// Make sure we commit the current transaction.
|
|||
|
//
|
|||
|
|
|||
|
NtfsCommitCurrentTransaction( IrpContext );
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
|
|||
|
ASSERT( IsRestartTableEmpty( &Vcb->TransactionTable ));
|
|||
|
|
|||
|
InitializeNewTable( sizeof( TRANSACTION_ENTRY ),
|
|||
|
INITIAL_NUMBER_TRANSACTIONS,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->TransactionTable.Table );
|
|||
|
Vcb->TransactionTable.Table = Pointers.Table;
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we do not process any log file before the restart
|
|||
|
// area, because we did not dump the open attribute table.
|
|||
|
//
|
|||
|
|
|||
|
RestartArea.StartOfCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
|
|||
|
//
|
|||
|
// More work to do if this is not a clean checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
DIRTY_PAGE_CONTEXT DirtyPageContext;
|
|||
|
PDIRTY_PAGE_ENTRY DirtyPage;
|
|||
|
POPEN_ATTRIBUTE_ENTRY OpenEntry;
|
|||
|
ULONG JustMe = 0;
|
|||
|
ULONG TempCount;
|
|||
|
BOOLEAN SkipCheckpoint;
|
|||
|
|
|||
|
//
|
|||
|
// Now we construct the dirty page table by calling the Cache Manager.
|
|||
|
// For each dirty page on files tagged with our log handle, he will
|
|||
|
// call us back at our DirtyPageRoutine. We will allocate the initial
|
|||
|
// Dirty Page Table, but we will let the call back routine grow it as
|
|||
|
// necessary.
|
|||
|
//
|
|||
|
|
|||
|
NtfsInitializeRestartTable( (((Vcb->RestartVersion == 0) ?
|
|||
|
sizeof( DIRTY_PAGE_ENTRY_V0 ) :
|
|||
|
sizeof( DIRTY_PAGE_ENTRY )) +
|
|||
|
((Vcb->ClustersPerPage - 1) * sizeof(LCN))),
|
|||
|
Vcb->DirtyPageTableSizeHint,
|
|||
|
&DirtyPages );
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &DirtyPages, TRUE );
|
|||
|
|
|||
|
DirtyPageTableInitialized = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Now we will acquire the Open Attribute Table shared to freeze changes.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
OpenAttributeTableAcquired = TRUE;
|
|||
|
|
|||
|
NameBytes = NtfsCalculateNamedBytes( IrpContext, Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// Now call the Cache Manager to give us all of our dirty pages
|
|||
|
// via the DirtyPageRoutine callback, and remember what the oldest
|
|||
|
// Lsn is for a dirty page.
|
|||
|
//
|
|||
|
|
|||
|
RtlZeroMemory( &DirtyPageContext, sizeof( DirtyPageContext ) );
|
|||
|
DirtyPageContext.DirtyPageTable = &DirtyPages;
|
|||
|
DirtyPageContext.OldestLsn.QuadPart = MAXLONGLONG;
|
|||
|
|
|||
|
CcGetDirtyPages( Vcb->LogHandle,
|
|||
|
&DirtyPageRoutine,
|
|||
|
(PVOID)IrpContext,
|
|||
|
(PVOID)&DirtyPageContext );
|
|||
|
|
|||
|
OldestDirtyPageLsn = DirtyPageContext.OldestLsn;
|
|||
|
|
|||
|
//
|
|||
|
// If we overflowed we can't contain the dirty pages in the dirty page
|
|||
|
// table and need to do a clean checkpoint instead
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPageContext.Overflow) {
|
|||
|
|
|||
|
//
|
|||
|
// We need the vcb shared for the flush which must be acquired
|
|||
|
// before the OAT
|
|||
|
//
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
OpenAttributeTableAcquired = FALSE;
|
|||
|
|
|||
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|||
|
AcquiredVcb = TRUE;
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
OpenAttributeTableAcquired = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Do a partial flush and see if the table no longer overflows afterwards
|
|||
|
//
|
|||
|
|
|||
|
NtfsFlushLsnStreams( IrpContext, Vcb, FALSE, TRUE );
|
|||
|
|
|||
|
//
|
|||
|
// Now call the Cache Manager to give us all of our dirty pages
|
|||
|
// via the DirtyPageRoutine callback, and remember what the oldest
|
|||
|
// Lsn is for a dirty page.
|
|||
|
//
|
|||
|
|
|||
|
RtlZeroMemory( &DirtyPageContext, sizeof( DirtyPageContext ) );
|
|||
|
DirtyPageContext.DirtyPageTable = &DirtyPages;
|
|||
|
DirtyPageContext.OldestLsn.QuadPart = MAXLONGLONG;
|
|||
|
|
|||
|
//
|
|||
|
// Loop through to deallocate all of the prev dirty page entries
|
|||
|
//
|
|||
|
|
|||
|
DirtyPage = NtfsGetFirstRestartTable( &DirtyPages );
|
|||
|
while (DirtyPage != NULL) {
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &DirtyPages,
|
|||
|
GetIndexFromRestartEntry( &DirtyPages,
|
|||
|
DirtyPage ));
|
|||
|
DirtyPage = NtfsGetNextRestartTable( &DirtyPages, DirtyPage );
|
|||
|
}
|
|||
|
|
|||
|
NameBytes = NtfsCalculateNamedBytes( IrpContext, Vcb );
|
|||
|
|
|||
|
CcGetDirtyPages( Vcb->LogHandle,
|
|||
|
&DirtyPageRoutine,
|
|||
|
(PVOID)IrpContext,
|
|||
|
(PVOID)&DirtyPageContext );
|
|||
|
|
|||
|
OldestDirtyPageLsn = DirtyPageContext.OldestLsn;
|
|||
|
|
|||
|
//
|
|||
|
// If we still overflowed - give up and run a full clean checkpoint
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPageContext.Overflow) {
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
IrpContext->LogFullReason = LF_DIRTY_PAGES;
|
|||
|
#endif
|
|||
|
|
|||
|
NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
TempCount = DirtyPages.Table->NumberAllocated;
|
|||
|
|
|||
|
Vcb->DirtyPageTableSizeHint = (TempCount & ~(INITIAL_DIRTY_TABLE_HINT - 1)) + INITIAL_DIRTY_TABLE_HINT;
|
|||
|
|
|||
|
//
|
|||
|
// Skip the fuzzy checkpoint if its not going to make restart any faster
|
|||
|
// i.e the oldest lsn is still the same as the last time we did it
|
|||
|
//
|
|||
|
|
|||
|
if (OldestDirtyPageLsn.QuadPart == Vcb->OldestDirtyLsn.QuadPart) {
|
|||
|
|
|||
|
//
|
|||
|
// Release any transaction tables
|
|||
|
//
|
|||
|
|
|||
|
if (OpenAttributeTableAcquired) {
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
OpenAttributeTableAcquired = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
if (TransactionTableAcquired) {
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
TransactionTableAcquired = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Flush the fileobject associated with the page if there is one
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPageContext.OldestFileObject != NULL) {
|
|||
|
|
|||
|
PSCB Scb = (PSCB)DirtyPageContext.OldestFileObject->FsContext;
|
|||
|
BOOLEAN AcquiredPaging;
|
|||
|
IO_STATUS_BLOCK Iosb;
|
|||
|
LARGE_INTEGER Offset;
|
|||
|
ULONG Length;
|
|||
|
|
|||
|
DirtyPage = GetRestartEntryFromIndex( DirtyPageContext.DirtyPageTable, DirtyPageContext.DirtyPageIndex );
|
|||
|
|
|||
|
//
|
|||
|
// At this point the vcn in the dirty page entry is actually a raw offset
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
Offset.QuadPart = ((PDIRTY_PAGE_ENTRY_V0)DirtyPage)->Vcn;
|
|||
|
Length = ((PDIRTY_PAGE_ENTRY_V0)DirtyPage)->LengthOfTransfer;
|
|||
|
|
|||
|
ASSERT( ((PDIRTY_PAGE_ENTRY_V0)DirtyPage)->OldestLsn.QuadPart == DirtyPageContext.OldestLsn.QuadPart );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Offset.QuadPart = DirtyPage->Vcn;
|
|||
|
Length = DirtyPage->LengthOfTransfer;
|
|||
|
|
|||
|
ASSERT( DirtyPage->OldestLsn.QuadPart == DirtyPageContext.OldestLsn.QuadPart );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Account for UsnJournal biasing if necc.
|
|||
|
// note at this point the vcn is actually still a byte offset
|
|||
|
//
|
|||
|
|
|||
|
if (Scb == Vcb->UsnJournal) {
|
|||
|
Offset.QuadPart += Vcb->UsnCacheBias;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Acquire same synchronization as a normal lazy write before flushing
|
|||
|
//
|
|||
|
|
|||
|
AcquiredPaging = NtfsAcquireScbForLazyWrite( Scb, TRUE );
|
|||
|
CcFlushCache( &Scb->NonpagedScb->SegmentObject, &Offset, Length, &Iosb );
|
|||
|
|
|||
|
if (AcquiredPaging) {
|
|||
|
NtfsReleaseScbFromLazyWrite( Scb );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
ObDereferenceObject( DirtyPageContext.OldestFileObject );
|
|||
|
}
|
|||
|
|
|||
|
leave;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Deref the oldest file if there is any returned from DirtyPageRoutine
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPageContext.OldestFileObject) {
|
|||
|
ObDereferenceObject( DirtyPageContext.OldestFileObject );
|
|||
|
}
|
|||
|
|
|||
|
ASSERT( (OldestDirtyPageLsn.QuadPart > Vcb->OldestDirtyLsn.QuadPart) || (TempCount == 0) );
|
|||
|
|
|||
|
if (OldestDirtyPageLsn.QuadPart != MAXLONGLONG) {
|
|||
|
Vcb->OldestDirtyLsn = OldestDirtyPageLsn;
|
|||
|
}
|
|||
|
|
|||
|
if ((OldestDirtyPageLsn.QuadPart != 0) &&
|
|||
|
OldestDirtyPageLsn.QuadPart < Vcb->LastBaseLsn.QuadPart) {
|
|||
|
|
|||
|
OldestDirtyPageLsn = Vcb->LastBaseLsn;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now loop through the dirty page table to extract all of the Vcn/Lcn
|
|||
|
// Mapping that we have, and insert it into the appropriate Scb.
|
|||
|
//
|
|||
|
|
|||
|
DirtyPage = NtfsGetFirstRestartTable( &DirtyPages );
|
|||
|
|
|||
|
//
|
|||
|
// The dirty page routine is called while holding spin locks,
|
|||
|
// so it cannot take page faults. Thus we must scan the dirty
|
|||
|
// page table we just built and fill in the Lcns here.
|
|||
|
//
|
|||
|
|
|||
|
while (DirtyPage != NULL) {
|
|||
|
|
|||
|
PSCB Scb;
|
|||
|
|
|||
|
//
|
|||
|
// If we have Lcn's then look them up.
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPage->LengthOfTransfer != 0) {
|
|||
|
|
|||
|
VCN Vcn;
|
|||
|
PLCN LcnArray;
|
|||
|
|
|||
|
//
|
|||
|
// Get the in-memory AttributeEntry from the dirty page entry.
|
|||
|
// Then update the dirty page entry with the on-disk TargetAttribute.
|
|||
|
// Also mark the pages dirty now.
|
|||
|
//
|
|||
|
|
|||
|
OpenEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
|
|||
|
DirtyPage->TargetAttribute );
|
|||
|
|
|||
|
OpenEntry->DirtyPagesSeen = TRUE;
|
|||
|
DirtyPage->TargetAttribute = OpenEntry->OatData->OnDiskAttributeIndex;
|
|||
|
|
|||
|
ASSERT( IsRestartTableEntryAllocated( OpenEntry ));
|
|||
|
|
|||
|
Scb = OpenEntry->OatData->Overlay.Scb;
|
|||
|
|
|||
|
//
|
|||
|
// Account for UsnJournal biasing if necc.
|
|||
|
// note at this point the vcn is actually still a byte offset
|
|||
|
//
|
|||
|
|
|||
|
if (Scb == Vcb->UsnJournal) {
|
|||
|
if (Vcb->RestartVersion == 0 ) {
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->Vcn = ((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->Vcn + Vcb->UsnCacheBias;
|
|||
|
} else {
|
|||
|
DirtyPage->Vcn = DirtyPage->Vcn + Vcb->UsnCacheBias;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Fix up the count of Lcns.
|
|||
|
//
|
|||
|
|
|||
|
DirtyPage->LcnsToFollow = ClustersFromBytes( Vcb, DirtyPage->LengthOfTransfer );
|
|||
|
|
|||
|
//
|
|||
|
// Now fix up the page entry to account for the differences in the
|
|||
|
// restart version structures and also make sure we don't have
|
|||
|
// an Lsn which precedes our current base Lsn.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->Reserved = 0;
|
|||
|
|
|||
|
if (((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->OldestLsn.QuadPart < Vcb->LastBaseLsn.QuadPart) {
|
|||
|
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->OldestLsn.QuadPart = Vcb->LastBaseLsn.QuadPart;
|
|||
|
}
|
|||
|
|
|||
|
Vcn = ((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->Vcn;
|
|||
|
Vcn = Int64ShraMod32( Vcn, Vcb->ClusterShift );
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->Vcn = Vcn;
|
|||
|
|
|||
|
LcnArray = &((PDIRTY_PAGE_ENTRY_V0) DirtyPage)->LcnsForPage[0];
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (DirtyPage->OldestLsn.QuadPart < Vcb->LastBaseLsn.QuadPart) {
|
|||
|
|
|||
|
DirtyPage->OldestLsn.QuadPart = Vcb->LastBaseLsn.QuadPart;
|
|||
|
}
|
|||
|
|
|||
|
DirtyPage->Vcn = Vcn = Int64ShraMod32( DirtyPage->Vcn, Vcb->ClusterShift );
|
|||
|
|
|||
|
LcnArray = &DirtyPage->LcnsForPage[0];
|
|||
|
}
|
|||
|
|
|||
|
LookupLcns( IrpContext,
|
|||
|
Scb,
|
|||
|
Vcn,
|
|||
|
DirtyPage->LcnsToFollow,
|
|||
|
FALSE,
|
|||
|
LcnArray );
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise free this dirty page entry.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &DirtyPages,
|
|||
|
GetIndexFromRestartEntry( &DirtyPages,
|
|||
|
DirtyPage ));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Point to next entry in table, or NULL.
|
|||
|
//
|
|||
|
|
|||
|
DirtyPage = NtfsGetNextRestartTable( &DirtyPages, DirtyPage );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the followings are all true, we can return as we don't want to
|
|||
|
// keep writing empty fuzzy checkpoints on idling volumes:
|
|||
|
//
|
|||
|
// 1) Last fuzzy checkpoint was clean (no dirty pages or no open transaction)
|
|||
|
// 2) No one has written to the log since last restart record
|
|||
|
// 3) Currently, there isn't any dirty page
|
|||
|
// 4) Currently, there isn't any transaction in the transaction table
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_PSEUDO_CLEAN ) &&
|
|||
|
(RestartArea.StartOfCheckpoint.QuadPart == Vcb->EndOfLastCheckpoint.QuadPart) &&
|
|||
|
IsRestartTableEmpty( &DirtyPages )) {
|
|||
|
|
|||
|
NtfsAcquireSharedStarveExRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
|
|||
|
SkipCheckpoint = IsRestartTableEmpty( &Vcb->TransactionTable );
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
SkipCheckpoint = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
if (SkipCheckpoint) {
|
|||
|
|
|||
|
//
|
|||
|
// Let's take this opportunity to shrink the Open Attribute and Transaction
|
|||
|
// table back if they have gotten large.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// First the Open Attribute Table
|
|||
|
//
|
|||
|
|
|||
|
if (!OpenAttributeTableAcquired) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
OpenAttributeTableAcquired = TRUE;
|
|||
|
} else {
|
|||
|
|
|||
|
ASSERT( ExIsResourceAcquiredExclusive( &Vcb->OpenAttributeTable.Resource ) );
|
|||
|
}
|
|||
|
|
|||
|
if (IsRestartTableEmpty( &Vcb->OpenAttributeTable ) &&
|
|||
|
(Vcb->OpenAttributeTable.Table->NumberEntries > HIGHWATER_ATTRIBUTE_COUNT)) {
|
|||
|
|
|||
|
//
|
|||
|
// Initialize first in case we get an allocation failure.
|
|||
|
//
|
|||
|
|
|||
|
InitializeNewTable( sizeof( OPEN_ATTRIBUTE_ENTRY ),
|
|||
|
INITIAL_NUMBER_ATTRIBUTES,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->OpenAttributeTable.Table );
|
|||
|
Vcb->OpenAttributeTable.Table = Pointers.Table;
|
|||
|
|
|||
|
//
|
|||
|
// Also reinitialize the OnDisk table if different.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->OnDiskOat != &Vcb->OpenAttributeTable) {
|
|||
|
|
|||
|
//
|
|||
|
// Initialize first in case we get an allocation failure.
|
|||
|
//
|
|||
|
|
|||
|
InitializeNewTable( sizeof( OPEN_ATTRIBUTE_ENTRY_V0 ),
|
|||
|
INITIAL_NUMBER_ATTRIBUTES,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->OnDiskOat->Table );
|
|||
|
Vcb->OnDiskOat->Table = Pointers.Table;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
OpenAttributeTableAcquired = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Now check the transaction table (freeing in the finally clause).
|
|||
|
//
|
|||
|
|
|||
|
if (!TransactionTableAcquired) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
TransactionTableAcquired = TRUE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ASSERT( ExIsResourceAcquiredExclusive( &Vcb->TransactionTable.Resource ) );
|
|||
|
}
|
|||
|
|
|||
|
if (IsRestartTableEmpty( &Vcb->TransactionTable )) {
|
|||
|
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle, 2, QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
|
|||
|
|
|||
|
if (Vcb->TransactionTable.Table->NumberEntries > HIGHWATER_TRANSACTION_COUNT) {
|
|||
|
|
|||
|
//
|
|||
|
// Initialize first in case we get an allocation failure.
|
|||
|
//
|
|||
|
|
|||
|
InitializeNewTable( sizeof(TRANSACTION_ENTRY),
|
|||
|
INITIAL_NUMBER_TRANSACTIONS,
|
|||
|
&Pointers );
|
|||
|
|
|||
|
NtfsFreePool( Vcb->TransactionTable.Table );
|
|||
|
Vcb->TransactionTable.Table = Pointers.Table;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
leave;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Take this opportunity to clear this flag first since we now know
|
|||
|
// this is not a pseudo clean checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_PSEUDO_CLEAN );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If there were any names, then allocate space for them and copy
|
|||
|
// them out.
|
|||
|
//
|
|||
|
|
|||
|
if (NameBytes != 0) {
|
|||
|
|
|||
|
PATTRIBUTE_NAME_ENTRY Name;
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the buffer, with space for two terminating 0's on
|
|||
|
// the end.
|
|||
|
//
|
|||
|
|
|||
|
NameBytes += 4;
|
|||
|
Name =
|
|||
|
NamesBuffer = NtfsAllocatePool( NonPagedPool, NameBytes );
|
|||
|
|
|||
|
//
|
|||
|
// Now loop to copy the names.
|
|||
|
//
|
|||
|
|
|||
|
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
|
|||
|
while (AttributeEntry != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Free the Open Attribute Entry if there were no
|
|||
|
// dirty pages and the Scb is gone. This is the only
|
|||
|
// place they are deleted. (Yes, I know we allocated
|
|||
|
// space for its name, but I didn't want to make three
|
|||
|
// passes through the open attribute table. Permeter
|
|||
|
// is running as we speak, and showing 407 open files
|
|||
|
// on NT/IDW5.)
|
|||
|
//
|
|||
|
|
|||
|
if (!AttributeEntry->DirtyPagesSeen
|
|||
|
|
|||
|
&&
|
|||
|
|
|||
|
(AttributeEntry->OatData->Overlay.Scb == NULL)) {
|
|||
|
|
|||
|
ULONG Index;
|
|||
|
|
|||
|
//
|
|||
|
// Get the index for the entry.
|
|||
|
//
|
|||
|
|
|||
|
Index = GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
|
|||
|
AttributeEntry );
|
|||
|
|
|||
|
//
|
|||
|
// Delete its name and free it up.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFreeScbAttributeName( AttributeEntry->OatData->AttributeName.Buffer );
|
|||
|
|
|||
|
if (Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( Vcb->OnDiskOat,
|
|||
|
AttributeEntry->OatData->OnDiskAttributeIndex );
|
|||
|
}
|
|||
|
|
|||
|
NtfsFreeOpenAttributeData( AttributeEntry->OatData );
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable, Index );
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise, if we are not deleting it, we have to
|
|||
|
// copy its name into the buffer we allocated.
|
|||
|
//
|
|||
|
|
|||
|
} else if (AttributeEntry->OatData->AttributeName.Length != 0) {
|
|||
|
|
|||
|
//
|
|||
|
// Prefix each name in the buffer with the attribute index
|
|||
|
// and name length. Be sure to use the index that will
|
|||
|
// be on-disk.
|
|||
|
//
|
|||
|
|
|||
|
Name->Index = (USHORT) AttributeEntry->OatData->OnDiskAttributeIndex;
|
|||
|
|
|||
|
Name->NameLength = AttributeEntry->OatData->AttributeName.Length;
|
|||
|
RtlCopyMemory( &Name->Name[0],
|
|||
|
AttributeEntry->OatData->AttributeName.Buffer,
|
|||
|
AttributeEntry->OatData->AttributeName.Length );
|
|||
|
|
|||
|
Name->Name[Name->NameLength / sizeof( WCHAR )] = 0;
|
|||
|
|
|||
|
Name = (PATTRIBUTE_NAME_ENTRY)((PCHAR)Name +
|
|||
|
sizeof(ATTRIBUTE_NAME_ENTRY) +
|
|||
|
Name->NameLength);
|
|||
|
|
|||
|
ASSERT( (PCHAR)Name <= ((PCHAR)NamesBuffer + NameBytes - 4) );
|
|||
|
}
|
|||
|
|
|||
|
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
|
|||
|
AttributeEntry );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Terminate the Names Buffer.
|
|||
|
//
|
|||
|
|
|||
|
Name->Index = 0;
|
|||
|
Name->NameLength = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now write all of the non-empty tables to the log.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Write the Open Attribute Table
|
|||
|
//
|
|||
|
// Make sure the tables are in sync.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( (IsRestartTableEmpty( Vcb->OnDiskOat ) && IsRestartTableEmpty( &Vcb->OpenAttributeTable )) ||
|
|||
|
(!IsRestartTableEmpty( Vcb->OnDiskOat ) && !IsRestartTableEmpty( &Vcb->OpenAttributeTable )));
|
|||
|
|
|||
|
if (!IsRestartTableEmpty( Vcb->OnDiskOat )) {
|
|||
|
RestartArea.OpenAttributeTableLsn =
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Vcb->MftScb,
|
|||
|
NULL,
|
|||
|
OpenAttributeTableDump,
|
|||
|
Vcb->OnDiskOat->Table,
|
|||
|
SizeOfRestartTable( Vcb->OnDiskOat ),
|
|||
|
Noop,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
(LONGLONG)0,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
|
|||
|
RestartArea.OpenAttributeTableLength = SizeOfRestartTable( Vcb->OnDiskOat );
|
|||
|
JustMe = 1;
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
OpenAttributeTableAcquired = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Write the Open Attribute Names
|
|||
|
//
|
|||
|
|
|||
|
if (NameBytes != 0) {
|
|||
|
RestartArea.AttributeNamesLsn =
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Vcb->MftScb,
|
|||
|
NULL,
|
|||
|
AttributeNamesDump,
|
|||
|
NamesBuffer,
|
|||
|
NameBytes,
|
|||
|
Noop,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
(LONGLONG)0,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
|
|||
|
RestartArea.AttributeNamesLength = NameBytes;
|
|||
|
JustMe = 1;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Write the Dirty Page Table
|
|||
|
//
|
|||
|
|
|||
|
if (!IsRestartTableEmpty( &DirtyPages )) {
|
|||
|
RestartArea.DirtyPageTableLsn =
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Vcb->MftScb,
|
|||
|
NULL,
|
|||
|
DirtyPageTableDump,
|
|||
|
DirtyPages.Table,
|
|||
|
SizeOfRestartTable(&DirtyPages),
|
|||
|
Noop,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
(LONGLONG)0,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
|
|||
|
RestartArea.DirtyPageTableLength = SizeOfRestartTable(&DirtyPages);
|
|||
|
JustMe = 1;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Write the Transaction Table if there is more than just us. We
|
|||
|
// are a transaction if we wrote any log records above.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
TransactionTableAcquired = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Assume we will want to do at least one more checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
|
|||
|
|
|||
|
if ((ULONG)Vcb->TransactionTable.Table->NumberAllocated > JustMe) {
|
|||
|
RestartArea.TransactionTableLsn =
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Vcb->MftScb,
|
|||
|
NULL,
|
|||
|
TransactionTableDump,
|
|||
|
Vcb->TransactionTable.Table,
|
|||
|
SizeOfRestartTable(&Vcb->TransactionTable),
|
|||
|
Noop,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
(LONGLONG)0,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
|
|||
|
RestartArea.TransactionTableLength =
|
|||
|
SizeOfRestartTable(&Vcb->TransactionTable);
|
|||
|
|
|||
|
//
|
|||
|
// Loop to see if the oldest Lsn comes from the transaction table.
|
|||
|
//
|
|||
|
|
|||
|
TransactionEntry = NtfsGetFirstRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
while (TransactionEntry != NULL) {
|
|||
|
|
|||
|
if ((TransactionEntry->FirstLsn.QuadPart != 0) &&
|
|||
|
|
|||
|
(TransactionEntry->FirstLsn.QuadPart < BaseLsn.QuadPart)) {
|
|||
|
|
|||
|
BaseLsn = TransactionEntry->FirstLsn;
|
|||
|
}
|
|||
|
|
|||
|
TransactionEntry = NtfsGetNextRestartTable( &Vcb->TransactionTable,
|
|||
|
TransactionEntry );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If LastTransactionLsnCount is non-zero, we should check to see if it's smaller than BaseLsn.
|
|||
|
// This is due to the window between creating a transaction to the point where we update
|
|||
|
// the FirstLsn in NtfsWriteLog.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->LastTransactionLsnCount != 0) {
|
|||
|
|
|||
|
if (Vcb->LastTransactionLsn.QuadPart < BaseLsn.QuadPart) {
|
|||
|
|
|||
|
BaseLsn = Vcb->LastTransactionLsn;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the transaction table is otherwise empty, then this is a good
|
|||
|
// time to reset our totals with Lfs, in case our counts get off a bit.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// If we are a transaction, then we have to add in our counts.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->TransactionId != 0) {
|
|||
|
|
|||
|
TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
|
|||
|
&Vcb->TransactionTable, IrpContext->TransactionId );
|
|||
|
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle,
|
|||
|
TransactionEntry->UndoRecords + 2,
|
|||
|
TransactionEntry->UndoBytes +
|
|||
|
QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise, we reset to our "idle" requirements.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle,
|
|||
|
2,
|
|||
|
QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the DirtyPage table is empty then mark this as a clean checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
if (IsRestartTableEmpty( &DirtyPages )) {
|
|||
|
|
|||
|
//
|
|||
|
// Remember the fact that this fuzzy checkpoint is pseudo clean
|
|||
|
// in the sense that the dirty page and transaction tables are empty.
|
|||
|
// It's ok if the other two tables are not empty as they are not important
|
|||
|
// in this case and will be cleaned up later on.
|
|||
|
//
|
|||
|
|
|||
|
SetFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_PSEUDO_CLEAN );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
TransactionTableAcquired = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// So far BaseLsn holds the minimum of the start Lsn for the checkpoint,
|
|||
|
// or any of the FirstLsn fields for active transactions. Now we see
|
|||
|
// if the oldest Lsn we need in the log should actually come from the
|
|||
|
// oldest page in the dirty page table.
|
|||
|
//
|
|||
|
|
|||
|
if ((OldestDirtyPageLsn.QuadPart != 0) &&
|
|||
|
|
|||
|
(OldestDirtyPageLsn.QuadPart < BaseLsn.QuadPart)) {
|
|||
|
|
|||
|
BaseLsn = OldestDirtyPageLsn;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now fill in the LowestOpenUsn in the RestartArea. This is an unsafe
|
|||
|
// test, but if we think we see an empty list, that is ok. In case no
|
|||
|
// files are open yet, make sure we do not backtrack from the number we got
|
|||
|
// at restart.
|
|||
|
//
|
|||
|
|
|||
|
RestartArea.MajorVersion = Vcb->RestartVersion;
|
|||
|
RestartArea.CurrentLsnAtMount = Vcb->CurrentLsnAtMount;
|
|||
|
RestartArea.BytesPerCluster = Vcb->BytesPerCluster;
|
|||
|
|
|||
|
RestartArea.Reserved = 0;
|
|||
|
RestartArea.UsnJournalReference = Vcb->UsnJournalReference;
|
|||
|
RestartArea.UsnCacheBias = Vcb->UsnCacheBias;
|
|||
|
|
|||
|
UsnJournal = Vcb->UsnJournal;
|
|||
|
if (UsnJournal != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Continue to advance the Usn in the Vcb on checkpoints, so that
|
|||
|
// if the list goes empty we do not get a restart that has to go
|
|||
|
// back to where we were at boot time. We use the value we captured at
|
|||
|
// the beginning - we own end resources (the transaction tables)
|
|||
|
// here so we can't reacquire the usn journal
|
|||
|
//
|
|||
|
|
|||
|
RestartArea.LowestOpenUsn = Vcb->LowestOpenUsn = LowestOpenUsn;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// BaseLsn must be monotonically increasing or we'll throw away recently
|
|||
|
// deallocatedclusters erroneously before they can be reused
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( Vcb->LastBaseLsn.QuadPart <= BaseLsn.QuadPart );
|
|||
|
Vcb->LastBaseLsn = Vcb->LastRestartArea = BaseLsn;
|
|||
|
|
|||
|
//
|
|||
|
// Finally, write our Restart Area to describe all of the above, and
|
|||
|
// give Lfs our new BaseLsn.
|
|||
|
//
|
|||
|
|
|||
|
LfsWriteRestartArea( Vcb->LogHandle,
|
|||
|
sizeof( RESTART_AREA ),
|
|||
|
&RestartArea,
|
|||
|
LfsCleanShutdown,
|
|||
|
&Vcb->LastRestartArea );
|
|||
|
|
|||
|
//
|
|||
|
// Extra work at the end of a clean checkpoint
|
|||
|
//
|
|||
|
|
|||
|
if (CleanVolume) {
|
|||
|
|
|||
|
//
|
|||
|
// Mark the fact that we've done a clean checkpoint at this time.
|
|||
|
//
|
|||
|
|
|||
|
Vcb->CleanCheckpointMark = Vcb->LogFileFullCount;
|
|||
|
Vcb->UnhandledLogFileFullCount = 0;
|
|||
|
Vcb->LastRestartAreaAtNonTopLevelLogFull.QuadPart = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Initialize our reserved area.
|
|||
|
// Also set the LastBaseLsn to the restart area itself. This will
|
|||
|
// prevent us from generating future dirty page table entries
|
|||
|
// which go back prior to the restart area.
|
|||
|
//
|
|||
|
|
|||
|
Vcb->LastBaseLsn = Vcb->LastRestartArea;
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle, 2, QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
|
|||
|
Vcb->DirtyPageTableSizeHint = INITIAL_DIRTY_TABLE_HINT;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now remember where the log file is at now, so we know when to
|
|||
|
// go idle above.
|
|||
|
//
|
|||
|
|
|||
|
Vcb->EndOfLastCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
DebugUnwind( NtfsCheckpointVolume );
|
|||
|
|
|||
|
//
|
|||
|
// If the Dirty Page Table got initialized, free it up.
|
|||
|
//
|
|||
|
|
|||
|
if (DirtyPageTableInitialized) {
|
|||
|
NtfsFreeRestartTable( &DirtyPages );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release any resources
|
|||
|
//
|
|||
|
|
|||
|
if (OpenAttributeTableAcquired) {
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
}
|
|||
|
|
|||
|
if (TransactionTableAcquired) {
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release any names buffer.
|
|||
|
//
|
|||
|
|
|||
|
if (NamesBuffer != NULL) {
|
|||
|
NtfsFreePool( NamesBuffer );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free any partial table we allocated.
|
|||
|
//
|
|||
|
|
|||
|
if (NewTable != NULL) {
|
|||
|
|
|||
|
NtfsFreePool( NewTable );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If this checkpoint created a transaction, free the index now.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->TransactionId != 0) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
|
|||
|
TRUE );
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
IrpContext->TransactionId = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (AcquireFiles) {
|
|||
|
|
|||
|
#ifdef NTFSDBG
|
|||
|
ASSERT( FlagOn( IrpContext->State, IRP_CONTEXT_STATE_CHECKPOINT_ACTIVE ));
|
|||
|
DebugDoit( ClearFlag( IrpContext->State, IRP_CONTEXT_STATE_CHECKPOINT_ACTIVE ));
|
|||
|
#endif // NTFSDBG
|
|||
|
|
|||
|
NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
if (AcquiredVcb) {
|
|||
|
|
|||
|
if (CleanVolume) {
|
|||
|
|
|||
|
//
|
|||
|
// If we acquire the vcb we also set the drain pending for the transaction table
|
|||
|
// and open attribute table. Turn that off now
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
|
|||
|
Vcb->TransactionTable.DrainPending = FALSE;
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
|
|||
|
Vcb->OpenAttributeTable.DrainPending = FALSE;
|
|||
|
NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
|
|||
|
if (Tracking) {
|
|||
|
KeQueryTickCount( (PLARGE_INTEGER)&Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].ElapsedTime );
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].ElapsedTime -=
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].StartTime;
|
|||
|
Vcb->ChkPointEntry[ Vcb->CurrentCheckpoint % NUM_CHECKPOINT_ENTRIES ].NumIos = IrpContext->Ios;
|
|||
|
|
|||
|
Vcb->CurrentCheckpoint += 1;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Capture the current base lsn before potentially giving up chkpt synchrnonization
|
|||
|
//
|
|||
|
|
|||
|
BaseLsn = Vcb->LastBaseLsn;
|
|||
|
|
|||
|
//
|
|||
|
// If we didn't own the checkpoint operation then indicate
|
|||
|
// that someone else is free to checkpoint. Hold the checkpoint
|
|||
|
// flags if we plan to trim the usn journal. The checkpoint
|
|||
|
// flags serialize the journal with the delete journal operation.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( !OwnsCheckpoint || CleanVolume );
|
|||
|
|
|||
|
if (!OwnsCheckpoint) {
|
|||
|
|
|||
|
if ((UsnJournal == NULL) || CleanVolume || AbnormalTermination()) {
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
ClearFlag( Vcb->CheckpointFlags,
|
|||
|
VCB_CHECKPOINT_SYNC_FLAGS | VCB_DUMMY_CHECKPOINT_POSTED );
|
|||
|
|
|||
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (RestorePreviousPriority) {
|
|||
|
|
|||
|
KeSetPriorityThread( (PKTHREAD)PsGetCurrentThread(),
|
|||
|
PreviousPriority );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We shouldn't have the OAT acquired anymore and the base lsn we're using to
|
|||
|
// trim the deallocated cluster list must not be > than the last base lsn in the
|
|||
|
// vcb
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( !ExIsResourceAcquiredSharedLite( &Vcb->OpenAttributeTable.Resource ) &&
|
|||
|
(BaseLsn.QuadPart <= Vcb->LastBaseLsn.QuadPart) );
|
|||
|
|
|||
|
NtfsFreeRecentlyDeallocated( IrpContext, Vcb, &BaseLsn, CleanVolume );
|
|||
|
|
|||
|
//
|
|||
|
// If there is a Usn Journal, call to perform possible trimming on a periodic checkpoint.
|
|||
|
//
|
|||
|
|
|||
|
if (!CleanVolume && (UsnJournal != NULL)) {
|
|||
|
NtfsTrimUsnJournal( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we need to post a defrag request then do so now.
|
|||
|
//
|
|||
|
|
|||
|
if (PostDefrag) {
|
|||
|
|
|||
|
PDEFRAG_MFT DefragMft;
|
|||
|
|
|||
|
//
|
|||
|
// Use a try-except to ignore allocation errors.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
if (!FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE )) {
|
|||
|
|
|||
|
SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
DefragMft = NtfsAllocatePool( NonPagedPool, sizeof( DEFRAG_MFT ));
|
|||
|
|
|||
|
DefragMft->Vcb = Vcb;
|
|||
|
DefragMft->DeallocateWorkItem = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Send it off.....
|
|||
|
//
|
|||
|
|
|||
|
ExInitializeWorkItem( &DefragMft->WorkQueueItem,
|
|||
|
(PWORKER_THREAD_ROUTINE)NtfsDefragMft,
|
|||
|
(PVOID)DefragMft );
|
|||
|
|
|||
|
ExQueueWorkItem( &DefragMft->WorkQueueItem, CriticalWorkQueue );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
} except( FsRtlIsNtstatusExpected( GetExceptionCode() )
|
|||
|
? EXCEPTION_EXECUTE_HANDLER
|
|||
|
: EXCEPTION_CONTINUE_SEARCH ) {
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsCheckpointVolume -> VOID\n") );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCheckpointForLogFileFull (
|
|||
|
IN PIRP_CONTEXT IrpContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to perform the clean checkpoint generated after
|
|||
|
a log file full. This routine will call the clean checkpoint routine
|
|||
|
and then release all of the resources acquired.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LSN LastKnownLsn;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
ASSERT( FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_OWNS_TOP_LEVEL ));
|
|||
|
|
|||
|
IrpContext->ExceptionStatus = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Call the checkpoint routine to do the actual work. Skip this in the case where there is no
|
|||
|
// longer a Vcb in the IrpContext. This can happen if doing some long running operation at
|
|||
|
// mount time (i.e. Usn scan). In that case the long running operation should periodically
|
|||
|
// checkpoint. Then Ntfs will do a clean checkpoint after restart and the remaining work
|
|||
|
// to do in the long-running operation will decrease. At some point it will decrease enough
|
|||
|
// to finish the mount.
|
|||
|
//
|
|||
|
// All of the other work is required since this IrpContext will be used to retry the mount.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->Vcb != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// If we're only trying to synchronize with a clean checkpoint use Li0 for
|
|||
|
// the lastknownLsn which will guarantee after NtfsCheckpointvolume gets
|
|||
|
// checkpoint synchronization it won't do anymore work. Otherwise use
|
|||
|
// the last restart area we recorded in NtfsProcessException at the raise point
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ONLY_SYNCH_CHECKPOINT )) {
|
|||
|
LastKnownLsn = IrpContext->LastRestartArea;
|
|||
|
} else {
|
|||
|
LastKnownLsn = Li0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// This can raise. However, in the case of dismounts, we do want this to
|
|||
|
// plough on and succeed the dismount. For example, cluster service marks
|
|||
|
// the volume offline first and sends the dismount afterward, but still expects it to succeed.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
NtfsCheckpointVolume( IrpContext,
|
|||
|
IrpContext->Vcb,
|
|||
|
FALSE,
|
|||
|
TRUE,
|
|||
|
FALSE,
|
|||
|
0,
|
|||
|
LastKnownLsn );
|
|||
|
|
|||
|
} except (NtfsCheckpointExceptionFilter( IrpContext,
|
|||
|
GetExceptionInformation(),
|
|||
|
GetExceptionCode() )) {
|
|||
|
|
|||
|
//
|
|||
|
// This is a LOG_FILE_FULL raise coming via dismount. Ignore errors
|
|||
|
// because we want the dismount to succeed.
|
|||
|
//
|
|||
|
|
|||
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|||
|
if (IrpContext->TransactionId != 0) {
|
|||
|
|
|||
|
NtfsCleanupFailedTransaction( IrpContext );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ONLY_SYNCH_CHECKPOINT );
|
|||
|
}
|
|||
|
|
|||
|
ASSERT( IrpContext->TransactionId == 0 );
|
|||
|
ASSERT( !ExIsResourceAcquiredSharedLite( &IrpContext->Vcb->OpenAttributeTable.Resource ) );
|
|||
|
|
|||
|
//
|
|||
|
// Cleanup the IrpContext but don't delete it.
|
|||
|
//
|
|||
|
|
|||
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
|
|||
|
NtfsCleanupIrpContext( IrpContext, TRUE );
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we restore the RestartArea.
|
|||
|
//
|
|||
|
|
|||
|
IrpContext->LastRestartArea = Li0;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
NtfsCheckpointForVolumeSnapshot (
|
|||
|
IN PIRP_CONTEXT IrpContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to perform a volume flush and a
|
|||
|
clean checkpoint before a snapshot of the volume is taken.
|
|||
|
Since we need to keep the volume quiescent, we make it a
|
|||
|
point to leave the file resources acquired on exit.
|
|||
|
|
|||
|
Arguments:
|
|||
|
IrpContext.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Status.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LOGICAL AcquiredCheckpoint;
|
|||
|
LOGICAL AcquiredFiles = FALSE;
|
|||
|
LOGICAL AcquiredVcb = FALSE;
|
|||
|
PVCB Vcb;
|
|||
|
NTSTATUS Status = STATUS_SUCCESS;
|
|||
|
LOGICAL DefragPermitted;
|
|||
|
KPRIORITY PreviousPriority;
|
|||
|
BOOLEAN RestorePreviousPriority = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Clear the Mft defrag flag to stop any actions behind our backs.
|
|||
|
//
|
|||
|
|
|||
|
Vcb = IrpContext->Vcb;
|
|||
|
|
|||
|
//
|
|||
|
// If this is a readonly volume, then there's nothing we need to do.
|
|||
|
//
|
|||
|
|
|||
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|||
|
|
|||
|
ASSERT( Status == STATUS_SUCCESS );
|
|||
|
DebugTrace( -1, Dbg, ("NtfsCheckpointForVolumeSnapshot -> %08lx\n", Status) );
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
DefragPermitted = FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
|
|||
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
AcquiredCheckpoint = FALSE;
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// Then lock out all other checkpoint operations.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
|
|||
|
while (FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_SYNC_FLAGS )) {
|
|||
|
|
|||
|
//
|
|||
|
// Release the checkpoint event because we cannot checkpoint now.
|
|||
|
//
|
|||
|
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
NtfsWaitOnCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_SYNC_FLAGS );
|
|||
|
NtfsResetCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
AcquiredCheckpoint = TRUE;
|
|||
|
|
|||
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|||
|
AcquiredVcb = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Check that the volume is still mounted.
|
|||
|
//
|
|||
|
|
|||
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|||
|
|
|||
|
Status = STATUS_VOLUME_DISMOUNTED;
|
|||
|
leave;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Start by flushing the volume, because we can't call FlushVolume later
|
|||
|
// while holding only the Main resources without their corresponding
|
|||
|
// pagingio resources. Flushing the userdata doesn't really need to be
|
|||
|
// atomic with the rest of the operation; we just have to make sure that
|
|||
|
// the volume is consistent and restartable without log recovery.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFlushVolume( IrpContext,
|
|||
|
Vcb,
|
|||
|
TRUE,
|
|||
|
FALSE,
|
|||
|
TRUE,
|
|||
|
FALSE );
|
|||
|
|
|||
|
//
|
|||
|
// Give ourselves some juice. We'll need it.
|
|||
|
//
|
|||
|
|
|||
|
PreviousPriority = KeSetPriorityThread( (PKTHREAD)PsGetCurrentThread(),
|
|||
|
LOW_REALTIME_PRIORITY );
|
|||
|
|
|||
|
if (PreviousPriority != LOW_REALTIME_PRIORITY) {
|
|||
|
|
|||
|
RestorePreviousPriority = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Lock, stock, clean checkpoint, volume flush and
|
|||
|
// two smoking barrels. No chance of acquiring PagingIo
|
|||
|
// here; pretty much only shutdown has that luxury.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE, FALSE );
|
|||
|
AcquiredFiles = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Generate usn CLOSE records. We don't bother to get the FcbMutex because
|
|||
|
// we already have the Fcb main resource exclusively.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->UsnJournal != NULL) {
|
|||
|
|
|||
|
PLIST_ENTRY Links;
|
|||
|
PFCB_USN_RECORD UsnRecord;
|
|||
|
|
|||
|
while (TRUE) {
|
|||
|
|
|||
|
NtfsLockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|||
|
|
|||
|
Links = Vcb->ModifiedOpenFiles.Flink;
|
|||
|
if (Links == &Vcb->ModifiedOpenFiles) {
|
|||
|
|
|||
|
NtfsUnlockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
UsnRecord = (PFCB_USN_RECORD)CONTAINING_RECORD( Links,
|
|||
|
FCB_USN_RECORD,
|
|||
|
ModifiedOpenFilesLinks );
|
|||
|
|
|||
|
NtfsUnlockFcb( IrpContext, Vcb->UsnJournal->Fcb );
|
|||
|
|
|||
|
//
|
|||
|
// Post the CLOSE record. Checkpointing takes this UsnRecord
|
|||
|
// off the ModifiedOpenFiles list.
|
|||
|
//
|
|||
|
|
|||
|
NtfsPostUsnChange( IrpContext, UsnRecord->Fcb, USN_REASON_CLOSE );
|
|||
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|||
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SetFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
|
|||
|
|
|||
|
#ifdef PERF_STATS
|
|||
|
IrpContext->LogFullReason = LF_SNAPSHOT;
|
|||
|
#endif
|
|||
|
|
|||
|
NtfsCheckpointVolume( IrpContext, Vcb, TRUE, TRUE, FALSE, 0, Vcb->LastRestartArea );
|
|||
|
NtfsCommitCurrentTransaction( IrpContext );
|
|||
|
|
|||
|
ClearFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
//
|
|||
|
// Restore DEFRAG_PERMITTED flag if we need to.
|
|||
|
//
|
|||
|
|
|||
|
if (DefragPermitted) {
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release the checkpoint, if we got it, but we aren't releasing
|
|||
|
// all the files unless there was an error.
|
|||
|
//
|
|||
|
|
|||
|
if (AcquiredCheckpoint) {
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|||
|
ClearFlag( Vcb->CheckpointFlags,
|
|||
|
VCB_CHECKPOINT_SYNC_FLAGS | VCB_DUMMY_CHECKPOINT_POSTED);
|
|||
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release the file resources only if we hit an error.
|
|||
|
// We normally do this in the completion routine for the IOCTL.
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
|
|||
|
|
|||
|
if (AcquiredFiles) {
|
|||
|
|
|||
|
NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
if (AcquiredVcb) {
|
|||
|
|
|||
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (RestorePreviousPriority) {
|
|||
|
|
|||
|
KeSetPriorityThread( (PKTHREAD)PsGetCurrentThread(),
|
|||
|
PreviousPriority );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsCheckpointForVolsnap -exit\n") );
|
|||
|
|
|||
|
return Status;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCleanCheckpoint (
|
|||
|
IN PVCB Vcb
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to perform a single clean checkpoint at the top level
|
|||
|
and return. It is used when the lazy writer gets a log file full in order
|
|||
|
to perform the clean checkpoint within the thread doing the lazy write.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
IRP_CONTEXT LocalIrpContext;
|
|||
|
PIRP_CONTEXT IrpContext = &LocalIrpContext;
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
//
|
|||
|
// Allocate an Irp Context for the request.
|
|||
|
//
|
|||
|
|
|||
|
NtfsInitializeIrpContext( NULL, TRUE, &IrpContext );
|
|||
|
IrpContext->Vcb = Vcb;
|
|||
|
|
|||
|
if (Vcb->LastRestartAreaAtNonTopLevelLogFull.QuadPart != 0) {
|
|||
|
IrpContext->LastRestartArea = Vcb->LastRestartAreaAtNonTopLevelLogFull;
|
|||
|
} else {
|
|||
|
IrpContext->LastRestartArea = Vcb->LastRestartArea;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// There is no point in posting any dummy requests.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireCheckpoint( IrpContext, IrpContext->Vcb );
|
|||
|
SetFlag( IrpContext->Vcb->CheckpointFlags, VCB_DUMMY_CHECKPOINT_POSTED );
|
|||
|
NtfsReleaseCheckpoint( IrpContext, IrpContext->Vcb );
|
|||
|
|
|||
|
//
|
|||
|
// Send this off to the FspDispatch routine. It will handle all of the
|
|||
|
// top level logic as well as deleting the IrpContext.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFspDispatch( IrpContext );
|
|||
|
|
|||
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCommitCurrentTransaction (
|
|||
|
IN PIRP_CONTEXT IrpContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine commits the current transaction by writing a final record
|
|||
|
to the log and deallocating the transaction Id.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PTRANSACTION_ENTRY TransactionEntry;
|
|||
|
PVCB Vcb = IrpContext->Vcb;
|
|||
|
PFCB UsnFcb;
|
|||
|
PUSN_FCB ThisUsn, LastUsn;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|||
|
try {
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// Walk through the queue of usn records. We want to remove any effect of this operation.
|
|||
|
//
|
|||
|
|
|||
|
ThisUsn = &IrpContext->Usn;
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
//
|
|||
|
// If we log the close for a file, then it is time to reset the
|
|||
|
// Usn reasons for the file. Nothing to do here unless we
|
|||
|
// wrote new reasons.
|
|||
|
//
|
|||
|
|
|||
|
if (ThisUsn->CurrentUsnFcb != NULL ) {
|
|||
|
|
|||
|
PSCB UsnJournal = Vcb->UsnJournal;
|
|||
|
PFCB_USN_RECORD FcbUsnRecord;
|
|||
|
|
|||
|
|
|||
|
UsnFcb = ThisUsn->CurrentUsnFcb;
|
|||
|
|
|||
|
NtfsLockFcb( IrpContext, UsnFcb );
|
|||
|
|
|||
|
if (UsnJournal != NULL) {
|
|||
|
NtfsLockFcb( IrpContext, UsnJournal->Fcb );
|
|||
|
}
|
|||
|
|
|||
|
FcbUsnRecord = UsnFcb->FcbUsnRecord;
|
|||
|
|
|||
|
//
|
|||
|
// After locking the fcb test for the presence of the fcb record again
|
|||
|
// DeleteUsnJournal may have already removed it
|
|||
|
//
|
|||
|
|
|||
|
if (FcbUsnRecord) {
|
|||
|
|
|||
|
UsnFcb->Usn = FcbUsnRecord->UsnRecord.Usn;
|
|||
|
|
|||
|
//
|
|||
|
// Now add or move the Fcb in the ModifiedOpenFiles list.
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( FcbUsnRecord->UsnRecord.Reason, USN_REASON_CLOSE )) {
|
|||
|
|
|||
|
//
|
|||
|
// Clean up the UsnRecord in the Fcb.
|
|||
|
//
|
|||
|
|
|||
|
FcbUsnRecord->UsnRecord.Reason = 0;
|
|||
|
FcbUsnRecord->UsnRecord.SourceInfo = 0;
|
|||
|
|
|||
|
if (UsnJournal != NULL) {
|
|||
|
|
|||
|
if( FcbUsnRecord->ModifiedOpenFilesLinks.Flink != NULL ) {
|
|||
|
|
|||
|
RemoveEntryList( &FcbUsnRecord->ModifiedOpenFilesLinks );
|
|||
|
FcbUsnRecord->ModifiedOpenFilesLinks.Flink = NULL;
|
|||
|
|
|||
|
if (FcbUsnRecord->TimeOutLinks.Flink != NULL) {
|
|||
|
|
|||
|
RemoveEntryList( &FcbUsnRecord->TimeOutLinks );
|
|||
|
FcbUsnRecord->TimeOutLinks.Flink = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (UsnJournal != NULL) {
|
|||
|
|
|||
|
if (FcbUsnRecord->ModifiedOpenFilesLinks.Flink != NULL) {
|
|||
|
|
|||
|
RemoveEntryList( &FcbUsnRecord->ModifiedOpenFilesLinks );
|
|||
|
if (FcbUsnRecord->TimeOutLinks.Flink != NULL) {
|
|||
|
|
|||
|
RemoveEntryList( &FcbUsnRecord->TimeOutLinks );
|
|||
|
FcbUsnRecord->TimeOutLinks.Flink = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
InsertTailList( &Vcb->ModifiedOpenFiles, &FcbUsnRecord->ModifiedOpenFilesLinks );
|
|||
|
|
|||
|
if (UsnFcb->CleanupCount == 0) {
|
|||
|
|
|||
|
InsertTailList( Vcb->CurrentTimeOutFiles, &FcbUsnRecord->TimeOutLinks );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// 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 );
|
|||
|
ThisUsn = LastUsn;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RtlZeroMemory( &ThisUsn->CurrentUsnFcb,
|
|||
|
sizeof( USN_FCB ) - FIELD_OFFSET( USN_FCB, CurrentUsnFcb ));
|
|||
|
}
|
|||
|
|
|||
|
if (UsnJournal != NULL) {
|
|||
|
NtfsUnlockFcb( IrpContext, UsnJournal->Fcb );
|
|||
|
}
|
|||
|
NtfsUnlockFcb( IrpContext, UsnFcb );
|
|||
|
}
|
|||
|
|
|||
|
if (ThisUsn->NextUsnFcb == NULL) { break; }
|
|||
|
|
|||
|
//
|
|||
|
// Move to the next entry.
|
|||
|
//
|
|||
|
|
|||
|
LastUsn = ThisUsn;
|
|||
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|||
|
} while (TRUE);
|
|||
|
|
|||
|
//
|
|||
|
// If this request created a transaction, complete it now.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->TransactionId != 0) {
|
|||
|
|
|||
|
LSN CommitLsn;
|
|||
|
|
|||
|
//
|
|||
|
// It is possible to get a LOG_FILE_FULL before writing
|
|||
|
// out the first log record of a transaction. In that
|
|||
|
// case there is a transaction Id but we haven't reserved
|
|||
|
// space in the log file. It is wrong to write the
|
|||
|
// commit record in this case because we can get an
|
|||
|
// unexpected LOG_FILE_FULL. We can also test the UndoRecords
|
|||
|
// count in the transaction entry but don't want to acquire
|
|||
|
// the restart table to make this check.
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG )) {
|
|||
|
|
|||
|
//
|
|||
|
// Write the log record to "forget" this transaction,
|
|||
|
// because it should not be aborted. Until if/when we
|
|||
|
// do real TP, commit and forget are atomic.
|
|||
|
//
|
|||
|
|
|||
|
CommitLsn =
|
|||
|
NtfsWriteLog( IrpContext,
|
|||
|
Vcb->MftScb,
|
|||
|
NULL,
|
|||
|
ForgetTransaction,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
CompensationLogRecord,
|
|||
|
(PVOID)&Li0,
|
|||
|
sizeof( IrpContext->ExceptionStatus ), // final exception status
|
|||
|
(LONGLONG)IrpContext->ExceptionStatus,
|
|||
|
0,
|
|||
|
0,
|
|||
|
0 );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We can now free the transaction table index, because we are
|
|||
|
// done with it now.
|
|||
|
//
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
|
|||
|
TRUE );
|
|||
|
|
|||
|
TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
|
|||
|
&Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
|
|||
|
//
|
|||
|
// Call Lfs to free our undo space.
|
|||
|
//
|
|||
|
|
|||
|
if ((TransactionEntry->UndoRecords != 0) &&
|
|||
|
(!FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ))) {
|
|||
|
|
|||
|
LfsResetUndoTotal( Vcb->LogHandle,
|
|||
|
TransactionEntry->UndoRecords,
|
|||
|
-TransactionEntry->UndoBytes );
|
|||
|
}
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
|
|||
|
//
|
|||
|
// Mark that there is no transaction for the irp and signal
|
|||
|
// any waiters if there are no transactions left
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->TransactionTable.Table->NumberAllocated == 0) {
|
|||
|
|
|||
|
KeSetEvent( &Vcb->TransactionsDoneEvent, 0, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &Vcb->TransactionTable );
|
|||
|
|
|||
|
IrpContext->TransactionId = 0;
|
|||
|
|
|||
|
//
|
|||
|
// One way we win by being recoverable, is that we do not really
|
|||
|
// have to do write-through - flushing the updates to the log
|
|||
|
// is enough. We don't make this call if we are in the abort
|
|||
|
// transaction path. Otherwise we could get a log file full
|
|||
|
// while aborting.
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( IrpContext->TopLevelIrpContext->State, IRP_CONTEXT_STATE_WRITE_THROUGH ) &&
|
|||
|
(IrpContext == IrpContext->TopLevelIrpContext) &&
|
|||
|
(IrpContext->TopLevelIrpContext->ExceptionStatus == STATUS_SUCCESS)) {
|
|||
|
|
|||
|
NtfsUpdateScbSnapshots( IrpContext );
|
|||
|
LfsFlushToLsn( Vcb->LogHandle, CommitLsn );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Signal any waiters for the new length.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->CheckNewLength != NULL) {
|
|||
|
|
|||
|
NtfsProcessNewLengthQueue( IrpContext, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|||
|
} except( ASSERT( GetExceptionCode() != STATUS_LOG_FILE_FULL ), EXCEPTION_CONTINUE_SEARCH ) {
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCheckpointCurrentTransaction (
|
|||
|
IN PIRP_CONTEXT IrpContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine checkpoints the current transaction by commiting it
|
|||
|
to the log and deallocating the transaction Id. The current request
|
|||
|
cann keep running, but changes to date are committed and will not be
|
|||
|
backed out.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PVCB Vcb = IrpContext->Vcb;
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If there are new UsnReasons in the IrpContext, then we should write the journal
|
|||
|
// now. Note that it is ok for a checkpoint to get logfile full, but in general commit
|
|||
|
// should not.
|
|||
|
//
|
|||
|
|
|||
|
if ((IrpContext->Usn.NewReasons | IrpContext->Usn.RemovedSourceInfo) != 0) {
|
|||
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|||
|
}
|
|||
|
|
|||
|
NtfsCommitCurrentTransaction( IrpContext );
|
|||
|
|
|||
|
//
|
|||
|
// Cleanup any recently deallocated record information for this transaction.
|
|||
|
//
|
|||
|
|
|||
|
NtfsDeallocateRecordsComplete( IrpContext );
|
|||
|
IrpContext->DeallocatedClusters = 0;
|
|||
|
IrpContext->FreeClusterChange = 0;
|
|||
|
|
|||
|
//
|
|||
|
// The following resources may have been flagged for immediate release on commit.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->AcquireFilesCount == 0) {
|
|||
|
|
|||
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_USN_JRNL )) {
|
|||
|
NtfsReleaseScb( IrpContext, Vcb->UsnJournal );
|
|||
|
}
|
|||
|
|
|||
|
if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_MFT )) {
|
|||
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_USN_JRNL |
|
|||
|
IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|||
|
|
|||
|
NtfsUpdateScbSnapshots( IrpContext );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsInitializeLogging (
|
|||
|
)
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is to be called once during startup of Ntfs (not once
|
|||
|
per volume), to initialize the logging support.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsInitializeLogging:\n") );
|
|||
|
LfsInitializeLogFileService();
|
|||
|
DebugTrace( -1, Dbg, ("NtfsInitializeLogging -> VOID\n") );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsStartLogFile (
|
|||
|
IN PSCB LogFileScb,
|
|||
|
IN PVCB Vcb
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine opens the log file for a volume by calling Lfs. The returned
|
|||
|
LogHandle is stored in the Vcb. If the log file has not been initialized,
|
|||
|
Lfs detects this and initializes it automatically.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
LogFileScb - The Scb for the log file
|
|||
|
|
|||
|
Vcb - Pointer to the Vcb for this volume
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
UNICODE_STRING UnicodeName;
|
|||
|
LFS_INFO LfsInfo;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsStartLogFile:\n") );
|
|||
|
|
|||
|
RtlInitUnicodeString( &UnicodeName, L"NTFS" );
|
|||
|
|
|||
|
//
|
|||
|
// LfsInfo structure acts as a information conduit between
|
|||
|
// LFS and the NTFS client.
|
|||
|
//
|
|||
|
|
|||
|
if (Vcb->MajorVersion >= 3) {
|
|||
|
|
|||
|
LfsInfo.LfsClientInfo = LfsFixedPageSize;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
LfsInfo.LfsClientInfo = LfsPackLog;
|
|||
|
}
|
|||
|
|
|||
|
LfsInfo.ReadOnly = (LOGICAL)NtfsIsVolumeReadOnly( Vcb );
|
|||
|
LfsInfo.InRestart = (LOGICAL)FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS );
|
|||
|
LfsInfo.BadRestart = (LOGICAL)FlagOn( Vcb->VcbState, VCB_STATE_BAD_RESTART );
|
|||
|
|
|||
|
//
|
|||
|
// Slam the allocation size into file size and valid data in case there
|
|||
|
// is some error.
|
|||
|
//
|
|||
|
|
|||
|
LogFileScb->Header.FileSize = LogFileScb->Header.AllocationSize;
|
|||
|
LogFileScb->Header.ValidDataLength = LogFileScb->Header.AllocationSize;
|
|||
|
|
|||
|
//
|
|||
|
// Now call into LFS and Open/Restart the log file. This could raise
|
|||
|
// for various reasons, one of which is an attempt to do restart
|
|||
|
// on a write protected volume. Vcb wont have the VALID_LOG_HANDLE flag then.
|
|||
|
//
|
|||
|
|
|||
|
Vcb->LogHeaderReservation = LfsOpenLogFile( LogFileScb->FileObject,
|
|||
|
UnicodeName,
|
|||
|
1,
|
|||
|
0,
|
|||
|
LogFileScb->Header.AllocationSize.QuadPart,
|
|||
|
&LfsInfo,
|
|||
|
&Vcb->LogHandle,
|
|||
|
&Vcb->LfsWriteData );
|
|||
|
|
|||
|
SetFlag( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE );
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsStartLogFile -> VOID\n") );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsStopLogFile (
|
|||
|
IN PVCB Vcb
|
|||
|
)
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine should be called during volume dismount to close the volume's
|
|||
|
log file with the log file service.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Vcb - Pointer to the Vcb for the volume
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
LFS_LOG_HANDLE LogHandle = Vcb->LogHandle;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsStopLogFile:\n") );
|
|||
|
|
|||
|
if (FlagOn( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE )) {
|
|||
|
|
|||
|
ASSERT( LogHandle != NULL );
|
|||
|
|
|||
|
//
|
|||
|
// We don't do any logfile flushing if the volume
|
|||
|
// is mounted read only or if the device is already gone.
|
|||
|
//
|
|||
|
|
|||
|
if (!NtfsIsVolumeReadOnly( Vcb )) {
|
|||
|
|
|||
|
//
|
|||
|
// Proceed even if this call fails. There is nothing
|
|||
|
// more we can do at this point.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
LfsFlushToLsn( LogHandle, LiMax );
|
|||
|
|
|||
|
} except( (FsRtlIsNtstatusExpected( GetExceptionCode() )) ?
|
|||
|
EXCEPTION_EXECUTE_HANDLER :
|
|||
|
EXCEPTION_CONTINUE_SEARCH ) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ClearFlag( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE );
|
|||
|
|
|||
|
//
|
|||
|
// Allow LFS to close its books. We do this even for readonly
|
|||
|
// mounts, although we filter writes at the LFS level for those.
|
|||
|
//
|
|||
|
|
|||
|
LfsCloseLogFile( LogHandle );
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsStopLogFile -> VOID\n") );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsInitializeRestartTable (
|
|||
|
IN ULONG EntrySize,
|
|||
|
IN ULONG NumberEntries,
|
|||
|
OUT PRESTART_POINTERS TablePointer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to allocate and initialize a new Restart Table,
|
|||
|
and return a pointer to it.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
EntrySize - Size of the table entries, in bytes.
|
|||
|
|
|||
|
NumberEntries - Number of entries to allocate for the table.
|
|||
|
|
|||
|
TablePointer - Returns a pointer to the table.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
NtfsInitializeRestartPointers( TablePointer );
|
|||
|
|
|||
|
//
|
|||
|
// Call common routine to allocate the actual table.
|
|||
|
//
|
|||
|
|
|||
|
InitializeNewTable( EntrySize, NumberEntries, TablePointer );
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
DebugUnwind( NtfsInitializeRestartTable );
|
|||
|
|
|||
|
//
|
|||
|
// On error, clean up any partial work that was done.
|
|||
|
//
|
|||
|
|
|||
|
if (AbnormalTermination()) {
|
|||
|
|
|||
|
NtfsFreeRestartTable( TablePointer );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsFreeRestartTable (
|
|||
|
IN PRESTART_POINTERS TablePointer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine frees a previously allocated Restart Table.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the Restart Table to delete.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
if (TablePointer->Table != NULL) {
|
|||
|
NtfsFreePool( TablePointer->Table );
|
|||
|
TablePointer->Table = NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (TablePointer->ResourceInitialized) {
|
|||
|
ExDeleteResourceLite( &TablePointer->Resource );
|
|||
|
TablePointer->ResourceInitialized = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsExtendRestartTable (
|
|||
|
IN PRESTART_POINTERS TablePointer,
|
|||
|
IN ULONG NumberNewEntries,
|
|||
|
IN ULONG FreeGoal
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine extends a previously allocated Restart Table, by
|
|||
|
creating and initializing a new one, and copying over the the
|
|||
|
table entries from the old one. The old table is then deallocated.
|
|||
|
On return, the table pointer points to the new Restart Table.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Address of the pointer to the previously created table.
|
|||
|
|
|||
|
NumberNewEntries - The number of addtional entries to be allocated
|
|||
|
in the new table.
|
|||
|
|
|||
|
FreeGoal - A hint as to what point the caller would like to truncate
|
|||
|
the table back to, when sufficient entries are deleted.
|
|||
|
If truncation is not desired, then MAXULONG may be specified.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PRESTART_TABLE NewTable, OldTable;
|
|||
|
ULONG OldSize;
|
|||
|
|
|||
|
OldSize = SizeOfRestartTable( TablePointer );
|
|||
|
|
|||
|
//
|
|||
|
// Get pointer to old table.
|
|||
|
//
|
|||
|
|
|||
|
OldTable = TablePointer->Table;
|
|||
|
ASSERT_RESTART_TABLE( OldTable );
|
|||
|
|
|||
|
//
|
|||
|
// Start by initializing a table for the new size.
|
|||
|
//
|
|||
|
|
|||
|
InitializeNewTable( OldTable->EntrySize,
|
|||
|
OldTable->NumberEntries + NumberNewEntries,
|
|||
|
TablePointer );
|
|||
|
|
|||
|
//
|
|||
|
// Copy body of old table in place to new table.
|
|||
|
//
|
|||
|
|
|||
|
NewTable = TablePointer->Table;
|
|||
|
RtlMoveMemory( (NewTable + 1),
|
|||
|
(OldTable + 1),
|
|||
|
OldTable->EntrySize * OldTable->NumberEntries );
|
|||
|
|
|||
|
//
|
|||
|
// Fix up new table's header, and fix up free list.
|
|||
|
//
|
|||
|
|
|||
|
NewTable->FreeGoal = MAXULONG;
|
|||
|
if (FreeGoal != MAXULONG) {
|
|||
|
NewTable->FreeGoal = sizeof(RESTART_TABLE) + FreeGoal * NewTable->EntrySize;
|
|||
|
}
|
|||
|
|
|||
|
if (OldTable->FirstFree != 0) {
|
|||
|
|
|||
|
NewTable->FirstFree = OldTable->FirstFree;
|
|||
|
*(PULONG)GetRestartEntryFromIndex( TablePointer, OldTable->LastFree ) =
|
|||
|
OldSize;;
|
|||
|
} else {
|
|||
|
|
|||
|
NewTable->FirstFree = OldSize;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Copy number allocated
|
|||
|
//
|
|||
|
|
|||
|
NewTable->NumberAllocated = OldTable->NumberAllocated;
|
|||
|
|
|||
|
ASSERT( NewTable->NumberAllocated >= 0 );
|
|||
|
ASSERT( NewTable->FirstFree != RESTART_ENTRY_ALLOCATED );
|
|||
|
//
|
|||
|
// Free the old table and return the new one.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFreePool( OldTable );
|
|||
|
|
|||
|
ASSERT_RESTART_TABLE( NewTable );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
ULONG
|
|||
|
NtfsAllocateRestartTableIndex (
|
|||
|
IN PRESTART_POINTERS TablePointer,
|
|||
|
IN ULONG Exclusive
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine allocates an index from within a previously initialized
|
|||
|
Restart Table. If the table is empty, it is extended.
|
|||
|
|
|||
|
Note that the table must already be acquired either shared or exclusive,
|
|||
|
and if it must be extended, then the table is released and will be
|
|||
|
acquired exclusive on return.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the Restart Table in which an index is to
|
|||
|
be allocated.
|
|||
|
|
|||
|
Exclusive - Indicates if we have the table exclusive (or if we know that
|
|||
|
synchronization is not a problem).
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The allocated index.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PRESTART_TABLE Table;
|
|||
|
ULONG EntryIndex;
|
|||
|
KLOCK_QUEUE_HANDLE LockHandle;
|
|||
|
PULONG Entry;
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsAllocateRestartTableIndex:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
|
|||
|
|
|||
|
Table = TablePointer->Table;
|
|||
|
ASSERT_RESTART_TABLE(Table);
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the spin lock to synchronize the allocation.
|
|||
|
//
|
|||
|
|
|||
|
KeAcquireInStackQueuedSpinLock( &TablePointer->SpinLock, &LockHandle );
|
|||
|
|
|||
|
//
|
|||
|
// If the table is empty, then we have to extend it.
|
|||
|
//
|
|||
|
|
|||
|
if (Table->FirstFree == 0) {
|
|||
|
|
|||
|
//
|
|||
|
// First release the spin lock and the table resource, and get
|
|||
|
// the resource exclusive.
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseInStackQueuedSpinLock( &LockHandle );
|
|||
|
|
|||
|
if (!Exclusive) {
|
|||
|
|
|||
|
NtfsReleaseRestartTable( TablePointer );
|
|||
|
NtfsAcquireExclusiveRestartTable( TablePointer, TRUE );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now extend the table. Note that if this routine raises, we have
|
|||
|
// nothing to release.
|
|||
|
//
|
|||
|
|
|||
|
NtfsExtendRestartTable( TablePointer, 16, MAXULONG );
|
|||
|
|
|||
|
//
|
|||
|
// And re-get our pointer to the restart table
|
|||
|
//
|
|||
|
|
|||
|
Table = TablePointer->Table;
|
|||
|
|
|||
|
//
|
|||
|
// Now get the spin lock again and proceed.
|
|||
|
//
|
|||
|
|
|||
|
KeAcquireInStackQueuedSpinLock( &TablePointer->SpinLock, &LockHandle );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Get First Free to return it.
|
|||
|
//
|
|||
|
|
|||
|
EntryIndex = Table->FirstFree;
|
|||
|
|
|||
|
ASSERT( EntryIndex != 0 );
|
|||
|
|
|||
|
//
|
|||
|
// Dequeue this entry and zero it.
|
|||
|
//
|
|||
|
|
|||
|
Entry = (PULONG)GetRestartEntryFromIndex( TablePointer, EntryIndex );
|
|||
|
|
|||
|
Table->FirstFree = *Entry;
|
|||
|
ASSERT( Table->FirstFree != RESTART_ENTRY_ALLOCATED );
|
|||
|
|
|||
|
RtlZeroMemory( Entry, Table->EntrySize );
|
|||
|
|
|||
|
//
|
|||
|
// Show that it's allocated.
|
|||
|
//
|
|||
|
|
|||
|
*Entry = RESTART_ENTRY_ALLOCATED;
|
|||
|
|
|||
|
//
|
|||
|
// If list is going empty, then we fix the LastFree as well.
|
|||
|
//
|
|||
|
|
|||
|
if (Table->FirstFree == 0) {
|
|||
|
|
|||
|
Table->LastFree = 0;
|
|||
|
}
|
|||
|
|
|||
|
Table->NumberAllocated += 1;
|
|||
|
|
|||
|
//
|
|||
|
// Now just release the spin lock before returning.
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseInStackQueuedSpinLock( &LockHandle );
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsAllocateRestartTableIndex -> %08lx\n", EntryIndex) );
|
|||
|
|
|||
|
return EntryIndex;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
NtfsAllocateRestartTableFromIndex (
|
|||
|
IN PRESTART_POINTERS TablePointer,
|
|||
|
IN ULONG Index
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine allocates a specific index from within a previously
|
|||
|
initialized Restart Table. If the index does not exist within the
|
|||
|
existing table, the table is extended.
|
|||
|
|
|||
|
Note that the table must already be acquired either shared or exclusive,
|
|||
|
and if it must be extended, then the table is released and will be
|
|||
|
acquired exclusive on return.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the Restart Table in which an index is to
|
|||
|
be allocated.
|
|||
|
|
|||
|
Index - The index to be allocated.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
The table entry allocated.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PULONG Entry;
|
|||
|
PULONG LastEntry;
|
|||
|
|
|||
|
PRESTART_TABLE Table;
|
|||
|
KLOCK_QUEUE_HANDLE LockHandle;
|
|||
|
|
|||
|
ULONG ThisIndex;
|
|||
|
ULONG LastIndex;
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsAllocateRestartTableFromIndex\n") );
|
|||
|
DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
|
|||
|
DebugTrace( 0, Dbg, ("Index = %08lx\n", Index) );
|
|||
|
|
|||
|
Table = TablePointer->Table;
|
|||
|
ASSERT_RESTART_TABLE(Table);
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the spin lock to synchronize the allocation.
|
|||
|
//
|
|||
|
|
|||
|
KeAcquireInStackQueuedSpinLock( &TablePointer->SpinLock, &LockHandle );
|
|||
|
|
|||
|
//
|
|||
|
// If the entry is not in the table, we will have to extend the table.
|
|||
|
//
|
|||
|
|
|||
|
if (!IsRestartIndexWithinTable( TablePointer, Index )) {
|
|||
|
|
|||
|
ULONG TableSize;
|
|||
|
ULONG BytesToIndex;
|
|||
|
ULONG AddEntries;
|
|||
|
|
|||
|
//
|
|||
|
// We extend the size by computing the number of entries
|
|||
|
// between the existing size and the desired index and
|
|||
|
// adding 1 to that.
|
|||
|
//
|
|||
|
|
|||
|
TableSize = SizeOfRestartTable( TablePointer );;
|
|||
|
BytesToIndex = Index - TableSize;
|
|||
|
|
|||
|
AddEntries = BytesToIndex / Table->EntrySize + 1;
|
|||
|
|
|||
|
//
|
|||
|
// There should always be an integral number of entries being added.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( BytesToIndex % Table->EntrySize == 0 );
|
|||
|
|
|||
|
//
|
|||
|
// First release the spin lock and the table resource, and get
|
|||
|
// the resource exclusive.
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseInStackQueuedSpinLock( &LockHandle );
|
|||
|
NtfsReleaseRestartTable( TablePointer );
|
|||
|
NtfsAcquireExclusiveRestartTable( TablePointer, TRUE );
|
|||
|
|
|||
|
//
|
|||
|
// Now extend the table. Note that if this routine raises, we have
|
|||
|
// nothing to release.
|
|||
|
//
|
|||
|
|
|||
|
NtfsExtendRestartTable( TablePointer,
|
|||
|
AddEntries,
|
|||
|
TableSize );
|
|||
|
|
|||
|
Table = TablePointer->Table;
|
|||
|
ASSERT_RESTART_TABLE(Table);
|
|||
|
|
|||
|
//
|
|||
|
// Now get the spin lock again and proceed.
|
|||
|
//
|
|||
|
|
|||
|
KeAcquireInStackQueuedSpinLock( &TablePointer->SpinLock, &LockHandle );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now see if the entry is already allocated, and just return if it is.
|
|||
|
//
|
|||
|
|
|||
|
Entry = (PULONG)GetRestartEntryFromIndex( TablePointer, Index );
|
|||
|
|
|||
|
if (!IsRestartTableEntryAllocated( Entry )) {
|
|||
|
|
|||
|
//
|
|||
|
// We now have to walk through the table, looking for the entry
|
|||
|
// we're interested in and the previous entry. Start by looking at the
|
|||
|
// first entry.
|
|||
|
//
|
|||
|
|
|||
|
ThisIndex = Table->FirstFree;
|
|||
|
|
|||
|
//
|
|||
|
// Get the Entry from the list.
|
|||
|
//
|
|||
|
|
|||
|
Entry = (PULONG) GetRestartEntryFromIndex( TablePointer, ThisIndex );
|
|||
|
|
|||
|
//
|
|||
|
// If this is a match, then we pull it out of the list and are done.
|
|||
|
//
|
|||
|
|
|||
|
if (ThisIndex == Index) {
|
|||
|
|
|||
|
//
|
|||
|
// Dequeue this entry.
|
|||
|
//
|
|||
|
|
|||
|
Table->FirstFree = *Entry;
|
|||
|
ASSERT( Table->FirstFree != RESTART_ENTRY_ALLOCATED );
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise we need to walk through the list looking for the
|
|||
|
// predecessor of our entry.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
while (TRUE) {
|
|||
|
|
|||
|
//
|
|||
|
// Remember the entry just found.
|
|||
|
//
|
|||
|
|
|||
|
LastIndex = ThisIndex;
|
|||
|
LastEntry = Entry;
|
|||
|
|
|||
|
//
|
|||
|
// We should never run out of entries.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( *LastEntry != 0 );
|
|||
|
|
|||
|
//
|
|||
|
// Lookup up the next entry in the list.
|
|||
|
//
|
|||
|
|
|||
|
ThisIndex = *LastEntry;
|
|||
|
Entry = (PULONG) GetRestartEntryFromIndex( TablePointer, ThisIndex );
|
|||
|
|
|||
|
//
|
|||
|
// If this is our match we are done.
|
|||
|
//
|
|||
|
|
|||
|
if (ThisIndex == Index) {
|
|||
|
|
|||
|
//
|
|||
|
// Dequeue this entry.
|
|||
|
//
|
|||
|
|
|||
|
*LastEntry = *Entry;
|
|||
|
|
|||
|
//
|
|||
|
// If this was the last entry, we update that in the
|
|||
|
// table as well.
|
|||
|
//
|
|||
|
|
|||
|
if (Table->LastFree == ThisIndex) {
|
|||
|
|
|||
|
Table->LastFree = LastIndex;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the list is now empty, we fix the LastFree as well.
|
|||
|
//
|
|||
|
|
|||
|
if (Table->FirstFree == 0) {
|
|||
|
|
|||
|
Table->LastFree = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Zero this entry. Then show that this is allocated and increment the
|
|||
|
// allocated count.
|
|||
|
//
|
|||
|
|
|||
|
RtlZeroMemory( Entry, Table->EntrySize );
|
|||
|
*Entry = RESTART_ENTRY_ALLOCATED;
|
|||
|
|
|||
|
Table->NumberAllocated += 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Now just release the spin lock before returning.
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseInStackQueuedSpinLock( &LockHandle );
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsAllocateRestartTableFromIndex -> %08lx\n", Entry) );
|
|||
|
|
|||
|
return (PVOID)Entry;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsFreeRestartTableIndex (
|
|||
|
IN PRESTART_POINTERS TablePointer,
|
|||
|
IN ULONG Index
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine frees a previously allocated index in a Restart Table.
|
|||
|
If the index is before FreeGoal for the table, it is simply deallocated to
|
|||
|
the front of the list for immediate reuse. If the index is beyond
|
|||
|
FreeGoal, then it is deallocated to the end of the list, to facilitate
|
|||
|
truncation of the list in the event that all of the entries beyond
|
|||
|
FreeGoal are freed. However, this routine does not automatically
|
|||
|
truncate the list, as this would cause too much overhead. The list
|
|||
|
is checked during periodic checkpoint processing.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the Restart Table to which the index is to be
|
|||
|
deallocated.
|
|||
|
|
|||
|
Index - The index being deallocated.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PRESTART_TABLE Table;
|
|||
|
PULONG Entry, OldLastEntry;
|
|||
|
KLOCK_QUEUE_HANDLE LockHandle;
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsFreeRestartTableIndex:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
|
|||
|
DebugTrace( 0, Dbg, ("Index = %08lx\n", Index) );
|
|||
|
|
|||
|
//
|
|||
|
// Get pointers to table and the entry we are freeing.
|
|||
|
//
|
|||
|
|
|||
|
Table = TablePointer->Table;
|
|||
|
ASSERT_RESTART_TABLE(Table);
|
|||
|
|
|||
|
ASSERT( (Table->FirstFree == 0) ||
|
|||
|
((Table->FirstFree >= 0x18) &&
|
|||
|
(((Table->FirstFree - 0x18) % Table->EntrySize) == 0)) );
|
|||
|
|
|||
|
ASSERT( (Index >= 0x18) &&
|
|||
|
((Index - 0x18) % Table->EntrySize) == 0 );
|
|||
|
|
|||
|
Entry = GetRestartEntryFromIndex( TablePointer, Index );
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the spinlock to synchronize the allocation.
|
|||
|
//
|
|||
|
|
|||
|
KeAcquireInStackQueuedSpinLock( &TablePointer->SpinLock, &LockHandle );
|
|||
|
|
|||
|
//
|
|||
|
// If the index is before FreeGoal, then do a normal deallocation at
|
|||
|
// the front of the list.
|
|||
|
//
|
|||
|
|
|||
|
if (Index < Table->FreeGoal) {
|
|||
|
|
|||
|
*Entry = Table->FirstFree;
|
|||
|
|
|||
|
ASSERT( Index != RESTART_ENTRY_ALLOCATED );
|
|||
|
|
|||
|
Table->FirstFree = Index;
|
|||
|
if (Table->LastFree == 0) {
|
|||
|
Table->LastFree = Index;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise we will deallocate this guy to the end of the list.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (Table->LastFree != 0) {
|
|||
|
OldLastEntry = GetRestartEntryFromIndex( TablePointer,
|
|||
|
Table->LastFree );
|
|||
|
*OldLastEntry = Index;
|
|||
|
} else {
|
|||
|
|
|||
|
ASSERT( Index != RESTART_ENTRY_ALLOCATED );
|
|||
|
|
|||
|
Table->FirstFree = Index;
|
|||
|
}
|
|||
|
Table->LastFree = Index;
|
|||
|
*Entry = 0;
|
|||
|
}
|
|||
|
|
|||
|
ASSERT( Table->NumberAllocated != 0 );
|
|||
|
Table->NumberAllocated -= 1;
|
|||
|
|
|||
|
//
|
|||
|
// Now just release the spin lock before returning.
|
|||
|
//
|
|||
|
|
|||
|
KeReleaseInStackQueuedSpinLock( &LockHandle );
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsFreeRestartTableIndex -> VOID\n") );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
NtfsGetFirstRestartTable (
|
|||
|
IN PRESTART_POINTERS TablePointer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine returns the first allocated entry from a Restart Table.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the respective Restart Table Pointers structure.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Pointer to the first entry, or NULL if none are allocated.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PCHAR Entry;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// If we know the table is empty, we can return immediately.
|
|||
|
//
|
|||
|
|
|||
|
if (IsRestartTableEmpty( TablePointer )) {
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Otherwise point to the first table entry.
|
|||
|
//
|
|||
|
|
|||
|
Entry = (PCHAR)(TablePointer->Table + 1);
|
|||
|
|
|||
|
//
|
|||
|
// Loop until we hit the first one allocated, or the end of the list.
|
|||
|
//
|
|||
|
|
|||
|
while ((ULONG)(Entry - (PCHAR)TablePointer->Table) <
|
|||
|
SizeOfRestartTable(TablePointer)) {
|
|||
|
|
|||
|
if (IsRestartTableEntryAllocated(Entry)) {
|
|||
|
return (PVOID)Entry;
|
|||
|
}
|
|||
|
|
|||
|
Entry += TablePointer->Table->EntrySize;
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
PVOID
|
|||
|
NtfsGetNextRestartTable (
|
|||
|
IN PRESTART_POINTERS TablePointer,
|
|||
|
IN PVOID Current
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine returns the next allocated entry from a Restart Table.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
TablePointer - Pointer to the respective Restart Table Pointers structure.
|
|||
|
|
|||
|
Current - Current entry pointer.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Pointer to the next entry, or NULL if none are allocated.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
|
|||
|
{
|
|||
|
PCHAR Entry = (PCHAR)Current;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Point to the next entry.
|
|||
|
//
|
|||
|
|
|||
|
Entry += TablePointer->Table->EntrySize;
|
|||
|
|
|||
|
//
|
|||
|
// Loop until we hit the first one allocated, or the end of the list.
|
|||
|
//
|
|||
|
|
|||
|
while ((ULONG)(Entry - (PCHAR)TablePointer->Table) <
|
|||
|
SizeOfRestartTable(TablePointer)) {
|
|||
|
|
|||
|
if (IsRestartTableEntryAllocated(Entry)) {
|
|||
|
return (PVOID)Entry;
|
|||
|
}
|
|||
|
|
|||
|
Entry += TablePointer->Table->EntrySize;
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsUpdateOatVersion (
|
|||
|
IN PVCB Vcb,
|
|||
|
IN ULONG NewRestartVersion
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called when we are switching the restart version for a volume. This can happen
|
|||
|
either after a clean checkpoint or at mount when we encounter a restart area with a non-default
|
|||
|
version number.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Vcb - Pointer to the Vcb for the volume.
|
|||
|
|
|||
|
NewRestartVersion - Restart version to start using for this volume.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PRESTART_POINTERS NewTable = NULL;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("NtfsUpdateOatVersion\n") );
|
|||
|
|
|||
|
ASSERT( (Vcb->RestartVersion != NewRestartVersion) || (Vcb->OnDiskOat == NULL) );
|
|||
|
|
|||
|
//
|
|||
|
// Use a try finally to facilitate cleanup.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
if (NewRestartVersion == 0) {
|
|||
|
|
|||
|
//
|
|||
|
// If we are moving to version 0 then allocate a new table and
|
|||
|
// initialize it with the initial number of entries.
|
|||
|
//
|
|||
|
|
|||
|
NewTable = NtfsAllocatePool( NonPagedPool, sizeof( RESTART_POINTERS ));
|
|||
|
NtfsInitializeRestartTable( sizeof( OPEN_ATTRIBUTE_ENTRY_V0 ),
|
|||
|
INITIAL_NUMBER_ATTRIBUTES,
|
|||
|
NewTable );
|
|||
|
|
|||
|
Vcb->RestartVersion = 0;
|
|||
|
Vcb->OatEntrySize = SIZEOF_OPEN_ATTRIBUTE_ENTRY_V0;
|
|||
|
Vcb->OnDiskOat = NewTable;
|
|||
|
NewTable = NULL;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (Vcb->OnDiskOat != NULL) {
|
|||
|
|
|||
|
NtfsFreeRestartTable( Vcb->OnDiskOat );
|
|||
|
NtfsFreePool( Vcb->OnDiskOat );
|
|||
|
}
|
|||
|
|
|||
|
Vcb->OnDiskOat = &Vcb->OpenAttributeTable;
|
|||
|
Vcb->RestartVersion = 1;
|
|||
|
Vcb->OatEntrySize = sizeof( OPEN_ATTRIBUTE_ENTRY );
|
|||
|
}
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
DebugUnwind( NtfsUpdateOatVersion );
|
|||
|
|
|||
|
if (NewTable != NULL) {
|
|||
|
|
|||
|
NtfsFreePool( NewTable );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("NtfsUpdateOatVersion -> VOID\n") );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Internal support routine
|
|||
|
//
|
|||
|
|
|||
|
VOID
|
|||
|
DirtyPageRoutine (
|
|||
|
IN PFILE_OBJECT FileObject,
|
|||
|
IN PLARGE_INTEGER FileOffset,
|
|||
|
IN ULONG Length,
|
|||
|
IN PLSN OldestLsn,
|
|||
|
IN PLSN NewestLsn,
|
|||
|
IN PVOID Context1,
|
|||
|
IN PVOID Context2
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is used as the call back routine for retrieving dirty pages
|
|||
|
from the Cache Manager. It adds them to the Dirty Table list whose
|
|||
|
pointer is pointed to by the Context parameter.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
FileObject - Pointer to the file object which has the dirty page
|
|||
|
|
|||
|
FileOffset - File offset for start of dirty page
|
|||
|
|
|||
|
Length - Length recorded for the dirty page
|
|||
|
|
|||
|
OldestLsn - Oldest Lsn of an update not written through stored for that page
|
|||
|
(Can be zero if set dirty in paths that don't use lsns)
|
|||
|
|
|||
|
Context1 - IrpContext
|
|||
|
|
|||
|
Context2 - Pointer to the pointer to the Restart Table
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PDIRTY_PAGE_ENTRY PageEntry;
|
|||
|
PDIRTY_PAGE_CONTEXT DirtyPageContext = (PDIRTY_PAGE_CONTEXT)Context2;
|
|||
|
PRESTART_POINTERS DirtyPageTable = DirtyPageContext->DirtyPageTable;
|
|||
|
PSCB_NONPAGED NonpagedScb;
|
|||
|
ULONG PageIndex;
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("DirtyPageRoutine:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("FileObject = %08lx\n", FileObject) );
|
|||
|
DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", *FileOffset) );
|
|||
|
DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
|
|||
|
DebugTrace( 0, Dbg, ("OldestLsn = %016I64x\n", *OldestLsn) );
|
|||
|
DebugTrace( 0, Dbg, ("Context2 = %08lx\n", Context2) );
|
|||
|
|
|||
|
//
|
|||
|
// Get the Vcb out of the file object.
|
|||
|
//
|
|||
|
|
|||
|
NonpagedScb = CONTAINING_RECORD( FileObject->SectionObjectPointer,
|
|||
|
SCB_NONPAGED,
|
|||
|
SegmentObject );
|
|||
|
|
|||
|
//
|
|||
|
// We noop this call if the open attribute entry for this Scb is 0. We assume
|
|||
|
// there was a clean volume checkpoint which cleared this field.
|
|||
|
//
|
|||
|
|
|||
|
if (NonpagedScb->OpenAttributeTableIndex == 0 ) {
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("DirtyPageRoutine -> VOID\n") );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Check for an overrun in the table and stop processing in that case
|
|||
|
// The restart table format can't accomodate tables greater than 64k in size
|
|||
|
// due to the ushort used for the attribute index
|
|||
|
//
|
|||
|
|
|||
|
if (AllocatedSizeOfRestartTable( DirtyPageTable ) > MAX_RESTART_TABLE_SIZE ){
|
|||
|
|
|||
|
DirtyPageContext->Overflow = TRUE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Get a pointer to the entry we just allocated.
|
|||
|
//
|
|||
|
|
|||
|
PageIndex = NtfsAllocateRestartTableIndex( DirtyPageTable, TRUE );
|
|||
|
PageEntry = GetRestartEntryFromIndex( DirtyPageTable, PageIndex );
|
|||
|
|
|||
|
//
|
|||
|
// Now fill in the Dirty Page Entry, except for the Lcns, because
|
|||
|
// we are not allowed to take page faults now.
|
|||
|
// Use the index for the in-memory table now. We will update
|
|||
|
// this to the on-disk index back in CheckpointVolume.
|
|||
|
//
|
|||
|
|
|||
|
PageEntry->TargetAttribute = NonpagedScb->OpenAttributeTableIndex;
|
|||
|
ASSERT( NonpagedScb->OnDiskOatIndex != 0 );
|
|||
|
|
|||
|
PageEntry->LengthOfTransfer = Length;
|
|||
|
|
|||
|
//
|
|||
|
// Put the Vcn (FileOffset) and OldestLsn into the page at this point. Note
|
|||
|
// we don't want to put an Lsn into the table which is older than the current
|
|||
|
// BaseLsn. Store it here for now and we will fix it up when we process the
|
|||
|
// DiryPage table back in the checkpoint code.
|
|||
|
//
|
|||
|
|
|||
|
if (NonpagedScb->Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) PageEntry)->Vcn = FileOffset->QuadPart;
|
|||
|
((PDIRTY_PAGE_ENTRY_V0) PageEntry)->OldestLsn = *OldestLsn;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
PageEntry->Vcn = FileOffset->QuadPart;
|
|||
|
PageEntry->OldestLsn = *OldestLsn;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Update the oldest lsn info if this is the new oldest lsn
|
|||
|
//
|
|||
|
|
|||
|
if ((OldestLsn->QuadPart != 0) &&
|
|||
|
(OldestLsn->QuadPart < DirtyPageContext->OldestLsn.QuadPart)) {
|
|||
|
|
|||
|
if (DirtyPageContext->OldestFileObject != NULL) {
|
|||
|
ObDereferenceObject( DirtyPageContext->OldestFileObject );
|
|||
|
}
|
|||
|
|
|||
|
DirtyPageContext->DirtyPageIndex = PageIndex;
|
|||
|
DirtyPageContext->OldestFileObject = FileObject;
|
|||
|
DirtyPageContext->OldestLsn.QuadPart = OldestLsn->QuadPart;
|
|||
|
|
|||
|
ObReferenceObject( FileObject );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("DirtyPageRoutine -> VOID\n") );
|
|||
|
return;
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER( Context1 );
|
|||
|
UNREFERENCED_PARAMETER( NewestLsn );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Internal support routine
|
|||
|
//
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
LookupLcns (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PSCB Scb,
|
|||
|
IN VCN Vcn,
|
|||
|
IN ULONG ClusterCount,
|
|||
|
IN BOOLEAN MustBeAllocated,
|
|||
|
OUT PLCN_UNALIGNED FirstLcn
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine looks up the Lcns for a range of Vcns, and stores them in
|
|||
|
an output array. One Lcn is stored for each Vcn in the range, even
|
|||
|
if the Lcns are contiguous.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Scb - Scb for stream on which lookup should occur.
|
|||
|
|
|||
|
Vcn - Start of range of Vcns to look up.
|
|||
|
|
|||
|
ClusterCount - Number of Vcns to look up.
|
|||
|
|
|||
|
MustBeAllocated - FALSE - if need not be allocated, and should check Mcb only
|
|||
|
TRUE - if it must be allocated as far as caller knows (i.e.,
|
|||
|
NtfsLookupAllocation also has checks)
|
|||
|
|
|||
|
FirstLcn - Pointer to storage for first Lcn. The caller must guarantee
|
|||
|
that there is enough space to store ClusterCount Lcns.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
BOOLEAN - TRUE if we found the clusters, FALSE otherwise.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
BOOLEAN Allocated;
|
|||
|
LONGLONG Clusters;
|
|||
|
LCN Lcn;
|
|||
|
ULONG i;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
DebugTrace( +1, Dbg, ("LookupLcns:\n") );
|
|||
|
DebugTrace( 0, Dbg, ("Scb = %08l\n", Scb) );
|
|||
|
DebugTrace( 0, Dbg, ("Vcn = %016I64x\n", Vcn) );
|
|||
|
DebugTrace( 0, Dbg, ("ClusterCount = %08l\n", ClusterCount) );
|
|||
|
DebugTrace( 0, Dbg, ("FirstLcn = %08lx\n", FirstLcn) );
|
|||
|
|
|||
|
//
|
|||
|
// Loop until we have looked up all of the clusters
|
|||
|
//
|
|||
|
|
|||
|
while (ClusterCount != 0) {
|
|||
|
|
|||
|
if (MustBeAllocated) {
|
|||
|
|
|||
|
//
|
|||
|
// Lookup the next run.
|
|||
|
//
|
|||
|
|
|||
|
Allocated = NtfsLookupAllocation( IrpContext,
|
|||
|
Scb,
|
|||
|
Vcn,
|
|||
|
&Lcn,
|
|||
|
&Clusters,
|
|||
|
NULL,
|
|||
|
NULL );
|
|||
|
|
|||
|
ASSERT( Lcn != 0 );
|
|||
|
|
|||
|
//
|
|||
|
// Raise if this case not met. Otherwise we could walk off the end
|
|||
|
// of the LCN array.
|
|||
|
//
|
|||
|
|
|||
|
if (!Allocated) {
|
|||
|
|
|||
|
return FALSE;
|
|||
|
|
|||
|
} else if (Lcn == 0) {
|
|||
|
|
|||
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Allocated = NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, &Lcn, &Clusters, NULL, NULL, NULL, NULL );
|
|||
|
|
|||
|
//
|
|||
|
// If we are off the end of the Mcb, then set up to just return
|
|||
|
// Li0 for as many Lcns as are being looked up.
|
|||
|
//
|
|||
|
|
|||
|
if (!Allocated ||
|
|||
|
(Lcn == UNUSED_LCN)) {
|
|||
|
Lcn = 0;
|
|||
|
Clusters = ClusterCount;
|
|||
|
Allocated = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we got as many clusters as we were looking for, then just
|
|||
|
// take the number we were looking for.
|
|||
|
//
|
|||
|
|
|||
|
if (Clusters > ClusterCount) {
|
|||
|
|
|||
|
Clusters = ClusterCount;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Fill in the Lcns in the header.
|
|||
|
//
|
|||
|
|
|||
|
for (i = 0; i < (ULONG)Clusters; i++) {
|
|||
|
|
|||
|
*(FirstLcn++) = Lcn;
|
|||
|
|
|||
|
if (Allocated) {
|
|||
|
Lcn = Lcn + 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Adjust loop variables for the number Lcns we just received.
|
|||
|
//
|
|||
|
|
|||
|
Vcn = Vcn + Clusters;
|
|||
|
ClusterCount -= (ULONG)Clusters;
|
|||
|
}
|
|||
|
|
|||
|
DebugTrace( -1, Dbg, ("LookupLcns -> VOID\n") );
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
InitializeNewTable (
|
|||
|
IN ULONG EntrySize,
|
|||
|
IN ULONG NumberEntries,
|
|||
|
OUT PRESTART_POINTERS TablePointer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to allocate and initialize a new table when the
|
|||
|
associated Restart Table is being allocated or extended.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
EntrySize - Size of the table entries, in bytes.
|
|||
|
|
|||
|
NumberEntries - Number of entries to allocate for the table.
|
|||
|
|
|||
|
TablePointer - Returns a pointer to the table.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PRESTART_TABLE Table;
|
|||
|
PULONG Entry;
|
|||
|
ULONG Size;
|
|||
|
ULONG Offset;
|
|||
|
|
|||
|
ASSERT( EntrySize != 0 );
|
|||
|
|
|||
|
//
|
|||
|
// Calculate size of table to allocate.
|
|||
|
//
|
|||
|
|
|||
|
Size = EntrySize * NumberEntries + sizeof(RESTART_TABLE);
|
|||
|
|
|||
|
//
|
|||
|
// Allocate and zero out the table.
|
|||
|
//
|
|||
|
|
|||
|
Table =
|
|||
|
TablePointer->Table = NtfsAllocatePool( NonPagedPool, Size );
|
|||
|
|
|||
|
RtlZeroMemory( Table, Size );
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the table header.
|
|||
|
//
|
|||
|
|
|||
|
Table->EntrySize = (USHORT)EntrySize;
|
|||
|
Table->NumberEntries = (USHORT)NumberEntries;
|
|||
|
Table->FreeGoal = MAXULONG;
|
|||
|
Table->FirstFree = sizeof( RESTART_TABLE );
|
|||
|
Table->LastFree = Table->FirstFree + (NumberEntries - 1) * EntrySize;
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the free list.
|
|||
|
//
|
|||
|
|
|||
|
for (Entry = (PULONG)(Table + 1), Offset = sizeof(RESTART_TABLE) + EntrySize;
|
|||
|
Entry < (PULONG)((PCHAR)Table + Table->LastFree);
|
|||
|
Entry = (PULONG)((PCHAR)Entry + EntrySize), Offset += EntrySize) {
|
|||
|
|
|||
|
*Entry = Offset;
|
|||
|
}
|
|||
|
|
|||
|
ASSERT_RESTART_TABLE(Table);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsFreeRecentlyDeallocated (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PVCB Vcb,
|
|||
|
IN PLSN BaseLsn,
|
|||
|
IN ULONG CleanVolume
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Free up recently deallocated clusters for reuse
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IrpContext -
|
|||
|
|
|||
|
Vcb - volume to clean up
|
|||
|
|
|||
|
BaseLsn - the lsn we're up to now in the logfile, used to determine what can be freed
|
|||
|
and the new threshold for future frees
|
|||
|
|
|||
|
CleanVolume - if true the volume is being clean checkpointed and all the clusters can be freed
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PDEALLOCATED_CLUSTERS Clusters;
|
|||
|
BOOLEAN RemovedClusters = FALSE;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Quick exit if the list is empty
|
|||
|
//
|
|||
|
|
|||
|
if (IsListEmpty( &Vcb->DeallocatedClusterListHead ) || (Vcb->BitmapScb == NULL)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
|
|||
|
|
|||
|
Clusters = (PDEALLOCATED_CLUSTERS)Vcb->DeallocatedClusterListHead.Blink;
|
|||
|
|
|||
|
//
|
|||
|
// Now we want to check if we can release any of the clusters in the
|
|||
|
// deallocated cluster arrays. We know we can look at the
|
|||
|
// fields in the PriorDeallocatedClusters structure because they
|
|||
|
// are never modified in the running system.
|
|||
|
//
|
|||
|
// We will continue from the oldest in the list list until
|
|||
|
//
|
|||
|
// 1) there are no more dealloc lists
|
|||
|
// 2) there are no clusters in the dealloc list (it must be the only one at this point)
|
|||
|
// 3) the lsn == 0 and we're dirty which means we're at the front
|
|||
|
// 4) the lsn is newer in deallocated cluster list
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
while ((!IsListEmpty( &Vcb->DeallocatedClusterListHead )) &&
|
|||
|
(((Clusters->Lsn.QuadPart != 0) && (BaseLsn->QuadPart > Clusters->Lsn.QuadPart)) ||
|
|||
|
CleanVolume)) {
|
|||
|
|
|||
|
RemovedClusters = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// For all deallocated during clean checkpoints and non-most recent
|
|||
|
// ones during fuzzy ones:
|
|||
|
// Remove all of the mappings in the Mcb. Protect this with
|
|||
|
// a try-except.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
try {
|
|||
|
ULONG i;
|
|||
|
ULONGLONG StartingVcn;
|
|||
|
ULONGLONG StartingLcn;
|
|||
|
ULONGLONG ClusterCount;
|
|||
|
|
|||
|
if (Clusters->ClusterCount > 0) {
|
|||
|
|
|||
|
for (i = 0; FsRtlGetNextLargeMcbEntry( &Clusters->Mcb, i, &StartingVcn, &StartingLcn, &ClusterCount ); i += 1) {
|
|||
|
|
|||
|
if (StartingVcn == StartingLcn) {
|
|||
|
|
|||
|
if (NtfsAddCachedRun( IrpContext,
|
|||
|
Vcb,
|
|||
|
StartingLcn,
|
|||
|
ClusterCount,
|
|||
|
RunStateFree ) <= 0) break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
PDEALLOCATED_CLUSTERS NextClusters = (PDEALLOCATED_CLUSTERS)Clusters->Link.Blink;
|
|||
|
|
|||
|
//
|
|||
|
// We are committed to freeing the clusters out of the PriorDeallocatedClusters
|
|||
|
// in any case.
|
|||
|
//
|
|||
|
|
|||
|
Vcb->DeallocatedClusters -= Clusters->ClusterCount;
|
|||
|
|
|||
|
//
|
|||
|
// Move this cluster list out of the vcb
|
|||
|
//
|
|||
|
|
|||
|
RemoveEntryList( &Clusters->Link );
|
|||
|
|
|||
|
//
|
|||
|
// delete dynamic clusters lists / reset static ones
|
|||
|
//
|
|||
|
|
|||
|
if ((Clusters != &Vcb->DeallocatedClusters1) && (Clusters != &Vcb->DeallocatedClusters2 )) {
|
|||
|
|
|||
|
FsRtlUninitializeLargeMcb( &Clusters->Mcb );
|
|||
|
NtfsFreePool( Clusters );
|
|||
|
} else {
|
|||
|
Clusters->Link.Flink = NULL;
|
|||
|
Clusters->ClusterCount = 0;
|
|||
|
FsRtlResetLargeMcb( &Clusters->Mcb, TRUE );
|
|||
|
}
|
|||
|
ASSERT( Vcb->DeallocatedClusters >= 0 );
|
|||
|
|
|||
|
Clusters = NextClusters;
|
|||
|
}
|
|||
|
|
|||
|
} except( NtfsCatchOutOfMemoryExceptionFilter( IrpContext, GetExceptionInformation() )) {
|
|||
|
|
|||
|
//
|
|||
|
// Keep going even if out of memory
|
|||
|
//
|
|||
|
|
|||
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we removed any clusters on a fuzzy checkpoint lets make a new active one so
|
|||
|
// the current active one can be cleaned up eventually
|
|||
|
// On a clean checkpoint if we removed all the nodes add a blank one back
|
|||
|
//
|
|||
|
|
|||
|
if (!CleanVolume) {
|
|||
|
|
|||
|
ASSERT( !IsListEmpty( &Vcb->DeallocatedClusterListHead ) );
|
|||
|
|
|||
|
if (RemovedClusters && (Clusters->ClusterCount > 0)) {
|
|||
|
Clusters = NtfsGetDeallocatedClusters( IrpContext, Vcb );
|
|||
|
}
|
|||
|
|
|||
|
} else if (IsListEmpty( &Vcb->DeallocatedClusterListHead )) {
|
|||
|
|
|||
|
ASSERT( Vcb->DeallocatedClusters1.Link.Flink == NULL );
|
|||
|
|
|||
|
Vcb->DeallocatedClusters1.Lsn.QuadPart = 0;
|
|||
|
InsertHeadList( &Vcb->DeallocatedClusterListHead, &Vcb->DeallocatedClusters1.Link );
|
|||
|
}
|
|||
|
|
|||
|
} finally {
|
|||
|
|
|||
|
NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsCleanupFailedTransaction (
|
|||
|
IN PIRP_CONTEXT IrpContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called to cleanup the IrpContext and free structures
|
|||
|
in the event a transaction fails to commit or abort.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PUSN_FCB ThisUsn;
|
|||
|
PUSN_FCB LastUsn;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
//
|
|||
|
// Clear the flags indicating a transaction is underway.
|
|||
|
//
|
|||
|
|
|||
|
ClearFlag( IrpContext->Flags,
|
|||
|
IRP_CONTEXT_FLAG_WROTE_LOG | IRP_CONTEXT_FLAG_RAISED_STATUS | IRP_CONTEXT_FLAG_MODIFIED_BITMAP );
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the recently deallocated queue is empty.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
if (!IsListEmpty( &IrpContext->RecentlyDeallocatedQueue )) {
|
|||
|
|
|||
|
NtfsDeallocateRecordsComplete( IrpContext );
|
|||
|
}
|
|||
|
|
|||
|
} except (FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
|
|||
|
EXCEPTION_EXECUTE_HANDLER :
|
|||
|
EXCEPTION_CONTINUE_SEARCH) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Show that we haven't deallocated any clusters.
|
|||
|
//
|
|||
|
|
|||
|
IrpContext->DeallocatedClusters = 0;
|
|||
|
IrpContext->FreeClusterChange = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Don't rollback any size changes.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
NtfsUpdateScbSnapshots( IrpContext );
|
|||
|
|
|||
|
} except (FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
|
|||
|
EXCEPTION_EXECUTE_HANDLER :
|
|||
|
EXCEPTION_CONTINUE_SEARCH) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the last restart area is zeroed.
|
|||
|
//
|
|||
|
|
|||
|
IrpContext->LastRestartArea.QuadPart = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Pull the Usn Fcb fields.
|
|||
|
//
|
|||
|
|
|||
|
ThisUsn = &IrpContext->Usn;
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
if (ThisUsn->CurrentUsnFcb != NULL) {
|
|||
|
|
|||
|
PFCB UsnFcb = ThisUsn->CurrentUsnFcb;
|
|||
|
|
|||
|
NtfsLockFcb( IrpContext, UsnFcb );
|
|||
|
|
|||
|
//
|
|||
|
// If any rename flags are part of the new reasons then
|
|||
|
// make sure to look the name up again.
|
|||
|
//
|
|||
|
|
|||
|
if (FlagOn( ThisUsn->NewReasons,
|
|||
|
USN_REASON_RENAME_NEW_NAME | USN_REASON_RENAME_OLD_NAME )) {
|
|||
|
|
|||
|
ClearFlag( UsnFcb->FcbState, FCB_STATE_VALID_USN_NAME );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now restore the reason and source info fields.
|
|||
|
//
|
|||
|
|
|||
|
ClearFlag( UsnFcb->FcbUsnRecord->UsnRecord.Reason,
|
|||
|
ThisUsn->NewReasons );
|
|||
|
if (UsnFcb->FcbUsnRecord->UsnRecord.Reason == 0) {
|
|||
|
|
|||
|
UsnFcb->FcbUsnRecord->UsnRecord.SourceInfo = 0;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
SetFlag( UsnFcb->FcbUsnRecord->UsnRecord.SourceInfo,
|
|||
|
ThisUsn->RemovedSourceInfo );
|
|||
|
}
|
|||
|
|
|||
|
NtfsUnlockFcb( IrpContext, UsnFcb );
|
|||
|
|
|||
|
//
|
|||
|
// Zero out the structure.
|
|||
|
//
|
|||
|
|
|||
|
ThisUsn->CurrentUsnFcb = NULL;
|
|||
|
ThisUsn->NewReasons = 0;
|
|||
|
ThisUsn->RemovedSourceInfo = 0;
|
|||
|
ThisUsn->UsnFcbFlags = 0;
|
|||
|
|
|||
|
//
|
|||
|
// If not the first pass through the loop then update
|
|||
|
// the last usn structure with what we point to here.
|
|||
|
//
|
|||
|
|
|||
|
if (ThisUsn != &IrpContext->Usn) {
|
|||
|
|
|||
|
LastUsn->NextUsnFcb = ThisUsn->NextUsnFcb;
|
|||
|
NtfsFreePool( ThisUsn );
|
|||
|
ThisUsn = LastUsn;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (ThisUsn->NextUsnFcb == NULL) { break; }
|
|||
|
|
|||
|
LastUsn = ThisUsn;
|
|||
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|||
|
|
|||
|
} while (TRUE);
|
|||
|
|
|||
|
} except (FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
|
|||
|
EXCEPTION_EXECUTE_HANDLER :
|
|||
|
EXCEPTION_CONTINUE_SEARCH) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Don't wake any waiters for this failed operation.
|
|||
|
//
|
|||
|
|
|||
|
try {
|
|||
|
|
|||
|
if (IrpContext->CheckNewLength != NULL) {
|
|||
|
|
|||
|
NtfsProcessNewLengthQueue( IrpContext, TRUE );
|
|||
|
}
|
|||
|
|
|||
|
} except (FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
|
|||
|
EXCEPTION_EXECUTE_HANDLER :
|
|||
|
EXCEPTION_CONTINUE_SEARCH) {
|
|||
|
|
|||
|
NOTHING;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Remove this from the transaction table if present.
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->TransactionId != 0) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( &IrpContext->Vcb->TransactionTable,
|
|||
|
TRUE );
|
|||
|
|
|||
|
NtfsFreeRestartTableIndex( &IrpContext->Vcb->TransactionTable,
|
|||
|
IrpContext->TransactionId );
|
|||
|
|
|||
|
//
|
|||
|
// Notify any waiters if there are no more transactions
|
|||
|
//
|
|||
|
|
|||
|
if (IrpContext->Vcb->TransactionTable.Table->NumberAllocated == 0) {
|
|||
|
|
|||
|
KeSetEvent( &IrpContext->Vcb->TransactionsDoneEvent, 0, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
NtfsReleaseRestartTable( &IrpContext->Vcb->TransactionTable );
|
|||
|
|
|||
|
IrpContext->TransactionId = 0;
|
|||
|
}
|
|||
|
|
|||
|
IrpContext->ExceptionStatus = STATUS_SUCCESS;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Local support routine
|
|||
|
//
|
|||
|
|
|||
|
LONG
|
|||
|
NtfsCatchOutOfMemoryExceptionFilter (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PEXCEPTION_POINTERS ExceptionPointer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Exception filter for out of memory errors. This will swallow 0xC0000009A's and let
|
|||
|
all other exceptions filter on
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IrpContext - IrpContext
|
|||
|
|
|||
|
ExceptionPointer - Pointer to the exception context.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Exception status - EXCEPTION_CONTINUE_SEARCH if we want to raise to another handler,
|
|||
|
EXCEPTION_EXECUTE_HANDLER if we plan to proceed on.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
UNREFERENCED_PARAMETER( IrpContext );
|
|||
|
|
|||
|
if (ExceptionPointer->ExceptionRecord->ExceptionCode != STATUS_INSUFFICIENT_RESOURCES) {
|
|||
|
return EXCEPTION_CONTINUE_SEARCH;
|
|||
|
}
|
|||
|
|
|||
|
return EXCEPTION_EXECUTE_HANDLER;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Local support routine
|
|||
|
//
|
|||
|
|
|||
|
LONG
|
|||
|
NtfsCheckpointExceptionFilter (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PEXCEPTION_POINTERS ExceptionPointer,
|
|||
|
IN NTSTATUS ExceptionCode
|
|||
|
)
|
|||
|
|
|||
|
{
|
|||
|
//
|
|||
|
// Swallow all expected errors if this is a dismount doing a log file full.
|
|||
|
//
|
|||
|
|
|||
|
if ((FlagOn( IrpContext->State, IRP_CONTEXT_STATE_DISMOUNT_LOG_FLUSH )) &&
|
|||
|
(FsRtlIsNtstatusExpected( ExceptionCode ))) {
|
|||
|
|
|||
|
return EXCEPTION_EXECUTE_HANDLER;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
return EXCEPTION_CONTINUE_SEARCH;
|
|||
|
}
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER( ExceptionPointer );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
NtfsFreeAttributeEntry (
|
|||
|
IN PVCB Vcb,
|
|||
|
IN POPEN_ATTRIBUTE_ENTRY AttributeEntry
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Free an attribute entry and all the connected entries in the other tables
|
|||
|
+ any memory associated with it
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IrpContext - IrpContext
|
|||
|
|
|||
|
AttributeEntry - Entry to free
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NONE
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
ULONG Index;
|
|||
|
|
|||
|
if (AttributeEntry->OatData->AttributeNamePresent) {
|
|||
|
|
|||
|
//
|
|||
|
// Delete its name, if it has one. Check that we aren't
|
|||
|
// using the hardcode $I30 name.
|
|||
|
//
|
|||
|
|
|||
|
NtfsFreeScbAttributeName( AttributeEntry->OatData->AttributeName.Buffer );
|
|||
|
|
|||
|
} else if (AttributeEntry->OatData->Overlay.Scb != NULL) {
|
|||
|
|
|||
|
AttributeEntry->OatData->Overlay.Scb->NonpagedScb->OpenAttributeTableIndex =
|
|||
|
AttributeEntry->OatData->Overlay.Scb->NonpagedScb->OnDiskOatIndex = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Get the index for the entry.
|
|||
|
//
|
|||
|
|
|||
|
Index = GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
|
|||
|
AttributeEntry );
|
|||
|
|
|||
|
if (Vcb->RestartVersion == 0) {
|
|||
|
|
|||
|
NtfsAcquireExclusiveRestartTable( Vcb->OnDiskOat, TRUE );
|
|||
|
NtfsFreeRestartTableIndex( Vcb->OnDiskOat, AttributeEntry->OatData->OnDiskAttributeIndex );
|
|||
|
NtfsReleaseRestartTable( Vcb->OnDiskOat );
|
|||
|
}
|
|||
|
|
|||
|
NtfsFreeOpenAttributeData( AttributeEntry->OatData );
|
|||
|
NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable, Index );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
ULONG
|
|||
|
NtfsCalculateNamedBytes (
|
|||
|
IN PIRP_CONTEXT IrpContext,
|
|||
|
IN PVCB Vcb
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Calculated number of named bytes necc. to hold all the open attributes
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IrpContext - IrpContext
|
|||
|
|
|||
|
Vcb -
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
Number of bytes needed to write all the names of the open attributes
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
POPEN_ATTRIBUTE_ENTRY AttributeEntry;
|
|||
|
ULONG NameBytes = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Loop to see how much we will have to allocate for attribute names.
|
|||
|
//
|
|||
|
|
|||
|
AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
|
|||
|
|
|||
|
while (AttributeEntry != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// This checks for one type of aliasing.
|
|||
|
//
|
|||
|
|
|||
|
// ASSERT( (AttributeEntry->Overlay.Scb == NULL) ||
|
|||
|
// (AttributeEntry->Overlay.Scb->OpenAttributeTableIndex ==
|
|||
|
// GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
|
|||
|
// AttributeEntry )));
|
|||
|
|
|||
|
//
|
|||
|
// Clear the DirtyPageSeen flag prior to collecting the dirty pages,
|
|||
|
// to help us figure out which Open Attribute Entries we still need.
|
|||
|
//
|
|||
|
|
|||
|
AttributeEntry->DirtyPagesSeen = FALSE;
|
|||
|
|
|||
|
if (AttributeEntry->OatData->AttributeName.Length != 0) {
|
|||
|
|
|||
|
//
|
|||
|
// Add to our name total, the size of an Attribute Entry,
|
|||
|
// which includes the size of the terminating UNICODE_NULL.
|
|||
|
//
|
|||
|
|
|||
|
NameBytes += AttributeEntry->OatData->AttributeName.Length +
|
|||
|
sizeof(ATTRIBUTE_NAME_ENTRY);
|
|||
|
}
|
|||
|
|
|||
|
AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
|
|||
|
AttributeEntry );
|
|||
|
}
|
|||
|
|
|||
|
return NameBytes;
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER( IrpContext );
|
|||
|
}
|
|||
|
|