You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2451 lines
67 KiB
2451 lines
67 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
MftSup.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the master file table management routines for Ntfs
|
|
|
|
Author:
|
|
|
|
Your Name [Email] dd-Mon-Year
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (NTFS_BUG_CHECK_STRUCSUP)
|
|
|
|
//
|
|
// Local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_MFTSUP)
|
|
|
|
//
|
|
// Boolean controlling whether to allow holes in the Mft.
|
|
//
|
|
|
|
BOOLEAN NtfsPerforateMft = FALSE;
|
|
|
|
//
|
|
// Local support routines
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsTruncateMft (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsDefragMftPriv (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
);
|
|
|
|
LONG
|
|
NtfsReadMftExceptionFilter (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PEXCEPTION_POINTERS ExceptionPointer,
|
|
IN PBCB Bcb,
|
|
IN LONGLONG FileOffset
|
|
);
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
VOID
|
|
NtfsVerifyFileReference (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PMFT_SEGMENT_REFERENCE MftSegment
|
|
);
|
|
#endif
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsAllocateMftRecord)
|
|
#pragma alloc_text(PAGE, NtfsCheckForDefrag)
|
|
#pragma alloc_text(PAGE, NtfsDeallocateMftRecord)
|
|
#pragma alloc_text(PAGE, NtfsDefragMftPriv)
|
|
#pragma alloc_text(PAGE, NtfsFillMftHole)
|
|
#pragma alloc_text(PAGE, NtfsInitializeMftHoleRecords)
|
|
#pragma alloc_text(PAGE, NtfsInitializeMftRecord)
|
|
#pragma alloc_text(PAGE, NtfsIsMftIndexInHole)
|
|
#pragma alloc_text(PAGE, NtfsLogMftFileRecord)
|
|
#pragma alloc_text(PAGE, NtfsPinMftRecord)
|
|
#pragma alloc_text(PAGE, NtfsReadFileRecord)
|
|
#pragma alloc_text(PAGE, NtfsReadMftRecord)
|
|
#pragma alloc_text(PAGE, NtfsTruncateMft)
|
|
#pragma alloc_text(PAGE, NtfsIterateMft)
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
#pragma alloc_text(PAGE, NtfsVerifyFileReference)
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
#if NTFSDBG
|
|
ULONG FileRecordCacheHit = 0;
|
|
ULONG FileRecordCacheMiss = 0;
|
|
#endif // DBG
|
|
|
|
VOID
|
|
NtfsReadFileRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFILE_REFERENCE FileReference,
|
|
OUT PBCB *Bcb,
|
|
OUT PFILE_RECORD_SEGMENT_HEADER *BaseFileRecord,
|
|
OUT PATTRIBUTE_RECORD_HEADER *FirstAttribute,
|
|
OUT PLONGLONG MftFileOffset OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads the specified file record from the Mft or cache if its present
|
|
If it comes from disk it is always verified.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume on which Mft is to be read
|
|
|
|
Fcb - If specified allows us to identify the file which owns the
|
|
invalid file record.
|
|
|
|
FileReference - File reference, including sequence number, of the file record
|
|
to be read.
|
|
|
|
Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
|
|
|
|
BaseFileRecord - Returns a pointer to the requested file record.
|
|
|
|
FirstAttribute - Returns a pointer to the first attribute in the file record.
|
|
|
|
MftFileOffset - If specified, returns the file offset of the file record.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsReadFileRecord\n") );
|
|
|
|
//
|
|
// Perform a quick look-aside to see if the file record being requested
|
|
// is one that we have cached in the IrpContext. If so, we reuse that Bcb
|
|
//
|
|
|
|
if (NtfsFindCachedFileRecord( IrpContext,
|
|
NtfsSegmentNumber( FileReference ),
|
|
Bcb,
|
|
BaseFileRecord )) {
|
|
|
|
//
|
|
// We found the Bcb and File record in the cache. Figure out the remainder
|
|
// of the data
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( MftFileOffset )) {
|
|
*MftFileOffset =
|
|
LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( FileReference ));
|
|
|
|
DebugDoit( FileRecordCacheHit++ );
|
|
|
|
}
|
|
} else {
|
|
|
|
USHORT SequenceNumber = FileReference->SequenceNumber;
|
|
|
|
DebugDoit( FileRecordCacheMiss++ );
|
|
|
|
NtfsReadMftRecord( IrpContext,
|
|
Vcb,
|
|
FileReference,
|
|
TRUE,
|
|
Bcb,
|
|
BaseFileRecord,
|
|
MftFileOffset );
|
|
|
|
//
|
|
// Make sure the file is in use - we validated everything else in NtfsReadMftRecord
|
|
//
|
|
|
|
if (!FlagOn( (*BaseFileRecord)->Flags, FILE_RECORD_SEGMENT_IN_USE )) {
|
|
|
|
NtfsUnpinBcb( IrpContext, Bcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, FileReference, NULL );
|
|
}
|
|
}
|
|
|
|
*FirstAttribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)*BaseFileRecord +
|
|
(*BaseFileRecord)->FirstAttributeOffset);
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsReadFileRecord -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsReadMftRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PMFT_SEGMENT_REFERENCE SegmentReference,
|
|
IN BOOLEAN CheckRecord,
|
|
OUT PBCB *Bcb,
|
|
OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
|
|
OUT PLONGLONG MftFileOffset OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads the specified Mft record from the Mft, without checking
|
|
sequence numbers. This routine may be used to read records in the Mft for
|
|
a file other than its base file record, or it could conceivably be used for
|
|
extraordinary maintenance functions.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume on which Mft is to be read
|
|
|
|
SegmentReference - File reference, including sequence number, of the file
|
|
record to be read.
|
|
|
|
Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
|
|
|
|
FileRecord - Returns a pointer to the requested file record.
|
|
|
|
MftFileOffset - If specified, returns the file offset of the file record.
|
|
|
|
CheckRecord - Do a check of records consistency - always set TRUE unless the
|
|
record is unowned and could change beneath us
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord2;
|
|
LONGLONG FileOffset;
|
|
PBCB Bcb2 = NULL;
|
|
BOOLEAN ErrorPath = FALSE;
|
|
|
|
LONGLONG LlTemp1;
|
|
ULONG CorruptHint;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsReadMftRecord\n") );
|
|
DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
|
|
DebugTrace( 0, Dbg, ("SegmentReference = %08lx\n", NtfsSegmentNumber( SegmentReference )) );
|
|
*Bcb = NULL;
|
|
|
|
try {
|
|
|
|
//
|
|
// Capture the Segment Reference and make sure the Sequence Number is 0.
|
|
//
|
|
|
|
FileOffset = NtfsFullSegmentNumber( SegmentReference );
|
|
|
|
//
|
|
// Calculate the file offset in the Mft to the file record segment.
|
|
//
|
|
|
|
FileOffset = LlBytesFromFileRecords( Vcb, FileOffset );
|
|
|
|
//
|
|
// Pass back the file offset within the Mft.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( MftFileOffset )) {
|
|
|
|
*MftFileOffset = FileOffset;
|
|
}
|
|
|
|
//
|
|
// Try to read it from the normal Mft.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsMapStream( IrpContext,
|
|
Vcb->MftScb,
|
|
FileOffset,
|
|
Vcb->BytesPerFileRecordSegment,
|
|
Bcb,
|
|
(PVOID *)FileRecord );
|
|
|
|
//
|
|
// Raise here if we have a file record covered by the mirror,
|
|
// and we do not see the file signature.
|
|
//
|
|
|
|
if ((FileOffset < Vcb->Mft2Scb->Header.FileSize.QuadPart) &&
|
|
(*(PULONG)(*FileRecord)->MultiSectorHeader.Signature != *(PULONG)FileSignature)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DATA_ERROR, NULL, NULL );
|
|
}
|
|
|
|
|
|
//
|
|
// If we get an exception that is not expected, then we will allow
|
|
// the search to continue and let the crash occur in the "normal" place.
|
|
// Otherwise, if the read is within the part of the Mft mirrored in Mft2,
|
|
// then we will simply try to read the data from Mft2. If the expected
|
|
// status came from a read not within Mft2, then we will also continue,
|
|
// which cause one of our caller's try-except's to initiate an unwind.
|
|
//
|
|
|
|
} except (NtfsReadMftExceptionFilter( IrpContext, GetExceptionInformation(), *Bcb, FileOffset )) {
|
|
|
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|
ErrorPath = TRUE;
|
|
}
|
|
|
|
if (ErrorPath) {
|
|
|
|
//
|
|
// Try to read from Mft2. If this fails with an expected status,
|
|
// then we are just going to have to give up and let the unwind
|
|
// occur from one of our caller's try-except.
|
|
//
|
|
|
|
NtfsMapStream( IrpContext,
|
|
Vcb->Mft2Scb,
|
|
FileOffset,
|
|
Vcb->BytesPerFileRecordSegment,
|
|
&Bcb2,
|
|
(PVOID *)&FileRecord2 );
|
|
|
|
//
|
|
// Pin the original page because we are going to update it.
|
|
//
|
|
|
|
NtfsPinMappedData( IrpContext,
|
|
Vcb->MftScb,
|
|
FileOffset,
|
|
Vcb->BytesPerFileRecordSegment,
|
|
Bcb );
|
|
|
|
//
|
|
// Now copy the entire page.
|
|
//
|
|
|
|
RtlCopyMemory( *FileRecord,
|
|
FileRecord2,
|
|
Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// Set it dirty with the largest Lsn, so that whoever is doing Restart
|
|
// will successfully establish the "oldest unapplied Lsn".
|
|
//
|
|
|
|
LlTemp1 = MAXLONGLONG;
|
|
|
|
CcSetDirtyPinnedData( *Bcb,
|
|
(PLARGE_INTEGER)&LlTemp1 );
|
|
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb2 );
|
|
}
|
|
|
|
//
|
|
// Do a consistency check
|
|
//
|
|
|
|
if ( CheckRecord && FlagOn((*FileRecord)->Flags, FILE_RECORD_SEGMENT_IN_USE ) ) {
|
|
if (!NtfsCheckFileRecord( Vcb, *FileRecord, SegmentReference, &CorruptHint )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, SegmentReference, NULL );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
NtfsUnpinBcb( IrpContext, Bcb );
|
|
NtfsUnpinBcb( IrpContext, &Bcb2 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now that we've pinned a file record, cache it in the IrpContext so that
|
|
// it can be safely retrieved later without the expense of mapping again.
|
|
// Don't do any caching if there are no handles, we don't want to do this for
|
|
// mount.
|
|
//
|
|
|
|
if (Vcb->CleanupCount != 0) {
|
|
|
|
NtfsAddToFileRecordCache( IrpContext,
|
|
NtfsSegmentNumber( SegmentReference ),
|
|
*Bcb,
|
|
*FileRecord );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Bcb > %08lx\n", Bcb) );
|
|
DebugTrace( 0, Dbg, ("FileRecord > %08lx\n", *FileRecord) );
|
|
DebugTrace( -1, Dbg, ("NtfsReadMftRecord -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsPinMftRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PMFT_SEGMENT_REFERENCE SegmentReference,
|
|
IN BOOLEAN PreparingToWrite,
|
|
OUT PBCB *Bcb,
|
|
OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
|
|
OUT PLONGLONG MftFileOffset OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine pins the specified Mft record from the Mft, without checking
|
|
sequence numbers. This routine may be used to pin records in the Mft for
|
|
a file other than its base file record, or it could conceivably be used for
|
|
extraordinary maintenance functions, such as during restart.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume on which Mft is to be read
|
|
|
|
SegmentReference - File reference, including sequence number, of the file
|
|
record to be read.
|
|
|
|
PreparingToWrite - TRUE if caller is preparing to write, and does not care
|
|
about whether the record read correctly
|
|
|
|
Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
|
|
|
|
FileRecord - Returns a pointer to the requested file record.
|
|
|
|
MftFileOffset - If specified, returns the file offset of the file record.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG FileOffset;
|
|
|
|
ASSERT_IRP_CONTEXT( IrpContext );
|
|
ASSERT_VCB( Vcb );
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsPinMftRecord\n") );
|
|
DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
|
|
DebugTrace( 0, Dbg, ("SegmentReference = %08lx\n", NtfsSegmentNumber( SegmentReference )) );
|
|
|
|
//
|
|
// Capture the Segment Reference and make sure the Sequence Number is 0.
|
|
//
|
|
|
|
FileOffset = NtfsFullSegmentNumber( SegmentReference );
|
|
|
|
//
|
|
// Calculate the file offset in the Mft to the file record segment.
|
|
//
|
|
|
|
FileOffset = LlBytesFromFileRecords( Vcb, FileOffset );
|
|
|
|
//
|
|
// Pass back the file offset within the Mft.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( MftFileOffset )) {
|
|
|
|
*MftFileOffset = FileOffset;
|
|
}
|
|
|
|
//
|
|
// Try to read it from the normal Mft.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsPinStream( IrpContext,
|
|
Vcb->MftScb,
|
|
FileOffset,
|
|
Vcb->BytesPerFileRecordSegment,
|
|
Bcb,
|
|
(PVOID *)FileRecord );
|
|
|
|
//
|
|
// If we get an exception that is not expected, then we will allow
|
|
// the search to continue and let the crash occur in the "normal" place.
|
|
// Otherwise, if the read is within the part of the Mft mirrored in Mft2,
|
|
// then we will simply try to read the data from Mft2. If the expected
|
|
// status came from a read not within Mft2, then we will also continue,
|
|
// which cause one of our caller's try-except's to initiate an unwind.
|
|
//
|
|
|
|
} except(!FsRtlIsNtstatusExpected(GetExceptionCode()) ?
|
|
EXCEPTION_CONTINUE_SEARCH :
|
|
( FileOffset < Vcb->Mft2Scb->Header.FileSize.QuadPart ) ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
//
|
|
// Try to read from Mft2. If this fails with an expected status,
|
|
// then we are just going to have to give up and let the unwind
|
|
// occur from one of our caller's try-except.
|
|
//
|
|
|
|
NtfsMinimumExceptionProcessing( IrpContext );
|
|
NtfsPinStream( IrpContext,
|
|
Vcb->Mft2Scb,
|
|
FileOffset,
|
|
Vcb->BytesPerFileRecordSegment,
|
|
Bcb,
|
|
(PVOID *)FileRecord );
|
|
|
|
}
|
|
|
|
if (!PreparingToWrite &&
|
|
(*(PULONG)(*FileRecord)->MultiSectorHeader.Signature != *(PULONG)FileSignature)) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, SegmentReference, NULL );
|
|
}
|
|
|
|
//
|
|
// Now that we've pinned a file record, cache it in the IrpContext so that
|
|
// it can be safely retrieved later without the expense of mapping again.
|
|
// Don't do any caching if there are no handles, we don't want to do this for
|
|
// mount.
|
|
//
|
|
|
|
if (Vcb->CleanupCount != 0) {
|
|
|
|
NtfsAddToFileRecordCache( IrpContext,
|
|
NtfsSegmentNumber( SegmentReference ),
|
|
*Bcb,
|
|
*FileRecord );
|
|
}
|
|
|
|
DebugTrace( 0, Dbg, ("Bcb > %08lx\n", Bcb) );
|
|
DebugTrace( 0, Dbg, ("FileRecord > %08lx\n", *FileRecord) );
|
|
DebugTrace( -1, Dbg, ("NtfsPinMftRecord -> VOID\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
MFT_SEGMENT_REFERENCE
|
|
NtfsAllocateMftRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN BOOLEAN MftData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to allocate a record in the Mft file. We need
|
|
to find the bitmap attribute for the Mft file and call into the bitmap
|
|
package to allocate us a record.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume on which Mft is to be read
|
|
|
|
MftData - TRUE if the file record is being allocated to describe the
|
|
$DATA attribute for the Mft.
|
|
|
|
Return Value:
|
|
|
|
MFT_SEGMENT_REFERENCE - The is the segment reference for the allocated
|
|
record. It contains the file reference number but without
|
|
the previous sequence number.
|
|
|
|
--*/
|
|
|
|
{
|
|
MFT_SEGMENT_REFERENCE NewMftRecord;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
BOOLEAN FoundAttribute;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsAllocateMftRecord: Entered\n") );
|
|
|
|
//
|
|
// Synchronize the lookup by acquiring the Mft.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
|
|
//
|
|
// Lookup the bitmap allocation for the Mft file. This is the
|
|
// bitmap attribute for the Mft file.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try finally to cleanup the attribute context.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Lookup the bitmap attribute for the Mft.
|
|
//
|
|
|
|
FoundAttribute = NtfsLookupAttributeByCode( IrpContext,
|
|
Vcb->MftScb->Fcb,
|
|
&Vcb->MftScb->Fcb->FileReference,
|
|
$BITMAP,
|
|
&AttrContext );
|
|
//
|
|
// Error if we don't find the bitmap
|
|
//
|
|
|
|
if (!FoundAttribute) {
|
|
|
|
DebugTrace( 0, Dbg, ("Should find bitmap attribute\n") );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Reserve a new mft record if necc.
|
|
//
|
|
|
|
if (!FlagOn(Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED)) {
|
|
|
|
(VOID)NtfsReserveMftRecord( IrpContext,
|
|
Vcb,
|
|
&AttrContext );
|
|
}
|
|
|
|
//
|
|
// If we need this record for the Mft Data attribute, then we need to
|
|
// use the one we have already reserved, and then remember there is'nt
|
|
// one reserved anymore.
|
|
//
|
|
|
|
if (MftData) {
|
|
|
|
ASSERT( FlagOn(Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED) );
|
|
|
|
NtfsSetSegmentNumber( &NewMftRecord,
|
|
0,
|
|
NtfsAllocateMftReservedRecord( IrpContext,
|
|
Vcb,
|
|
&AttrContext ) );
|
|
|
|
//
|
|
// Never let use get file record zero for this or we could lose a
|
|
// disk.
|
|
//
|
|
|
|
ASSERT( NtfsUnsafeSegmentNumber( &NewMftRecord ) != 0 );
|
|
|
|
if (NtfsUnsafeSegmentNumber( &NewMftRecord ) == 0) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Allocate the record.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsSetSegmentNumber( &NewMftRecord,
|
|
0,
|
|
NtfsAllocateRecord( IrpContext,
|
|
&Vcb->MftScb->ScbType.Index.RecordAllocationContext,
|
|
&AttrContext ) );
|
|
}
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsAllocateMftRecord );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
DebugTrace( -1, Dbg, ("NtfsAllocateMftRecord: Exit\n") );
|
|
}
|
|
|
|
return NewMftRecord;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInitializeMftRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN OUT PMFT_SEGMENT_REFERENCE MftSegment,
|
|
IN OUT PFILE_RECORD_SEGMENT_HEADER FileRecord,
|
|
IN PBCB Bcb,
|
|
IN BOOLEAN Directory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes a Mft record for use. We need to initialize the
|
|
sequence number for this usage of the the record. We also initialize the
|
|
update sequence array and the field which indicates the first usable
|
|
attribute offset in the record.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume for the Mft.
|
|
|
|
MftSegment - This is a pointer to the file reference for this
|
|
segment. We store the sequence number in it to make this
|
|
a fully valid file reference.
|
|
|
|
FileRecord - Pointer to the file record to initialize.
|
|
|
|
Bcb - Bcb to use to set this page dirty via NtfsWriteLog.
|
|
|
|
Directory - Boolean indicating if this file is a directory containing
|
|
an index over the filename attribute.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG FileRecordOffset;
|
|
|
|
PUSHORT UsaSequenceNumber;
|
|
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsInitializeMftRecord: Entered\n") );
|
|
|
|
//
|
|
// Write a log record to uninitialize the structure in case we abort.
|
|
// We need to do this prior to setting the IN_USE bit.
|
|
// We don't store the Lsn for this operation in the page because there
|
|
// is no redo operation.
|
|
//
|
|
|
|
//
|
|
// Capture the Segment Reference and make sure the Sequence Number is 0.
|
|
//
|
|
|
|
FileRecordOffset = NtfsFullSegmentNumber(MftSegment);
|
|
|
|
FileRecordOffset = LlBytesFromFileRecords( Vcb, FileRecordOffset );
|
|
|
|
//
|
|
// We now log the new Mft record.
|
|
//
|
|
|
|
FileRecord->Lsn = NtfsWriteLog( IrpContext,
|
|
Vcb->MftScb,
|
|
Bcb,
|
|
Noop,
|
|
NULL,
|
|
0,
|
|
DeallocateFileRecordSegment,
|
|
NULL,
|
|
0,
|
|
FileRecordOffset,
|
|
0,
|
|
0,
|
|
Vcb->BytesPerFileRecordSegment );
|
|
|
|
RtlZeroMemory( &FileRecord->ReferenceCount,
|
|
Vcb->BytesPerFileRecordSegment - FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER, ReferenceCount ));
|
|
|
|
//
|
|
// First we update the sequence count in the file record and our
|
|
// Mft segment. We avoid using 0 as a sequence number.
|
|
//
|
|
|
|
if (FileRecord->SequenceNumber == 0) {
|
|
|
|
FileRecord->SequenceNumber = 1;
|
|
}
|
|
|
|
//
|
|
// Store the new sequence number in the Mft segment given us by the
|
|
// caller.
|
|
//
|
|
|
|
MftSegment->SequenceNumber = FileRecord->SequenceNumber;
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
|
|
//
|
|
// Do a DBG-only sanity check to see if we're errorneously reusing this file reference.
|
|
//
|
|
|
|
NtfsVerifyFileReference( IrpContext, MftSegment );
|
|
|
|
#endif
|
|
|
|
//
|
|
// Fill in the header for the Update sequence array.
|
|
//
|
|
|
|
*(PULONG)FileRecord->MultiSectorHeader.Signature = *(PULONG)FileSignature;
|
|
|
|
FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset = FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER, UpdateArrayForCreateOnly );
|
|
FileRecord->MultiSectorHeader.UpdateSequenceArraySize = (USHORT)UpdateSequenceArraySize( Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// We initialize the update sequence array sequence number to one.
|
|
//
|
|
|
|
UsaSequenceNumber = Add2Ptr( FileRecord, FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset );
|
|
*UsaSequenceNumber = 1;
|
|
|
|
//
|
|
// The first attribute offset begins on a quad-align boundary
|
|
// after the update sequence array.
|
|
//
|
|
|
|
FileRecord->FirstAttributeOffset = (USHORT)(FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset
|
|
+ (FileRecord->MultiSectorHeader.UpdateSequenceArraySize
|
|
* sizeof( UPDATE_SEQUENCE_NUMBER )));
|
|
|
|
FileRecord->FirstAttributeOffset = (USHORT)QuadAlign( FileRecord->FirstAttributeOffset );
|
|
|
|
//
|
|
// This is also the first free byte in this file record.
|
|
//
|
|
|
|
FileRecord->FirstFreeByte = FileRecord->FirstAttributeOffset;
|
|
|
|
//
|
|
// We set the flags to show the segment is in use and look at
|
|
// the directory parameter to indicate whether to show
|
|
// the name index present.
|
|
//
|
|
|
|
FileRecord->Flags = (USHORT)(FILE_RECORD_SEGMENT_IN_USE |
|
|
(Directory ? FILE_FILE_NAME_INDEX_PRESENT : 0));
|
|
|
|
//
|
|
// The size is given in the Vcb.
|
|
//
|
|
|
|
FileRecord->BytesAvailable = Vcb->BytesPerFileRecordSegment;
|
|
|
|
//
|
|
// The current FRS number.
|
|
//
|
|
|
|
FileRecord->SegmentNumberHighPart = MftSegment->SegmentNumberHighPart;
|
|
FileRecord->SegmentNumberLowPart = MftSegment->SegmentNumberLowPart;
|
|
|
|
//
|
|
// Now we put an $END attribute in the File record.
|
|
//
|
|
|
|
AttributeHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord,
|
|
FileRecord->FirstFreeByte );
|
|
|
|
FileRecord->FirstFreeByte += QuadAlign( sizeof(ATTRIBUTE_TYPE_CODE) );
|
|
|
|
//
|
|
// Fill in the fields in the attribute.
|
|
//
|
|
|
|
AttributeHeader->TypeCode = $END;
|
|
|
|
//
|
|
// Remember if this is the first time used.
|
|
//
|
|
|
|
AttributeHeader->RecordLength = 0x11477982;
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsInitializeMftRecord: Exit\n") );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeallocateMftRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN ULONG FileNumber
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will cause an Mft record to go into the NOT_USED state.
|
|
We pin the record and modify the sequence count and IN USE bit.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume.
|
|
|
|
FileNumber - This is the low 32 bits for the file number.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
LONGLONG FileOffset;
|
|
MFT_SEGMENT_REFERENCE Reference;
|
|
PBCB MftBcb = NULL;
|
|
|
|
BOOLEAN FoundAttribute;
|
|
BOOLEAN AcquiredMft = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDeallocateMftRecord: Entered\n") );
|
|
|
|
NtfsSetSegmentNumber( &Reference, 0, FileNumber );
|
|
Reference.SequenceNumber = 0;
|
|
|
|
//
|
|
// Lookup the bitmap allocation for the Mft file.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try finally to cleanup the attribute context.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsPinMftRecord( IrpContext,
|
|
Vcb,
|
|
&Reference,
|
|
TRUE,
|
|
&MftBcb,
|
|
&FileRecord,
|
|
&FileOffset );
|
|
|
|
//
|
|
// Log changes if the file is currently in use
|
|
//
|
|
|
|
if (FlagOn(FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE)) {
|
|
|
|
FileRecord->Lsn = NtfsWriteLog( IrpContext,
|
|
Vcb->MftScb,
|
|
MftBcb,
|
|
DeallocateFileRecordSegment,
|
|
NULL,
|
|
0,
|
|
InitializeFileRecordSegment,
|
|
FileRecord,
|
|
PtrOffset(FileRecord, &FileRecord->Flags) + 4,
|
|
FileOffset,
|
|
0,
|
|
0,
|
|
Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// We increment the sequence count in the file record and clear
|
|
// the In-Use flag.
|
|
//
|
|
|
|
ClearFlag( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE );
|
|
|
|
FileRecord->SequenceNumber += 1;
|
|
|
|
NtfsUnpinBcb( IrpContext, &MftBcb );
|
|
}
|
|
|
|
//
|
|
// Synchronize the lookup by acquiring the Mft.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
AcquiredMft = TRUE;
|
|
|
|
//
|
|
// Lookup the bitmap attribute for the Mft.
|
|
//
|
|
|
|
FoundAttribute = NtfsLookupAttributeByCode( IrpContext,
|
|
Vcb->MftScb->Fcb,
|
|
&Vcb->MftScb->Fcb->FileReference,
|
|
$BITMAP,
|
|
&AttrContext );
|
|
//
|
|
// Error if we don't find the bitmap
|
|
//
|
|
|
|
if (!FoundAttribute) {
|
|
|
|
DebugTrace( 0, Dbg, ("Should find bitmap attribute\n") );
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
|
|
}
|
|
|
|
NtfsDeallocateRecord( IrpContext,
|
|
&Vcb->MftScb->ScbType.Index.RecordAllocationContext,
|
|
FileNumber,
|
|
&AttrContext );
|
|
|
|
//
|
|
// If this file number is less than our reserved index then clear
|
|
// the reserved index.
|
|
//
|
|
|
|
if (FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED ) &&
|
|
(FileNumber < Vcb->MftScb->ScbType.Mft.ReservedIndex)) {
|
|
|
|
ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MFT_REC_RESERVED );
|
|
ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
|
|
|
|
Vcb->MftScb->ScbType.Mft.ReservedIndex = 0;
|
|
}
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
|
|
Vcb->MftFreeRecords += 1;
|
|
Vcb->MftScb->ScbType.Mft.FreeRecordChange += 1;
|
|
|
|
if (AcquiredMft) {
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsDeallocateMftRecord );
|
|
|
|
NtfsUnpinBcb( IrpContext, &MftBcb );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDeallocateMftRecord: Exit\n") );
|
|
}
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsIsMftIndexInHole (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN ULONG Index,
|
|
OUT PULONG HoleLength OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to check if an Mft index lies within a hole in
|
|
the Mft.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume.
|
|
|
|
Index - This is the index to test. It is the lower 32 bits of an
|
|
Mft segment.
|
|
|
|
HoleLength - This is the length of the hole starting at this index.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the index is within the Mft and there is no allocation
|
|
for it.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN InHole = FALSE;
|
|
VCN Vcn;
|
|
LCN Lcn;
|
|
LONGLONG Clusters;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If the index is past the last file record then it is not considered
|
|
// to be in a hole.
|
|
//
|
|
|
|
if (Index < (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart )) {
|
|
|
|
if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
Vcn = Index << Vcb->MftToClusterShift;
|
|
|
|
} else {
|
|
|
|
Vcn = Index >> Vcb->MftToClusterShift;
|
|
}
|
|
|
|
//
|
|
// Now look this up the Mcb for the Mft. This Vcn had better be
|
|
// in the Mcb or there is some problem.
|
|
//
|
|
|
|
if (!NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
|
|
Vcn,
|
|
&Lcn,
|
|
&Clusters,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL )) {
|
|
|
|
ASSERT( FALSE );
|
|
NtfsRaiseStatus( IrpContext,
|
|
STATUS_FILE_CORRUPT_ERROR,
|
|
NULL,
|
|
Vcb->MftScb->Fcb );
|
|
}
|
|
|
|
if (Lcn == UNUSED_LCN) {
|
|
|
|
InHole = TRUE;
|
|
|
|
//
|
|
// We know the number of clusters beginning from
|
|
// this point in the Mcb. Convert to file records
|
|
// and return to the user.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT( HoleLength )) {
|
|
|
|
if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
*HoleLength = ((ULONG)Clusters) >> Vcb->MftToClusterShift;
|
|
|
|
} else {
|
|
|
|
*HoleLength = ((ULONG)Clusters) << Vcb->MftToClusterShift;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return InHole;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsFillMftHole (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN ULONG Index
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to fill in a hole within the Mft. We will find
|
|
the beginning of the hole and then allocate the clusters to fill the
|
|
hole. We will try to fill a hole with the HoleGranularity in the Vcb.
|
|
If the hole containing this index is not that large we will truncate
|
|
the size being added. We always guarantee to allocate the clusters on
|
|
file record boundaries.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume.
|
|
|
|
Index - This is the index to test. It is the lower 32 bits of an
|
|
Mft segment.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG FileRecords;
|
|
ULONG BaseIndex;
|
|
|
|
VCN IndexVcn;
|
|
VCN HoleStartVcn;
|
|
VCN StartingVcn;
|
|
|
|
LCN Lcn = UNUSED_LCN;
|
|
LONGLONG ClusterCount;
|
|
LONGLONG RunClusterCount;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Convert the Index to a Vcn in the file. Find the cluster that would
|
|
// be the start of this hole if the hole is fully deallocated.
|
|
//
|
|
|
|
if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
IndexVcn = Index << Vcb->MftToClusterShift;
|
|
HoleStartVcn = (Index & Vcb->MftHoleInverseMask) << Vcb->MftToClusterShift;
|
|
|
|
} else {
|
|
|
|
IndexVcn = Index >> Vcb->MftToClusterShift;
|
|
HoleStartVcn = (Index & Vcb->MftHoleInverseMask) >> Vcb->MftToClusterShift;
|
|
}
|
|
|
|
//
|
|
// Lookup the run containing this index.
|
|
//
|
|
|
|
NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
|
|
IndexVcn,
|
|
&Lcn,
|
|
&ClusterCount,
|
|
NULL,
|
|
&RunClusterCount,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// This had better be a hole.
|
|
//
|
|
|
|
if (Lcn != UNUSED_LCN) {
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
|
|
}
|
|
|
|
//
|
|
// Take the start of the deallocated space and round up to a hole boundary.
|
|
//
|
|
|
|
StartingVcn = IndexVcn - (RunClusterCount - ClusterCount);
|
|
|
|
if (StartingVcn <= HoleStartVcn) {
|
|
|
|
StartingVcn = HoleStartVcn;
|
|
RunClusterCount -= (HoleStartVcn - StartingVcn);
|
|
StartingVcn = HoleStartVcn;
|
|
|
|
//
|
|
// We can go to the beginning of a hole. Just use the Vcn for the file
|
|
// record we want to reallocate.
|
|
//
|
|
|
|
} else {
|
|
|
|
RunClusterCount = ClusterCount;
|
|
StartingVcn = IndexVcn;
|
|
}
|
|
|
|
//
|
|
// Trim the cluster count back to a hole if necessary.
|
|
//
|
|
|
|
if ((ULONG) RunClusterCount >= Vcb->MftClustersPerHole) {
|
|
|
|
RunClusterCount = Vcb->MftClustersPerHole;
|
|
|
|
//
|
|
// We don't have enough clusters for a full hole. Make sure
|
|
// we end on a file record boundary however. We must end up
|
|
// with enough clusters for the file record we are reallocating.
|
|
//
|
|
|
|
} else if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
((PLARGE_INTEGER) &ClusterCount)->LowPart &= (Vcb->ClustersPerFileRecordSegment - 1);
|
|
|
|
if (StartingVcn + ClusterCount < IndexVcn + Vcb->ClustersPerFileRecordSegment) {
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now attempt to allocate the space.
|
|
//
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
Vcb->MftScb->FileObject,
|
|
Vcb->MftScb,
|
|
StartingVcn,
|
|
ClusterCount,
|
|
FALSE,
|
|
NULL );
|
|
|
|
//
|
|
// Compute the number of file records reallocated and then
|
|
// initialize and deallocate each file record.
|
|
//
|
|
|
|
if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
FileRecords = (ULONG) ClusterCount >> Vcb->MftToClusterShift;
|
|
BaseIndex = (ULONG) StartingVcn >> Vcb->MftToClusterShift;
|
|
|
|
} else {
|
|
|
|
FileRecords = (ULONG) ClusterCount << Vcb->MftToClusterShift;
|
|
BaseIndex = (ULONG) StartingVcn << Vcb->MftToClusterShift;
|
|
}
|
|
|
|
NtfsInitializeMftHoleRecords( IrpContext,
|
|
Vcb,
|
|
BaseIndex,
|
|
FileRecords );
|
|
|
|
Vcb->MftHoleRecords -= FileRecords;
|
|
Vcb->MftScb->ScbType.Mft.HoleRecordChange -= FileRecords;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsLogMftFileRecord (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
|
|
IN LONGLONG MftOffset,
|
|
IN PBCB Bcb,
|
|
IN BOOLEAN Redo
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to log changes to the file record for the Mft
|
|
file. We log the entire record instead of individual changes so
|
|
that we can recover the data even if there is a USA error. The entire
|
|
data will be sitting in the Log file.
|
|
|
|
Arguments:
|
|
|
|
Vcb - This is the Vcb for the volume being logged.
|
|
|
|
FileRecord - This is the file record being logged.
|
|
|
|
MftOffset - This is the offset of this file record in the Mft stream.
|
|
|
|
Bcb - This is the Bcb for the pinned file record.
|
|
|
|
RedoOperation - Boolean indicating if we are logging
|
|
a redo or undo operation.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID RedoBuffer;
|
|
NTFS_LOG_OPERATION RedoOperation;
|
|
ULONG RedoLength;
|
|
|
|
PVOID UndoBuffer;
|
|
NTFS_LOG_OPERATION UndoOperation;
|
|
ULONG UndoLength;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Find the logging values based on whether this is an
|
|
// undo or redo.
|
|
//
|
|
|
|
if (Redo) {
|
|
|
|
RedoBuffer = FileRecord;
|
|
RedoOperation = InitializeFileRecordSegment;
|
|
RedoLength = FileRecord->FirstFreeByte;
|
|
|
|
UndoBuffer = NULL;
|
|
UndoOperation = Noop;
|
|
UndoLength = 0;
|
|
|
|
} else {
|
|
|
|
UndoBuffer = FileRecord;
|
|
UndoOperation = InitializeFileRecordSegment;
|
|
UndoLength = FileRecord->FirstFreeByte;
|
|
|
|
RedoBuffer = NULL;
|
|
RedoOperation = Noop;
|
|
RedoLength = 0;
|
|
}
|
|
|
|
//
|
|
// Now that we have calculated all the values, call the logging
|
|
// routine.
|
|
//
|
|
|
|
NtfsWriteLog( IrpContext,
|
|
Vcb->MftScb,
|
|
Bcb,
|
|
RedoOperation,
|
|
RedoBuffer,
|
|
RedoLength,
|
|
UndoOperation,
|
|
UndoBuffer,
|
|
UndoLength,
|
|
MftOffset,
|
|
0,
|
|
0,
|
|
Vcb->BytesPerFileRecordSegment );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
NtfsDefragMft (
|
|
IN PDEFRAG_MFT DefragMft
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called whenever we have detected that the Mft is in a state
|
|
where defragging is desired.
|
|
|
|
Arguments:
|
|
|
|
DefragMft - This is the defrag structure.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if we took some defrag step, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
TOP_LEVEL_CONTEXT TopLevelContext;
|
|
PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
|
|
|
|
PVCB Vcb;
|
|
PIRP_CONTEXT IrpContext = NULL;
|
|
|
|
BOOLEAN DefragStepTaken = FALSE;
|
|
|
|
DebugTrace( +1, Dbg, ("NtfsDefragMft: Entered\n") );
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
ThreadTopLevelContext = NtfsInitializeTopLevelIrp( &TopLevelContext, TRUE, FALSE );
|
|
ASSERT( ThreadTopLevelContext == &TopLevelContext );
|
|
|
|
Vcb = DefragMft->Vcb;
|
|
|
|
//
|
|
// Use a try-except to catch errors here.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Deallocate the defrag structure we were called with.
|
|
//
|
|
|
|
if (DefragMft->DeallocateWorkItem) {
|
|
|
|
NtfsFreePool( DefragMft );
|
|
}
|
|
|
|
//
|
|
// Create the Irp context. We will use all of the transaction support
|
|
// contained in a normal IrpContext.
|
|
//
|
|
|
|
NtfsInitializeIrpContext( NULL, TRUE, &IrpContext );
|
|
IrpContext->Vcb = Vcb;
|
|
|
|
NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
|
|
if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED )
|
|
&& FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
DefragStepTaken = NtfsDefragMftPriv( IrpContext,
|
|
Vcb );
|
|
} else {
|
|
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
}
|
|
|
|
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
|
|
|
} except( NtfsExceptionFilter( IrpContext, GetExceptionInformation())) {
|
|
|
|
NtfsProcessException( IrpContext, NULL, GetExceptionCode() );
|
|
|
|
//
|
|
// If the exception code was not LOG_FILE_FULL then
|
|
// disable defragging.
|
|
//
|
|
|
|
if (GetExceptionCode() != STATUS_LOG_FILE_FULL) {
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
}
|
|
|
|
DefragStepTaken = FALSE;
|
|
}
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
|
|
ASSERT( IoGetTopLevelIrp() != (PIRP) &TopLevelContext );
|
|
FsRtlExitFileSystem();
|
|
|
|
DebugTrace( -1, Dbg, ("NtfsDefragMft: Exit\n") );
|
|
|
|
return DefragStepTaken;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsCheckForDefrag (
|
|
IN OUT PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to check whether there is any defrag work to do
|
|
involving freeing file records and creating holes in the Mft. It
|
|
will modify the TRIGGERED flag in the Vcb if there is still work to
|
|
do.
|
|
|
|
Arguments:
|
|
|
|
Vcb - This is the Vcb for the volume to defrag.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG RecordsToClusters;
|
|
LONGLONG AdjClusters;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Convert the available Mft records to clusters.
|
|
//
|
|
|
|
if (Vcb->FileRecordsPerCluster) {
|
|
|
|
RecordsToClusters = Int64ShllMod32(((LONGLONG)(Vcb->MftFreeRecords - Vcb->MftHoleRecords)),
|
|
Vcb->MftToClusterShift);
|
|
|
|
} else {
|
|
|
|
RecordsToClusters = Int64ShraMod32(((LONGLONG)(Vcb->MftFreeRecords - Vcb->MftHoleRecords)),
|
|
Vcb->MftToClusterShift);
|
|
}
|
|
|
|
//
|
|
// If we have already triggered the defrag then check if we are below
|
|
// the lower threshold.
|
|
//
|
|
|
|
if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
|
|
|
|
AdjClusters = Vcb->FreeClusters >> MFT_DEFRAG_LOWER_THRESHOLD;
|
|
|
|
if (AdjClusters >= RecordsToClusters) {
|
|
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED );
|
|
}
|
|
|
|
//
|
|
// Otherwise check if we have exceeded the upper threshold.
|
|
//
|
|
|
|
} else {
|
|
|
|
AdjClusters = Vcb->FreeClusters >> MFT_DEFRAG_UPPER_THRESHOLD;
|
|
|
|
if (AdjClusters < RecordsToClusters) {
|
|
|
|
SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInitializeMftHoleRecords (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN ULONG FirstIndex,
|
|
IN ULONG RecordCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to initialize the file records created when filling
|
|
a hole in the Mft.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Vcb for volume.
|
|
|
|
FirstIndex - Index for the start of the hole to fill.
|
|
|
|
RecordCount - Count of file records in the hole.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBCB Bcb = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Loop to initialize each file record.
|
|
//
|
|
|
|
while (RecordCount--) {
|
|
|
|
PUSHORT UsaSequenceNumber;
|
|
PMULTI_SECTOR_HEADER UsaHeader;
|
|
|
|
MFT_SEGMENT_REFERENCE ThisMftSegment;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
//
|
|
// Convert the index to a segment reference.
|
|
//
|
|
|
|
*((PLONGLONG)&ThisMftSegment) = FirstIndex;
|
|
|
|
//
|
|
// Pin the file record to initialize.
|
|
//
|
|
|
|
NtfsPinMftRecord( IrpContext,
|
|
Vcb,
|
|
&ThisMftSegment,
|
|
TRUE,
|
|
&Bcb,
|
|
&FileRecord,
|
|
NULL );
|
|
|
|
//
|
|
// Initialize the file record including clearing the in-use
|
|
// bit.
|
|
//
|
|
|
|
RtlZeroMemory( FileRecord, Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// Fill in the header for the Update sequence array.
|
|
//
|
|
|
|
UsaHeader = (PMULTI_SECTOR_HEADER) FileRecord;
|
|
|
|
*(PULONG)UsaHeader->Signature = *(PULONG)FileSignature;
|
|
|
|
UsaHeader->UpdateSequenceArrayOffset = FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER,
|
|
UpdateArrayForCreateOnly );
|
|
UsaHeader->UpdateSequenceArraySize = (USHORT)UpdateSequenceArraySize( Vcb->BytesPerFileRecordSegment );
|
|
|
|
//
|
|
// We initialize the update sequence array sequence number to one.
|
|
//
|
|
|
|
UsaSequenceNumber = Add2Ptr( FileRecord, UsaHeader->UpdateSequenceArrayOffset );
|
|
*UsaSequenceNumber = 1;
|
|
|
|
//
|
|
// The first attribute offset begins on a quad-align boundary
|
|
// after the update sequence array.
|
|
//
|
|
|
|
FileRecord->FirstAttributeOffset = (USHORT)(UsaHeader->UpdateSequenceArrayOffset
|
|
+ (UsaHeader->UpdateSequenceArraySize
|
|
* sizeof( UPDATE_SEQUENCE_NUMBER )));
|
|
|
|
FileRecord->FirstAttributeOffset = (USHORT)QuadAlign( FileRecord->FirstAttributeOffset );
|
|
|
|
//
|
|
// The size is given in the Vcb.
|
|
//
|
|
|
|
FileRecord->BytesAvailable = Vcb->BytesPerFileRecordSegment;
|
|
|
|
//
|
|
// Now we put an $END attribute in the File record.
|
|
//
|
|
|
|
AttributeHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord,
|
|
FileRecord->FirstAttributeOffset );
|
|
|
|
//
|
|
// The first free byte is after this location.
|
|
//
|
|
|
|
FileRecord->FirstFreeByte = QuadAlign( FileRecord->FirstAttributeOffset
|
|
+ sizeof( ATTRIBUTE_TYPE_CODE ));
|
|
|
|
//
|
|
// Fill in the fields in the attribute.
|
|
//
|
|
|
|
AttributeHeader->TypeCode = $END;
|
|
|
|
//
|
|
// The current FRS number.
|
|
//
|
|
|
|
FileRecord->SegmentNumberHighPart = ThisMftSegment.SegmentNumberHighPart;
|
|
FileRecord->SegmentNumberLowPart = ThisMftSegment.SegmentNumberLowPart;
|
|
|
|
//
|
|
// Log the entire file record.
|
|
//
|
|
|
|
NtfsLogMftFileRecord( IrpContext,
|
|
Vcb,
|
|
FileRecord,
|
|
LlBytesFromFileRecords( Vcb, FirstIndex ),
|
|
Bcb,
|
|
TRUE );
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
|
|
//
|
|
// Move to the next record.
|
|
//
|
|
|
|
FirstIndex += 1;
|
|
}
|
|
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsInitializeMftHoleRecords );
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsTruncateMft (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to perform the work of truncating the Mft. If will
|
|
truncate the Mft and adjust the sizes of the Mft and Mft bitmap.
|
|
|
|
Arguments:
|
|
|
|
Vcb - This is the Vcb for the volume to defrag.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if we could deallocate any disk space, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID RangePtr;
|
|
ULONG Index;
|
|
VCN StartingVcn;
|
|
VCN NextVcn;
|
|
LCN NextLcn;
|
|
LONGLONG ClusterCount;
|
|
LONGLONG FileOffset;
|
|
|
|
ULONG FreeRecordChange;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Try to find a range of file records at the end of the file which can
|
|
// be deallocated.
|
|
//
|
|
|
|
if (!NtfsFindMftFreeTail( IrpContext, Vcb, &FileOffset )) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
FreeRecordChange = (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart - FileOffset );
|
|
|
|
Vcb->MftFreeRecords -= FreeRecordChange;
|
|
Vcb->MftScb->ScbType.Mft.FreeRecordChange -= FreeRecordChange;
|
|
|
|
//
|
|
// Now we want to figure out how many holes we may be removing from the Mft.
|
|
// Walk through the Mcb and count the holes.
|
|
//
|
|
|
|
StartingVcn = LlClustersFromBytes( Vcb, FileOffset );
|
|
|
|
NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
|
|
StartingVcn,
|
|
&NextLcn,
|
|
&ClusterCount,
|
|
NULL,
|
|
NULL,
|
|
&RangePtr,
|
|
&Index );
|
|
|
|
do {
|
|
|
|
//
|
|
// If this is a hole then update the hole count in the Vcb and
|
|
// hole change count in the MftScb.
|
|
//
|
|
|
|
if (NextLcn == UNUSED_LCN) {
|
|
|
|
ULONG HoleChange;
|
|
|
|
if (Vcb->FileRecordsPerCluster == 0) {
|
|
|
|
HoleChange = ((ULONG)ClusterCount) >> Vcb->MftToClusterShift;
|
|
|
|
} else {
|
|
|
|
HoleChange = ((ULONG)ClusterCount) << Vcb->MftToClusterShift;
|
|
}
|
|
|
|
Vcb->MftHoleRecords -= HoleChange;
|
|
Vcb->MftScb->ScbType.Mft.HoleRecordChange -= HoleChange;
|
|
}
|
|
|
|
Index += 1;
|
|
|
|
} while (NtfsGetSequentialMcbEntry( &Vcb->MftScb->Mcb,
|
|
&RangePtr,
|
|
Index,
|
|
&NextVcn,
|
|
&NextLcn,
|
|
&ClusterCount ));
|
|
|
|
//
|
|
// We want to flush the data in the Mft out to disk in
|
|
// case a lazywrite comes in during a window where we have
|
|
// removed the allocation but before a possible abort.
|
|
//
|
|
|
|
CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject,
|
|
(PLARGE_INTEGER)&FileOffset,
|
|
BytesFromFileRecords( Vcb, FreeRecordChange ),
|
|
&IoStatus );
|
|
|
|
ASSERT( IoStatus.Status == STATUS_SUCCESS );
|
|
|
|
//
|
|
// Now do the truncation.
|
|
//
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
Vcb->MftScb->FileObject,
|
|
Vcb->MftScb,
|
|
StartingVcn,
|
|
MAXLONGLONG,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
NTSTATUS
|
|
NtfsIterateMft (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN OUT PFILE_REFERENCE FileReference,
|
|
IN FILE_RECORD_WALK FileRecordFunction,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine interates over the MFT. It calls the FileRecordFunction
|
|
with an Fcb for each existing file on the volume. The Fcb is owned
|
|
exclusive and Vcb is owned shared. The starting FileReference number
|
|
is passed in so that iterate can be restarted where is left off.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Pointer to the volume to control for the MFT
|
|
|
|
FileReference - Suplies a pointer to the starting file reference number
|
|
This value is updated as the interator progresses.
|
|
|
|
FileRecordFunction - Suplies a pointer to function to be called with
|
|
each file found in the MFT.
|
|
|
|
Context - Passed along to the FileRecordFunction.
|
|
|
|
Return Value:
|
|
|
|
Returns back status of the entire operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
ULONG LogFileFullCount = 0;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PFCB CurrentFcb = NULL;
|
|
BOOLEAN DecrementReferenceCount = FALSE;
|
|
KEVENT Event;
|
|
LARGE_INTEGER Timeout;
|
|
|
|
PAGED_CODE();
|
|
|
|
KeInitializeEvent( &Event, SynchronizationEvent, FALSE );
|
|
Timeout.QuadPart = 0;
|
|
|
|
while (TRUE) {
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// Check for APC delivery indicating thread death or cancel
|
|
//
|
|
|
|
Status = KeWaitForSingleObject( &Event,
|
|
Executive,
|
|
UserMode,
|
|
FALSE,
|
|
&Timeout );
|
|
FsRtlEnterFileSystem();
|
|
|
|
if (STATUS_TIMEOUT == Status) {
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If irp has been cancelled break out
|
|
//
|
|
|
|
if (IrpContext->OriginatingIrp && IrpContext->OriginatingIrp->Cancel) {
|
|
|
|
#ifdef BENL_DBG
|
|
KdPrint(( "Ntfs: cancelled mft iteration irp: 0x%x\n", IrpContext->OriginatingIrp ));
|
|
#endif
|
|
Status = STATUS_CANCELLED;
|
|
break;
|
|
}
|
|
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
|
|
try {
|
|
|
|
//
|
|
// Acquire the VCB shared and check whether we should
|
|
// continue.
|
|
//
|
|
|
|
if (!NtfsIsVcbAvailable( Vcb )) {
|
|
|
|
//
|
|
// The volume is going away, bail out.
|
|
//
|
|
|
|
Status = STATUS_VOLUME_DISMOUNTED;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Set the irp context flags to indicate that we are in the
|
|
// fsp and that the irp context should not be deleted when
|
|
// complete request or process exception are called. The in
|
|
// fsp flag keeps us from raising in a few places. These
|
|
// flags must be set inside the loop since they are cleared
|
|
// under certain conditions.
|
|
//
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_IN_FSP);
|
|
|
|
DecrementReferenceCount = TRUE;
|
|
|
|
Status = NtfsTryOpenFcb( IrpContext,
|
|
Vcb,
|
|
&CurrentFcb,
|
|
*FileReference );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Call the worker function.
|
|
//
|
|
|
|
Status = FileRecordFunction( IrpContext, CurrentFcb, Context );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Complete the request which commits the pending
|
|
// transaction if there is one and releases of the
|
|
// acquired resources. The IrpContext will not
|
|
// be deleted because the no delete flag is set.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ASSERT(CurrentFcb->ReferenceCount > 0);
|
|
CurrentFcb->ReferenceCount--;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
DecrementReferenceCount = FALSE;
|
|
NtfsTeardownStructures( IrpContext,
|
|
CurrentFcb,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL );
|
|
|
|
} finally {
|
|
|
|
if (CurrentFcb != NULL) {
|
|
|
|
if (DecrementReferenceCount) {
|
|
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ASSERT(CurrentFcb->ReferenceCount > 0);
|
|
CurrentFcb->ReferenceCount--;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
DecrementReferenceCount = FALSE;
|
|
}
|
|
|
|
CurrentFcb = NULL;
|
|
}
|
|
|
|
//
|
|
// Make sure to release any maps in the cached file records in
|
|
// the Irp Context.
|
|
//
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// If a status of not found was return then just continue to
|
|
// the next file record.
|
|
//
|
|
|
|
if (Status == STATUS_NOT_FOUND) {
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Release resources
|
|
//
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_RETAIN_FLAGS );
|
|
NtfsCompleteRequest( IrpContext, NULL, STATUS_SUCCESS );
|
|
|
|
//
|
|
// Advance to the next file record.
|
|
//
|
|
|
|
(*((LONGLONG UNALIGNED *) FileReference))++;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine.
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsDefragMftPriv (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the main worker routine which performs the Mft defragging. This routine
|
|
will defrag according to the following priorities. First try to deallocate the
|
|
tail of the file. Second rewrite the mapping for the file if necessary. Finally
|
|
try to find a range of the Mft that we can turn into a hole. We will only do
|
|
the first and third if we are trying to reclaim disk space. The second we will
|
|
do to try and keep us from getting into trouble while modify Mft records which
|
|
describe the Mft itself.
|
|
|
|
Arguments:
|
|
|
|
Vcb - This is the Vcb for the volume being defragged.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if a defrag operation was successfully done, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
|
|
BOOLEAN CleanupAttributeContext = FALSE;
|
|
BOOLEAN DefragStepTaken = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// We will acquire the Scb for the Mft for this operation.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// If we don't have a reserved record then reserve one now.
|
|
//
|
|
|
|
if (!FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED )) {
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
CleanupAttributeContext = TRUE;
|
|
|
|
//
|
|
// Lookup the bitmap. There is an error if we can't find
|
|
// it.
|
|
//
|
|
|
|
if (!NtfsLookupAttributeByCode( IrpContext,
|
|
Vcb->MftScb->Fcb,
|
|
&Vcb->MftScb->Fcb->FileReference,
|
|
$BITMAP,
|
|
&AttrContext )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
|
|
}
|
|
|
|
(VOID)NtfsReserveMftRecord( IrpContext,
|
|
Vcb,
|
|
&AttrContext );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
CleanupAttributeContext = FALSE;
|
|
}
|
|
|
|
//
|
|
// We now want to test for the three defrag operation we
|
|
// do. Start by checking if we are still trying to
|
|
// recover Mft space for the disk. This is true if
|
|
// have begun defragging and are above the lower threshold
|
|
// or have not begun defragging and are above the upper
|
|
// threshold.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
NtfsCheckForDefrag( Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
|
|
//
|
|
// If we are actively defragging and can deallocate space
|
|
// from the tail of the file then do that. We won't synchronize
|
|
// testing the flag for the defrag state below since making
|
|
// the calls is benign in any case.
|
|
//
|
|
|
|
if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
|
|
|
|
if (NtfsTruncateMft( IrpContext, Vcb )) {
|
|
|
|
try_return( DefragStepTaken = TRUE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Else if we need to rewrite the mapping for the file do
|
|
// so now.
|
|
//
|
|
|
|
if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP )) {
|
|
|
|
if (NtfsRewriteMftMapping( IrpContext,
|
|
Vcb )) {
|
|
|
|
try_return( DefragStepTaken = TRUE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// The last choice is to try to find a candidate for a hole in
|
|
// the file. We will walk backwards from the end of the file.
|
|
//
|
|
|
|
if (NtfsPerforateMft &&
|
|
FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
|
|
|
|
if (NtfsCreateMftHole( IrpContext, Vcb )) {
|
|
|
|
try_return( DefragStepTaken = TRUE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We couldn't do any work to defrag. This means that we can't
|
|
// even try to defrag unless a file record is freed at some
|
|
// point.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
|
|
try_exit: NOTHING;
|
|
} finally {
|
|
|
|
DebugUnwind( NtfsDefragMftPriv );
|
|
|
|
if (CleanupAttributeContext) {
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
}
|
|
|
|
return DefragStepTaken;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
LONG
|
|
NtfsReadMftExceptionFilter (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PEXCEPTION_POINTERS ExceptionPointer,
|
|
IN PBCB Bcb,
|
|
IN LONGLONG FileOffset
|
|
)
|
|
{
|
|
//
|
|
// Check if we support this error,
|
|
// if we didn't fail to totally page in the first time since we need the original
|
|
// to copy the mirror one into, or if the offset isn't within the mirror range
|
|
//
|
|
|
|
if (!FsRtlIsNtstatusExpected( ExceptionPointer->ExceptionRecord->ExceptionCode ) ||
|
|
(Bcb == NULL) ||
|
|
(FileOffset >= IrpContext->Vcb->Mft2Scb->Header.FileSize.QuadPart)) {
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
//
|
|
// Clear the status field in the IrpContext. We're going to retry in the mirror
|
|
//
|
|
|
|
IrpContext->ExceptionStatus = STATUS_SUCCESS;
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
|
|
#if (DBG || defined( NTFS_FREE_ASSERTS ))
|
|
|
|
//
|
|
// Look for a prior entry in the Fcb table for the same value.
|
|
//
|
|
|
|
VOID
|
|
NtfsVerifyFileReference (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PMFT_SEGMENT_REFERENCE MftSegment
|
|
)
|
|
|
|
{
|
|
MFT_SEGMENT_REFERENCE TestReference;
|
|
ULONG Index = 5;
|
|
FCB_TABLE_ELEMENT Key;
|
|
PFCB_TABLE_ELEMENT Entry;
|
|
|
|
TestReference = *MftSegment;
|
|
TestReference.SequenceNumber -= 1;
|
|
|
|
NtfsAcquireFcbTable( NULL, IrpContext->Vcb );
|
|
|
|
while((TestReference.SequenceNumber != 0) && (Index != 0)) {
|
|
|
|
Key.FileReference = TestReference;
|
|
|
|
if ((Entry = RtlLookupElementGenericTable( &IrpContext->Vcb->FcbTable, &Key )) != NULL) {
|
|
|
|
//
|
|
// Let's be optimistic and do an unsafe check. If we can't get the resource,
|
|
// we'll just assume that it's in the process of getting deleted.
|
|
//
|
|
|
|
if (!FlagOn( Entry->Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
|
|
|
|
if (NtfsAcquireResourceExclusive( IrpContext, Entry->Fcb, FALSE )) {
|
|
|
|
//
|
|
// Either the Fcb should be marked as deleted or there should be no
|
|
// Scbs lying around to flush.
|
|
//
|
|
|
|
if (!FlagOn( Entry->Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
|
|
|
|
PLIST_ENTRY Links;
|
|
PSCB NextScb;
|
|
|
|
Links = Entry->Fcb->ScbQueue.Flink;
|
|
|
|
//
|
|
// We don't care if there are Scb's as long as none of them
|
|
// represent real data.
|
|
//
|
|
|
|
while (Links != &Entry->Fcb->ScbQueue) {
|
|
|
|
NextScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
if (NextScb->AttributeTypeCode != $UNUSED) {
|
|
|
|
break;
|
|
}
|
|
|
|
Links = Links->Flink;
|
|
}
|
|
|
|
//
|
|
// Leave the test for deleted in the assert message so the debugger output
|
|
// is more descriptive.
|
|
//
|
|
|
|
ASSERT( FlagOn( Entry->Fcb->FcbState, FCB_STATE_FILE_DELETED ) ||
|
|
(Links == &Entry->Fcb->ScbQueue) );
|
|
}
|
|
NtfsReleaseResource( IrpContext, Entry->Fcb );
|
|
}
|
|
}
|
|
}
|
|
|
|
Index -= 1;
|
|
TestReference.SequenceNumber -= 1;
|
|
}
|
|
|
|
NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
|
|
return;
|
|
}
|
|
|
|
#endif
|