mirror of https://github.com/tongzx/nt5src
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.
4937 lines
157 KiB
4937 lines
157 KiB
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
UsnSup.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the Usn Journal support routines for NtOfs
|
|
|
|
Author:
|
|
|
|
Tom Miller [TomM] 1-Dec-1996
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
#include "lockorder.h"
|
|
|
|
//
|
|
// Define a tag for general pool allocations from this module
|
|
//
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('UFtN')
|
|
|
|
#define GENERATE_CLOSE_RECORD_LIMIT (200)
|
|
|
|
UNICODE_STRING $Max = CONSTANT_UNICODE_STRING( L"$Max" );
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS
|
|
NtfsUsnTableCompare (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
PVOID FirstStruct,
|
|
PVOID SecondStruct
|
|
);
|
|
|
|
PVOID
|
|
NtfsUsnTableAllocate (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
CLONG ByteSize
|
|
);
|
|
|
|
VOID
|
|
NtfsUsnTableFree (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
PVOID Buffer
|
|
);
|
|
|
|
VOID
|
|
NtfsCancelReadUsnJournal (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
);
|
|
|
|
VOID
|
|
NtfsCancelDeleteUsnJournal (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
);
|
|
|
|
NTSTATUS
|
|
NtfsDeleteUsnWorker (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PVOID Context
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsValidateUsnPage (
|
|
IN PUSN_RECORD UsnRecord,
|
|
IN USN PageUsn,
|
|
IN USN *UserStartUsn OPTIONAL,
|
|
IN LONGLONG UsnFileSize,
|
|
OUT PBOOLEAN ValidUserStartUsn OPTIONAL,
|
|
OUT USN *NextUsn
|
|
);
|
|
|
|
//
|
|
// VOID
|
|
// NtfsAdvanceUsnJournal (
|
|
// PVCB Vcb,
|
|
// PUSN_JOURNAL_INSTANCE UsnJournalInstance,
|
|
// LONGLONG OldSize,
|
|
// PBOOLEAN NewMax
|
|
// );
|
|
//
|
|
|
|
#define NtfsAdvanceUsnJournal(V,I,SZ,M) { \
|
|
ULONG _TempUlong; \
|
|
_TempUlong = USN_PAGE_BOUNDARY; \
|
|
if (USN_PAGE_BOUNDARY < (V)->BytesPerCluster) { \
|
|
_TempUlong = (V)->BytesPerCluster; \
|
|
} \
|
|
(I)->LowestValidUsn = SZ + _TempUlong - 1; \
|
|
((PLARGE_INTEGER) &(I)->LowestValidUsn)->LowPart &= ~(_TempUlong - 1); \
|
|
KeQuerySystemTime( (PLARGE_INTEGER) &(I)->JournalId ); \
|
|
*(M) = TRUE; \
|
|
}
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsDeleteUsnJournal)
|
|
#pragma alloc_text(PAGE, NtfsDeleteUsnSpecial)
|
|
#pragma alloc_text(PAGE, NtfsDeleteUsnWorker)
|
|
#pragma alloc_text(PAGE, NtfsPostUsnChange)
|
|
#pragma alloc_text(PAGE, NtfsQueryUsnJournal)
|
|
#pragma alloc_text(PAGE, NtfsReadUsnJournal)
|
|
#pragma alloc_text(PAGE, NtfsSetupUsnJournal)
|
|
#pragma alloc_text(PAGE, NtfsTrimUsnJournal)
|
|
#pragma alloc_text(PAGE, NtfsUsnTableCompare)
|
|
#pragma alloc_text(PAGE, NtfsUsnTableAllocate)
|
|
#pragma alloc_text(PAGE, NtfsUsnTableFree)
|
|
#pragma alloc_text(PAGE, NtfsValidateUsnPage)
|
|
#pragma alloc_text(PAGE, NtfsWriteUsnJournalChanges)
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
NtfsReadUsnJournal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN BOOLEAN ProbeInput
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads records filtered from the Usn journal.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - Only optional if we are being called to cancel an async
|
|
request.
|
|
|
|
Irp - request being serviced
|
|
|
|
ProbeInput - Indicates if we should probe the user input buffer. We also
|
|
call this routine internally and don't want to probe in that case.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation.
|
|
STATUS_PENDING - if asynch Irp queued for later completion.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUSN_RECORD UsnRecord;
|
|
USN_RECORD UNALIGNED *OutputUsnRecord;
|
|
PVOID UserBuffer;
|
|
LONGLONG ViewLength;
|
|
ULONG RemainingUserBuffer, BytesUsed;
|
|
MAP_HANDLE MapHandle;
|
|
|
|
READ_USN_JOURNAL_DATA CapturedData;
|
|
PSCB UsnJournal;
|
|
ULONG JournalAcquired = FALSE;
|
|
ULONG AccessingUserBuffer = FALSE;
|
|
ULONG DecrementReferenceCount = FALSE;
|
|
ULONG VcbAcquired = FALSE;
|
|
ULONG Wait;
|
|
ULONG OriginalWait;
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PFILE_OBJECT FileObject;
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current Irp stack location and save some references.
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Extract and decode the file object and check for type of open.
|
|
//
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
|
|
|
|
if ((Ccb == NULL) || !FlagOn( Ccb->AccessFlags, MANAGE_VOLUME_ACCESS)) {
|
|
|
|
ASSERT( ProbeInput );
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_ACCESS_DENIED );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// This request must be able to wait for resources. Set WAIT to TRUE.
|
|
//
|
|
|
|
Wait = TRUE;
|
|
if (ProbeInput && !FlagOn( IrpContext->State, IRP_CONTEXT_STATE_WAIT )) {
|
|
|
|
Wait = FALSE;
|
|
}
|
|
|
|
NtOfsInitializeMapHandle( &MapHandle );
|
|
UserBuffer = NtfsMapUserBuffer( Irp );
|
|
|
|
try {
|
|
|
|
//
|
|
// We always want to be able to wait for resources in this routine but need to be able
|
|
// to restore the original wait value in the Irp. After this the original wait will
|
|
// have only the wait flag set and then only if it originally wasn't set. In clean
|
|
// up we just need to clear the irp context flags using this mask.
|
|
//
|
|
|
|
OriginalWait = (IrpContext->State ^ IRP_CONTEXT_STATE_WAIT) & IRP_CONTEXT_STATE_WAIT;
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
//
|
|
// Detect if we fail while accessing the input buffer.
|
|
//
|
|
|
|
try {
|
|
|
|
AccessingUserBuffer = TRUE;
|
|
|
|
//
|
|
// Probe the input buffer if not in kernel mode and we haven't already done so.
|
|
//
|
|
|
|
if (Irp->RequestorMode != KernelMode) {
|
|
|
|
if (ProbeInput) {
|
|
|
|
ProbeForRead( IrpSp->Parameters.FileSystemControl.Type3InputBuffer,
|
|
IrpSp->Parameters.FileSystemControl.InputBufferLength,
|
|
sizeof( ULONG ));
|
|
}
|
|
|
|
//
|
|
// Probe the output buffer if we haven't locked it down yet.
|
|
// Capture the JournalData from the unsafe user buffer.
|
|
//
|
|
|
|
if (Irp->MdlAddress == NULL) {
|
|
|
|
ProbeForWrite( UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, sizeof( ULONG ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Acquire the Vcb to serialize journal operations with delete journal and dismount.
|
|
// Only do this if are being called directly by the user.
|
|
//
|
|
|
|
if (ProbeInput) {
|
|
|
|
VcbAcquired = NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
|
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE )) {
|
|
|
|
UsnJournal = NULL;
|
|
|
|
} else {
|
|
|
|
UsnJournal = Vcb->UsnJournal;
|
|
}
|
|
|
|
} else {
|
|
|
|
UsnJournal = Vcb->UsnJournal;
|
|
}
|
|
|
|
//
|
|
// Make sure no one is deleting the journal.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_DELETE )) {
|
|
|
|
Status = STATUS_JOURNAL_DELETE_IN_PROGRESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Also check that the version is still active.
|
|
//
|
|
|
|
if (UsnJournal == NULL) {
|
|
|
|
Status = STATUS_JOURNAL_NOT_ACTIVE;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Check that the buffer sizes meet our minimum needs.
|
|
//
|
|
|
|
if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof( READ_USN_JOURNAL_DATA )) {
|
|
|
|
Status = STATUS_INVALID_USER_BUFFER;
|
|
leave;
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( &CapturedData,
|
|
IrpSp->Parameters.FileSystemControl.Type3InputBuffer,
|
|
sizeof( READ_USN_JOURNAL_DATA ));
|
|
|
|
//
|
|
// Check that the user is querying with the correct journal ID.
|
|
//
|
|
|
|
if (CapturedData.UsnJournalID != Vcb->UsnJournalInstance.JournalId) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check that the output buffer can hold at least one USN.
|
|
//
|
|
|
|
if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof( USN )) {
|
|
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
leave;
|
|
}
|
|
|
|
AccessingUserBuffer = FALSE;
|
|
|
|
//
|
|
// Set up for filling output records
|
|
//
|
|
|
|
RemainingUserBuffer = IrpSp->Parameters.FileSystemControl.OutputBufferLength - sizeof(USN);
|
|
OutputUsnRecord = (PUSN_RECORD) Add2Ptr( UserBuffer, sizeof(USN) );
|
|
BytesUsed = sizeof(USN);
|
|
|
|
NtfsAcquireResourceShared( IrpContext, UsnJournal, TRUE );
|
|
JournalAcquired = TRUE;
|
|
|
|
if (VcbAcquired) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
VcbAcquired = FALSE;
|
|
}
|
|
|
|
//
|
|
// Verify the volume is mounted.
|
|
//
|
|
|
|
if (FlagOn( UsnJournal->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
|
|
Status = STATUS_VOLUME_DISMOUNTED;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If 0 was specified as the Usn, then translate that to the first record
|
|
// in the Usn journal.
|
|
//
|
|
|
|
if (CapturedData.StartUsn == 0) {
|
|
CapturedData.StartUsn = Vcb->FirstValidUsn;
|
|
}
|
|
|
|
//
|
|
// Loop here until he gets some data, if that is what the caller wants.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// Make sure he is within the stream.
|
|
//
|
|
|
|
if (CapturedData.StartUsn < Vcb->FirstValidUsn) {
|
|
CapturedData.StartUsn = Vcb->FirstValidUsn;
|
|
Status = STATUS_JOURNAL_ENTRY_DELETED;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Make sure he is within the stream.
|
|
//
|
|
|
|
if (CapturedData.StartUsn >= UsnJournal->Header.FileSize.QuadPart) {
|
|
|
|
//
|
|
// If he wants to wait for data, then wait here.
|
|
//
|
|
// If an asynchronous request has
|
|
// met its wakeup condition, then this Irp will not be the same as the
|
|
// Originating Irp, and we do not want to give him a second chance since
|
|
// this could cause us to loop in NtOfsPostNewLength. (Basically the only
|
|
// case where this could happen anyway is if he gave us a bogus StartUsn
|
|
// which is too high.)
|
|
//
|
|
|
|
if (CapturedData.BytesToWaitFor != 0) {
|
|
|
|
//
|
|
// Make sure the journal doesn't get deleted while
|
|
// this Irp is outstanding.
|
|
//
|
|
|
|
InterlockedIncrement( &UsnJournal->CloseCount );
|
|
DecrementReferenceCount = TRUE;
|
|
|
|
//
|
|
// If the caller does not want to wait, then just queue his
|
|
// Irp to be completed when sufficient bytes come in. If we were
|
|
// called for another Irp, then do the same, since we know that
|
|
// was another async Irp.
|
|
//
|
|
|
|
if (!Wait || (Irp != IrpContext->OriginatingIrp)) {
|
|
|
|
//
|
|
// Now set up our wait block, capturing the user's parameters.
|
|
// Update the Irp to say where the input parameters are now.
|
|
//
|
|
|
|
Status = NtfsHoldIrpForNewLength( IrpContext,
|
|
UsnJournal,
|
|
Irp,
|
|
CapturedData.StartUsn + CapturedData.BytesToWaitFor,
|
|
NtfsCancelReadUsnJournal,
|
|
&CapturedData,
|
|
&IrpSp->Parameters.FileSystemControl.Type3InputBuffer,
|
|
sizeof( READ_USN_JOURNAL_DATA ));
|
|
|
|
//
|
|
// If pending then someone else will decrement the reference count.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
|
|
DecrementReferenceCount = FALSE;
|
|
}
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// We can safely release the resource. Our reference on the Scb above
|
|
// will keep it from being deleted.
|
|
//
|
|
|
|
NtfsReleaseResource( IrpContext, UsnJournal );
|
|
JournalAcquired = FALSE;
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
Status = NtOfsWaitForNewLength( UsnJournal,
|
|
CapturedData.StartUsn + CapturedData.BytesToWaitFor,
|
|
FALSE,
|
|
Irp,
|
|
NtfsCancelReadUsnJournal,
|
|
((CapturedData.Timeout != 0) ?
|
|
(PLARGE_INTEGER) &CapturedData.Timeout :
|
|
NULL) );
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
//
|
|
// Get out in the error case.
|
|
//
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Acquire the resource to proceed with the request.
|
|
//
|
|
|
|
NtfsAcquireResourceShared( IrpContext, UsnJournal, TRUE );
|
|
JournalAcquired = TRUE;
|
|
|
|
//
|
|
// Decrement our reference on the Scb.
|
|
//
|
|
|
|
InterlockedDecrement( &UsnJournal->CloseCount );
|
|
DecrementReferenceCount = FALSE;
|
|
|
|
//
|
|
// The journal may have been deleted while we weren't holding
|
|
// anything.
|
|
//
|
|
|
|
if (UsnJournal != UsnJournal->Vcb->UsnJournal) {
|
|
|
|
if (FlagOn( UsnJournal->Vcb->VcbState, VCB_STATE_USN_DELETE )) {
|
|
Status = STATUS_JOURNAL_DELETE_IN_PROGRESS;
|
|
} else {
|
|
Status = STATUS_JOURNAL_NOT_ACTIVE;
|
|
}
|
|
leave;
|
|
}
|
|
|
|
ASSERT( Status == STATUS_SUCCESS );
|
|
|
|
//
|
|
// **** Get out if we are shutting down the volume.
|
|
//
|
|
|
|
// if (ShuttingDown) {
|
|
// Status = STATUS_TOO_LATE;
|
|
// leave;
|
|
// }
|
|
|
|
//
|
|
// Otherwise, get out. Note, we may have processed a number of records
|
|
// that did not match his filter criteria, so we will return success, so
|
|
// we can at least give him an updated Usn so we do not have to skip over
|
|
// all those records again.
|
|
//
|
|
|
|
} else {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through as many views as required to fill the output buffer.
|
|
//
|
|
|
|
while ((RemainingUserBuffer != 0) && (CapturedData.StartUsn < UsnJournal->Header.FileSize.QuadPart)) {
|
|
|
|
LONGLONG BiasedStartUsn;
|
|
BOOLEAN ValidUserStartUsn;
|
|
USN NextUsn;
|
|
ULONG RecordSize;
|
|
|
|
//
|
|
// Calculate length to process in this view.
|
|
//
|
|
|
|
ViewLength = UsnJournal->Header.FileSize.QuadPart - CapturedData.StartUsn;
|
|
if (ViewLength > (VACB_MAPPING_GRANULARITY - (ULONG)(CapturedData.StartUsn & (VACB_MAPPING_GRANULARITY - 1)))) {
|
|
ViewLength = VACB_MAPPING_GRANULARITY - (ULONG)(CapturedData.StartUsn & (VACB_MAPPING_GRANULARITY - 1));
|
|
}
|
|
|
|
//
|
|
// Map the view containing the desired Usn.
|
|
//
|
|
|
|
BiasedStartUsn = CapturedData.StartUsn - Vcb->UsnCacheBias;
|
|
NtOfsMapAttribute( IrpContext, UsnJournal, BiasedStartUsn, (ULONG)ViewLength, (PVOID *)&UsnRecord, &MapHandle );
|
|
|
|
//
|
|
// For each page in the view we want to validate the page and return the records
|
|
// within the page starting at the user's current usn.
|
|
//
|
|
|
|
do {
|
|
|
|
//
|
|
// Validate the records on the entire page are valid.
|
|
//
|
|
|
|
if (!NtfsValidateUsnPage( (PUSN_RECORD) BlockAlignTruncate( ((ULONG_PTR) UsnRecord), USN_PAGE_SIZE ),
|
|
BlockAlignTruncate( CapturedData.StartUsn, USN_PAGE_SIZE ),
|
|
&CapturedData.StartUsn,
|
|
UsnJournal->Header.FileSize.QuadPart,
|
|
&ValidUserStartUsn,
|
|
&NextUsn )) {
|
|
|
|
//
|
|
// Simply fail the request with bad data.
|
|
//
|
|
|
|
Status = STATUS_DATA_ERROR;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If the user gave us an incorrect Usn then fail the request.
|
|
//
|
|
|
|
if (!ValidUserStartUsn) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Now loop to process this page. We know the Usn values which exist on the page and
|
|
// there are no checks for valid data needed.
|
|
//
|
|
|
|
while (CapturedData.StartUsn < NextUsn) {
|
|
|
|
RecordSize = UsnRecord->RecordLength;
|
|
|
|
//
|
|
// Only recognize version 2 records.
|
|
//
|
|
|
|
if (FlagOn( UsnRecord->Reason, CapturedData.ReasonMask ) &&
|
|
(!CapturedData.ReturnOnlyOnClose || FlagOn( UsnRecord->Reason, USN_REASON_CLOSE )) &&
|
|
(UsnRecord->MajorVersion == 2)) {
|
|
|
|
if (RecordSize > RemainingUserBuffer) {
|
|
RemainingUserBuffer = 0;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Copy the data back to the unsafe user buffer.
|
|
//
|
|
|
|
AccessingUserBuffer = TRUE;
|
|
|
|
//
|
|
// Copy directly if the version numbers match.
|
|
//
|
|
|
|
RtlCopyMemory( OutputUsnRecord, UsnRecord, RecordSize );
|
|
|
|
AccessingUserBuffer = FALSE;
|
|
|
|
RemainingUserBuffer -= RecordSize;
|
|
BytesUsed += RecordSize;
|
|
OutputUsnRecord = Add2Ptr( OutputUsnRecord, RecordSize );
|
|
}
|
|
|
|
CapturedData.StartUsn += RecordSize;
|
|
UsnRecord = Add2Ptr( UsnRecord, RecordSize );
|
|
|
|
//
|
|
// The view length should already account for record size.
|
|
//
|
|
|
|
ASSERT( ViewLength >= RecordSize );
|
|
ViewLength -= RecordSize;
|
|
}
|
|
|
|
//
|
|
// Break out if the users buffer is empty.
|
|
//
|
|
|
|
if (RemainingUserBuffer == 0) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// We finished the current page. Now move to the next page.
|
|
// Figure out how many bytes remain on this page.
|
|
// If the next offset is the start of the next page then make sure
|
|
// to mask off the page size bits again.
|
|
//
|
|
|
|
RecordSize = BlockOffset( USN_PAGE_SIZE - BlockOffset( (ULONG) NextUsn, USN_PAGE_SIZE ),
|
|
USN_PAGE_SIZE );
|
|
|
|
if (RecordSize > ViewLength) {
|
|
|
|
RecordSize = (ULONG) ViewLength;
|
|
}
|
|
|
|
UsnRecord = Add2Ptr( UsnRecord, RecordSize );
|
|
CapturedData.StartUsn += RecordSize;
|
|
ViewLength -= RecordSize;
|
|
|
|
} while (ViewLength != 0);
|
|
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
}
|
|
|
|
} while ((RemainingUserBuffer != 0) && (BytesUsed == sizeof(USN)));
|
|
|
|
Irp->IoStatus.Information = BytesUsed;
|
|
|
|
//
|
|
// Set the returned Usn. Move to the start of the next page if
|
|
// the next record won't fit on this page.
|
|
//
|
|
|
|
AccessingUserBuffer = TRUE;
|
|
*(USN *)UserBuffer = CapturedData.StartUsn;
|
|
AccessingUserBuffer = FALSE;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
Status = GetExceptionCode();
|
|
|
|
//
|
|
// Restore the original wait state back into the IrpContext.
|
|
//
|
|
|
|
ClearFlag( IrpContext->State, OriginalWait );
|
|
|
|
if (FsRtlIsNtstatusExpected( Status )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
|
|
} else {
|
|
|
|
ExRaiseStatus( AccessingUserBuffer ? STATUS_INVALID_USER_BUFFER : Status );
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
|
|
if (JournalAcquired) {
|
|
NtfsReleaseResource( IrpContext, UsnJournal );
|
|
}
|
|
|
|
if (DecrementReferenceCount) {
|
|
|
|
InterlockedDecrement( &UsnJournal->CloseCount );
|
|
}
|
|
|
|
if (VcbAcquired) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Complete the request, unless we've marked this Irp as pending and we plan to complete
|
|
// it later. If the Irp is not the originating Irp then it belongs to another request
|
|
// and we don't want to complete it.
|
|
//
|
|
|
|
//
|
|
// Restore the original wait flag back into the IrpContext.
|
|
//
|
|
|
|
ClearFlag( IrpContext->State, OriginalWait );
|
|
|
|
ASSERT( (Status == STATUS_PENDING) || (Irp->CancelRoutine == NULL) );
|
|
|
|
NtfsCompleteRequest( (Irp == IrpContext->OriginatingIrp) ? IrpContext : NULL,
|
|
(Status != STATUS_PENDING) ? Irp : NULL,
|
|
Status );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
ULONG
|
|
NtfsPostUsnChange (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVOID ScbOrFcb,
|
|
IN ULONG Reason
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to post a set of changes to a file. A change is
|
|
only posted if at least one reason in the Reason mask is not already set
|
|
in either the Fcb or the IrpContext or if we are changing the source info
|
|
reasons in the Fcb.
|
|
|
|
Arguments:
|
|
|
|
ScbOrFcb - Supplies the file for which a change is being posted. If reason contains
|
|
USN_REASON_DATA_xxx reasons, then it must be an Scb, because we transform
|
|
the code for named streams and do other special handling.
|
|
|
|
Reason - Supplies a mask of reasons for which a change is being posted.
|
|
|
|
Return Value:
|
|
|
|
Nonzero if changes are actually posted from this or a previous call
|
|
|
|
--*/
|
|
|
|
{
|
|
PLCB Lcb;
|
|
PFCB_USN_RECORD FcbUsnRecord;
|
|
BOOLEAN Found;
|
|
PFCB Fcb;
|
|
PSCB Scb = NULL;
|
|
ULONG NewReasons;
|
|
ULONG RemovedSourceInfo;
|
|
PUSN_FCB ThisUsn;
|
|
BOOLEAN LockedFcb = FALSE;
|
|
BOOLEAN AcquiredFcb = FALSE;
|
|
|
|
//
|
|
// Assume we got an Fcb.
|
|
//
|
|
|
|
Fcb = (PFCB)ScbOrFcb;
|
|
|
|
ASSERT( !(Reason & (USN_REASON_DATA_OVERWRITE | USN_REASON_DATA_EXTEND | USN_REASON_DATA_TRUNCATION)) ||
|
|
(NTFS_NTC_FCB != Fcb->NodeTypeCode) );
|
|
|
|
//
|
|
// Switch if we got an Scb
|
|
//
|
|
|
|
if (Fcb->NodeTypeCode != NTFS_NTC_FCB) {
|
|
|
|
ASSERT_SCB(Fcb);
|
|
|
|
Scb = (PSCB)ScbOrFcb;
|
|
Fcb = Scb->Fcb;
|
|
}
|
|
|
|
//
|
|
// We better be holding some resource.
|
|
//
|
|
|
|
ASSERT( !IsListEmpty( &IrpContext->ExclusiveFcbList ) ||
|
|
((Fcb->PagingIoResource != NULL) && NtfsIsSharedFcbPagingIo( Fcb )) ||
|
|
NtfsIsSharedFcb( Fcb ) );
|
|
|
|
//
|
|
// If there is a Usn Journal and its not a system file setup the memory structures
|
|
// to hold the usn reasons
|
|
//
|
|
|
|
ThisUsn = &IrpContext->Usn;
|
|
|
|
if (FlagOn( Fcb->Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE ) &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
|
|
|
|
//
|
|
// First see if we have a Usn record structure already for this file. We might need
|
|
// the whole usn record or simply the name. If this is the RENAME_NEW_NAME record
|
|
// then find then name again as well.
|
|
//
|
|
|
|
if ((Fcb->FcbUsnRecord == NULL) ||
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_VALID_USN_NAME ) ||
|
|
FlagOn( Reason, USN_REASON_RENAME_NEW_NAME )) {
|
|
|
|
ULONG SizeToAllocate;
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
|
|
PFILE_NAME FileName = NULL;
|
|
|
|
NtfsInitializeAttributeContext( &AttributeContext );
|
|
|
|
try {
|
|
|
|
//
|
|
// First we have to find the designated file name. If we are lucky
|
|
// it is in an Lcb. We cannot do this in the case of rename, because
|
|
// the in-memory stuff is not fixed up yet.
|
|
//
|
|
|
|
if (!FlagOn( Reason, USN_REASON_RENAME_NEW_NAME )) {
|
|
|
|
Lcb = (PLCB)CONTAINING_RECORD( Fcb->LcbQueue.Flink, LCB, FcbLinks );
|
|
while (&Lcb->FcbLinks.Flink != &Fcb->LcbQueue.Flink) {
|
|
|
|
//
|
|
// If this is the designated file name, then we can get out pointing
|
|
// to the FILE_NAME in the Lcb.
|
|
//
|
|
|
|
if (FlagOn( Lcb->LcbState, LCB_STATE_DESIGNATED_LINK )) {
|
|
FileName = (PFILE_NAME)&Lcb->ParentDirectory;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Advance to next Lcb.
|
|
//
|
|
|
|
Lcb = (PLCB)CONTAINING_RECORD( Lcb->FcbLinks.Flink, LCB, FcbLinks );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we did not find the file name the easy way, then we have to go
|
|
// get it.
|
|
//
|
|
|
|
if (FileName == NULL) {
|
|
|
|
//
|
|
// Acquire some synchronization against the filerecord
|
|
//
|
|
|
|
NtfsAcquireResourceShared( IrpContext, Fcb, TRUE );
|
|
AcquiredFcb = TRUE;
|
|
|
|
//
|
|
// Now scan for the filename attribute we need.
|
|
//
|
|
|
|
Found = NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$FILE_NAME,
|
|
&AttributeContext );
|
|
|
|
while (Found) {
|
|
|
|
FileName = (PFILE_NAME)NtfsAttributeValue( NtfsFoundAttribute(&AttributeContext) );
|
|
|
|
if (!FlagOn(FileName->Flags, FILE_NAME_DOS) || FlagOn(FileName->Flags, FILE_NAME_NTFS)) {
|
|
break;
|
|
}
|
|
|
|
Found = NtfsLookupNextAttributeByCode( IrpContext,
|
|
Fcb,
|
|
$FILE_NAME,
|
|
&AttributeContext );
|
|
}
|
|
|
|
//
|
|
// If there is no file name, raise corrupt!
|
|
//
|
|
|
|
if (FileName == NULL) {
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Lock the Fcb so the record can't go away.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
LockedFcb = TRUE;
|
|
|
|
//
|
|
// Now test for the need for a new record and construct one
|
|
// if necc. Prev. test was unsafe for checking the Fcb->FcbUsnRecord
|
|
//
|
|
|
|
if ((Fcb->FcbUsnRecord == NULL) ||
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_VALID_USN_NAME ) ||
|
|
FlagOn( Reason, USN_REASON_RENAME_NEW_NAME )) {
|
|
|
|
//
|
|
// Calculate the size required for the record and allocate a new record.
|
|
//
|
|
|
|
SizeToAllocate = sizeof( FCB_USN_RECORD ) + (FileName->FileNameLength * sizeof(WCHAR));
|
|
FcbUsnRecord = NtfsAllocatePool( PagedPool, SizeToAllocate );
|
|
|
|
//
|
|
// Zero and initialize the new usn record.
|
|
//
|
|
|
|
RtlZeroMemory( FcbUsnRecord, SizeToAllocate );
|
|
|
|
FcbUsnRecord->NodeTypeCode = NTFS_NTC_USN_RECORD;
|
|
FcbUsnRecord->NodeByteSize = (USHORT)QuadAlign(FIELD_OFFSET( FCB_USN_RECORD, UsnRecord.FileName ) +
|
|
(FileName->FileNameLength * sizeof(WCHAR)));
|
|
FcbUsnRecord->Fcb = Fcb;
|
|
|
|
FcbUsnRecord->UsnRecord.RecordLength = FcbUsnRecord->NodeByteSize -
|
|
FIELD_OFFSET( FCB_USN_RECORD, UsnRecord );
|
|
|
|
FcbUsnRecord->UsnRecord.MajorVersion = 2;
|
|
FcbUsnRecord->UsnRecord.FileReferenceNumber = *(PULONGLONG)&Fcb->FileReference;
|
|
FcbUsnRecord->UsnRecord.ParentFileReferenceNumber = *(PULONGLONG)&FileName->ParentDirectory;
|
|
FcbUsnRecord->UsnRecord.SecurityId = Fcb->SecurityId;
|
|
FcbUsnRecord->UsnRecord.FileNameLength = FileName->FileNameLength * 2;
|
|
FcbUsnRecord->UsnRecord.FileNameOffset = FIELD_OFFSET( USN_RECORD, FileName );
|
|
|
|
RtlCopyMemory( FcbUsnRecord->UsnRecord.FileName,
|
|
FileName->FileName,
|
|
FileName->FileNameLength * 2 );
|
|
|
|
//
|
|
// If the record is there then copy the existing reasons and source info.
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord != NULL) {
|
|
|
|
FcbUsnRecord->UsnRecord.Reason = Fcb->FcbUsnRecord->UsnRecord.Reason;
|
|
FcbUsnRecord->UsnRecord.SourceInfo = Fcb->FcbUsnRecord->UsnRecord.SourceInfo;
|
|
|
|
//
|
|
// Deallocate the existing block if still there.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb->Vcb->UsnJournal->Fcb );
|
|
|
|
//
|
|
// Put the new block into the modified list if the current one is
|
|
// already there.
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord->ModifiedOpenFilesLinks.Flink != NULL) {
|
|
|
|
InsertTailList( &Fcb->FcbUsnRecord->ModifiedOpenFilesLinks,
|
|
&FcbUsnRecord->ModifiedOpenFilesLinks );
|
|
RemoveEntryList( &Fcb->FcbUsnRecord->ModifiedOpenFilesLinks );
|
|
|
|
if (Fcb->FcbUsnRecord->TimeOutLinks.Flink != NULL) {
|
|
|
|
InsertTailList( &Fcb->FcbUsnRecord->TimeOutLinks,
|
|
&FcbUsnRecord->TimeOutLinks );
|
|
RemoveEntryList( &Fcb->FcbUsnRecord->TimeOutLinks );
|
|
}
|
|
}
|
|
|
|
NtfsFreePool( Fcb->FcbUsnRecord );
|
|
Fcb->FcbUsnRecord = FcbUsnRecord;
|
|
NtfsUnlockFcb( IrpContext, Fcb->Vcb->UsnJournal->Fcb );
|
|
|
|
//
|
|
// Otherwise this is a new usn structure.
|
|
//
|
|
|
|
} else {
|
|
|
|
Fcb->FcbUsnRecord = FcbUsnRecord;
|
|
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// We are going to reuse the current fcb record in this path.
|
|
// This can happen in races between the write path which has only paged sharing
|
|
// and the close record path which has only main exclusive. In this
|
|
// case the only synchronization we have is the fcb->mutex
|
|
// The old usnrecord should be identical to the current one we would have constructed
|
|
//
|
|
|
|
ASSERT( FileName->FileNameLength * 2 == Fcb->FcbUsnRecord->UsnRecord.FileNameLength );
|
|
ASSERT( RtlEqualMemory( FileName->FileName, Fcb->FcbUsnRecord->UsnRecord.FileName, Fcb->FcbUsnRecord->UsnRecord.FileNameLength ) );
|
|
}
|
|
|
|
//
|
|
// Set the flag indicating that the Usn name is valid.
|
|
//
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_VALID_USN_NAME );
|
|
|
|
|
|
} finally {
|
|
|
|
if (LockedFcb) {
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
}
|
|
if (AcquiredFcb) {
|
|
NtfsReleaseResource( IrpContext, Fcb );
|
|
}
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have memory structures for the usn reasons fill in the new reasons
|
|
// Note: this means that journal may not be active at this point. We will always
|
|
// accumulate reasons once we have started
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord != NULL) {
|
|
|
|
//
|
|
// Scan the list to see if we already have an entry for this Fcb. If there are
|
|
// no entries then use the position in the IrpContext, otherwise allocate a USN_FCB
|
|
// and chain this into the IrpContext. Typical case for this is rename.
|
|
//
|
|
|
|
do {
|
|
|
|
if (ThisUsn->CurrentUsnFcb == Fcb) { break; }
|
|
|
|
//
|
|
// Check if we are at the last entry then we want to use the entry in the
|
|
// IrpContext.
|
|
//
|
|
|
|
if (ThisUsn->CurrentUsnFcb == NULL) {
|
|
|
|
RtlZeroMemory( &ThisUsn->CurrentUsnFcb,
|
|
sizeof( USN_FCB ) - FIELD_OFFSET( USN_FCB, CurrentUsnFcb ));
|
|
ThisUsn->CurrentUsnFcb = Fcb;
|
|
break;
|
|
}
|
|
|
|
if (ThisUsn->NextUsnFcb == NULL) {
|
|
|
|
//
|
|
// Allocate a new entry.
|
|
//
|
|
|
|
ThisUsn->NextUsnFcb = NtfsAllocatePool( PagedPool, sizeof( USN_FCB ));
|
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|
|
|
RtlZeroMemory( ThisUsn, sizeof( USN_FCB ));
|
|
ThisUsn->CurrentUsnFcb = Fcb;
|
|
break;
|
|
}
|
|
|
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// If the Reason is one of the data stream reasons, and this is the named data
|
|
// steam, then change the code.
|
|
//
|
|
|
|
ASSERT(USN_REASON_NAMED_DATA_OVERWRITE == (USN_REASON_DATA_OVERWRITE << 4));
|
|
ASSERT(USN_REASON_NAMED_DATA_EXTEND == (USN_REASON_DATA_EXTEND << 4));
|
|
ASSERT(USN_REASON_NAMED_DATA_TRUNCATION == (USN_REASON_DATA_TRUNCATION << 4));
|
|
|
|
if ((Reason & (USN_REASON_DATA_OVERWRITE | USN_REASON_DATA_EXTEND | USN_REASON_DATA_TRUNCATION)) &&
|
|
(Scb->AttributeName.Length != 0)) {
|
|
|
|
//
|
|
// If any flag other than these three are set already, the shift will make
|
|
// them look like other flags. For instance, USN_REASON_NAMED_DATA_EXTEND
|
|
// will become USN_REASON_FILE_DELETE, which will cause a number of problems.
|
|
//
|
|
|
|
ASSERT(!FlagOn( Reason, ~(USN_REASON_DATA_OVERWRITE | USN_REASON_DATA_EXTEND | USN_REASON_DATA_TRUNCATION) ));
|
|
|
|
Reason <<= 4;
|
|
}
|
|
|
|
//
|
|
// If there are no new reasons, then we can ignore this change.
|
|
//
|
|
// We will generate a new record if the SourceInfo indicates some
|
|
// change to the source info in the record.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// The rename flags are the only ones that do not accumulate until final close, since
|
|
// we write records designating old and new names. So if we are writing one flag
|
|
// we must clear the other.
|
|
//
|
|
|
|
if (FlagOn(Reason, USN_REASON_RENAME_OLD_NAME | USN_REASON_RENAME_NEW_NAME)) {
|
|
|
|
ClearFlag( ThisUsn->NewReasons,
|
|
(Reason ^ (USN_REASON_RENAME_OLD_NAME | USN_REASON_RENAME_NEW_NAME)) );
|
|
ClearFlag( Fcb->FcbUsnRecord->UsnRecord.Reason,
|
|
(Reason ^ (USN_REASON_RENAME_OLD_NAME | USN_REASON_RENAME_NEW_NAME)) );
|
|
}
|
|
|
|
//
|
|
// Check if the reason is a new reason.
|
|
//
|
|
|
|
NewReasons = FlagOn( ~(Fcb->FcbUsnRecord->UsnRecord.Reason | ThisUsn->NewReasons), Reason );
|
|
if (NewReasons != 0) {
|
|
|
|
//
|
|
// Check if we will remove a bit from the source info.
|
|
//
|
|
|
|
if ((Fcb->FcbUsnRecord->UsnRecord.SourceInfo != 0) &&
|
|
(Fcb->FcbUsnRecord->UsnRecord.Reason != 0) &&
|
|
(Reason != USN_REASON_CLOSE)) {
|
|
|
|
RemovedSourceInfo = FlagOn( Fcb->FcbUsnRecord->UsnRecord.SourceInfo,
|
|
~(IrpContext->SourceInfo | ThisUsn->RemovedSourceInfo) );
|
|
|
|
if (RemovedSourceInfo != 0) {
|
|
|
|
SetFlag( ThisUsn->RemovedSourceInfo, RemovedSourceInfo );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Post the new reasons to the IrpContext.
|
|
//
|
|
|
|
ThisUsn->CurrentUsnFcb = Fcb;
|
|
SetFlag( ThisUsn->NewReasons, NewReasons );
|
|
SetFlag( ThisUsn->UsnFcbFlags, USN_FCB_FLAG_NEW_REASON );
|
|
|
|
//
|
|
// Check if there is a change only to the source info.
|
|
// We look to see if we would remove a bit from the
|
|
// source info only if there has been at least one
|
|
// usn record already.
|
|
//
|
|
|
|
} else if ((Fcb->FcbUsnRecord->UsnRecord.SourceInfo != 0) &&
|
|
(Fcb->FcbUsnRecord->UsnRecord.Reason != 0) &&
|
|
(Reason != USN_REASON_CLOSE)) {
|
|
|
|
//
|
|
// Remember the bit being removed.
|
|
//
|
|
|
|
RemovedSourceInfo = FlagOn( Fcb->FcbUsnRecord->UsnRecord.SourceInfo,
|
|
~(IrpContext->SourceInfo | ThisUsn->RemovedSourceInfo) );
|
|
|
|
if (RemovedSourceInfo != 0) {
|
|
|
|
SetFlag( ThisUsn->RemovedSourceInfo, RemovedSourceInfo );
|
|
ThisUsn->CurrentUsnFcb = Fcb;
|
|
SetFlag( ThisUsn->UsnFcbFlags, USN_FCB_FLAG_NEW_REASON );
|
|
|
|
} else {
|
|
|
|
Reason = 0;
|
|
}
|
|
|
|
//
|
|
// If we did not apply the changes, then make sure we do no more special processing
|
|
// below.
|
|
//
|
|
|
|
} else {
|
|
|
|
Reason = 0;
|
|
}
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// For data overwrites it is necessary to actually write the Usn journal now, in
|
|
// case we crash before the request is completed, yet the data makes it out. Also
|
|
// we need to capture the Lsn to flush to if the data is getting flushed.
|
|
//
|
|
// We don't need to make this call if we are doing a rename. Rename will rewrite previous
|
|
// records.
|
|
//
|
|
|
|
if ((IrpContext->MajorFunction != IRP_MJ_SET_INFORMATION) &&
|
|
FlagOn( Reason, USN_REASON_DATA_OVERWRITE | USN_REASON_NAMED_DATA_OVERWRITE )) {
|
|
|
|
LSN UpdateLsn;
|
|
|
|
//
|
|
// For now assume we are not already a transaction, since we will be doing a
|
|
// checkpoint. (If this ASSERT ever fires, verify that it is ok to checkpoint
|
|
// the transaction in that case and fix the ASSERT!)
|
|
//
|
|
|
|
ASSERT(IrpContext->TransactionId == 0);
|
|
|
|
//
|
|
// Now write the journal, checkpoint the transaction, and free the UsnJournal to
|
|
// reduce contention. Get rid of any pinned Mft records, because WriteUsnJournal is going
|
|
// to acquire the Scb resource.
|
|
//
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Capture the Lsn to flush to *in the first thread to set one of the above bits*,
|
|
// before letting any data hit the disk. Synchronize it with the Fcb lock.
|
|
//
|
|
|
|
UpdateLsn = LfsQueryLastLsn( Fcb->Vcb->LogHandle );
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
Fcb->UpdateLsn = UpdateLsn;
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
}
|
|
}
|
|
|
|
return ThisUsn->NewReasons;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsWriteUsnJournalChanges (
|
|
PIRP_CONTEXT IrpContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to write a set of posted changes from the IrpContext
|
|
to the UsnJournal, if they have not already been posted.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
|
|
PFCB Fcb;
|
|
PVCB Vcb;
|
|
PSCB UsnJournal;
|
|
PUSN_FCB ThisUsn;
|
|
ULONG PreserveWaitState;
|
|
BOOLEAN WroteUsnRecord = FALSE;
|
|
BOOLEAN ReleaseFcbs = FALSE;
|
|
BOOLEAN CleanupContext = FALSE;
|
|
|
|
ThisUsn = &IrpContext->Usn;
|
|
|
|
do {
|
|
|
|
//
|
|
// Is there an Fcb with usn reasons in the current irpcontext usn_fcb structures ?
|
|
// Also are there any new reasons to report for this fcb.
|
|
//
|
|
|
|
if ((ThisUsn->CurrentUsnFcb != NULL) &&
|
|
FlagOn( ThisUsn->UsnFcbFlags, USN_FCB_FLAG_NEW_REASON )) {
|
|
|
|
Fcb = ThisUsn->CurrentUsnFcb;
|
|
Vcb = Fcb->Vcb;
|
|
UsnJournal = Vcb->UsnJournal;
|
|
|
|
//
|
|
// Remember that we wrote a record.
|
|
//
|
|
|
|
WroteUsnRecord = TRUE;
|
|
|
|
//
|
|
// We better be waitable.
|
|
//
|
|
|
|
PreserveWaitState = (IrpContext->State ^ IRP_CONTEXT_STATE_WAIT) & IRP_CONTEXT_STATE_WAIT;
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE )) {
|
|
|
|
//
|
|
// Acquire the Usn journal and lock the Fcb fields.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
NtfsAcquireExclusiveScb( IrpContext, UsnJournal );
|
|
ReleaseFcbs = TRUE;
|
|
}
|
|
|
|
try {
|
|
|
|
USN Usn;
|
|
ULONG BytesLeftInPage;
|
|
|
|
//
|
|
// Make sure the changes have not already been logged. We're
|
|
// looking for new reasons or a change to the source info.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// This is the tricky synchronization case. Assumption is
|
|
// that if name goes invalid we have both resources exclusive and any writes will
|
|
// be preceded by a post which will remove the invalid record
|
|
// This occurs when we remove a link and generate one record under the old name
|
|
// with the flag set as invalid
|
|
//
|
|
|
|
ASSERT( !FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE ) ||
|
|
FlagOn( Fcb->FcbState, FCB_STATE_VALID_USN_NAME ) ||
|
|
(NtfsIsExclusiveFcb( Fcb ) &&
|
|
((Fcb->PagingIoResource == NULL) || (NtfsIsExclusiveFcbPagingIo( Fcb )))) );
|
|
|
|
//
|
|
// Initialize the Fcb source info if this is our first record.
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord->UsnRecord.Reason == 0) {
|
|
|
|
Fcb->FcbUsnRecord->UsnRecord.SourceInfo = IrpContext->SourceInfo;
|
|
}
|
|
|
|
//
|
|
// Accumulate all reasons and store in the Fcb before unlocking the Fcb.
|
|
//
|
|
|
|
SetFlag( Fcb->FcbUsnRecord->UsnRecord.Reason, ThisUsn->NewReasons );
|
|
|
|
//
|
|
// Now clear the source info flags not supported by this
|
|
// caller.
|
|
//
|
|
|
|
ClearFlag( Fcb->FcbUsnRecord->UsnRecord.SourceInfo, ThisUsn->RemovedSourceInfo );
|
|
|
|
//
|
|
// Unlock Fcb now so we do not deadlock if we do a checkpoint.
|
|
//
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// Only actually persist to disk if the journal is active
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE )) {
|
|
|
|
ASSERT( UsnJournal != NULL );
|
|
|
|
//
|
|
// Initialize the context structure if we are doing a close.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbUsnRecord->UsnRecord.Reason, USN_REASON_CLOSE )) {
|
|
NtfsInitializeAttributeContext( &AttributeContext );
|
|
CleanupContext = TRUE;
|
|
}
|
|
|
|
Usn = UsnJournal->Header.FileSize.QuadPart;
|
|
BytesLeftInPage = USN_PAGE_SIZE - ((ULONG)Usn & (USN_PAGE_SIZE - 1));
|
|
|
|
//
|
|
// If there is not enough room left in this page for the
|
|
// current Usn Record, then advance to the next page boundary
|
|
// by writing 0's (these pages not zero-initialized( and update the Usn.
|
|
//
|
|
|
|
if (BytesLeftInPage < Fcb->FcbUsnRecord->UsnRecord.RecordLength) {
|
|
|
|
ASSERT( Fcb->FcbUsnRecord->UsnRecord.RecordLength <= USN_PAGE_SIZE );
|
|
|
|
NtOfsPutData( IrpContext, UsnJournal, -1, BytesLeftInPage, NULL );
|
|
Usn += BytesLeftInPage;
|
|
}
|
|
|
|
Fcb->FcbUsnRecord->UsnRecord.Usn = Usn;
|
|
|
|
//
|
|
// Build the FileAttributes from the Fcb.
|
|
//
|
|
|
|
Fcb->FcbUsnRecord->UsnRecord.FileAttributes = Fcb->Info.FileAttributes & FILE_ATTRIBUTE_VALID_FLAGS;
|
|
|
|
//
|
|
// We have to generate the DIRECTORY attribute.
|
|
//
|
|
|
|
if (IsDirectory( &Fcb->Info ) || IsViewIndex( &Fcb->Info )) {
|
|
SetFlag( Fcb->FcbUsnRecord->UsnRecord.FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
|
|
}
|
|
|
|
//
|
|
// If there are no flags set then explicitly set the NORMAL flag.
|
|
//
|
|
|
|
if (Fcb->FcbUsnRecord->UsnRecord.FileAttributes == 0) {
|
|
Fcb->FcbUsnRecord->UsnRecord.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
|
|
KeQuerySystemTime( &Fcb->FcbUsnRecord->UsnRecord.TimeStamp );
|
|
|
|
//
|
|
// Append the record to the UsnJournal. We should never see a record with
|
|
// both rename flags or the close flag with the old name flag.
|
|
//
|
|
|
|
ASSERT( !FlagOn( Fcb->FcbUsnRecord->UsnRecord.Reason, USN_REASON_RENAME_OLD_NAME ) ||
|
|
!FlagOn( Fcb->FcbUsnRecord->UsnRecord.Reason,
|
|
USN_REASON_CLOSE | USN_REASON_RENAME_NEW_NAME ));
|
|
|
|
NtOfsPutData( IrpContext,
|
|
UsnJournal,
|
|
-1,
|
|
Fcb->FcbUsnRecord->UsnRecord.RecordLength,
|
|
&Fcb->FcbUsnRecord->UsnRecord );
|
|
|
|
#ifdef BRIANDBG
|
|
//
|
|
// The Usn better be in an allocated piece.
|
|
//
|
|
|
|
{
|
|
LCN Lcn;
|
|
LONGLONG ClusterCount;
|
|
|
|
if (!NtfsLookupAllocation( IrpContext,
|
|
UsnJournal,
|
|
LlClustersFromBytesTruncate( Vcb, Usn ),
|
|
&Lcn,
|
|
&ClusterCount,
|
|
NULL,
|
|
NULL ) ||
|
|
(Lcn == UNUSED_LCN)) {
|
|
ASSERT( FALSE );
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// If this is the close record, then we must update the Usn in the file record.
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbUsnRecord->UsnRecord.Reason, USN_REASON_FILE_DELETE ) &&
|
|
!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
|
|
|
|
//
|
|
// See if we need to actually grow Standard Information first.
|
|
// Do this even if we don't write the Usn record now. We may
|
|
// generate a close record for this file during mount and
|
|
// we expect the STANDARD_INFORMATION to support Usns.
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
|
|
|
|
//
|
|
// Grow the standard information.
|
|
//
|
|
|
|
NtfsGrowStandardInformation( IrpContext, Fcb );
|
|
}
|
|
|
|
|
|
if (FlagOn( Fcb->FcbUsnRecord->UsnRecord.Reason, USN_REASON_CLOSE )) {
|
|
|
|
//
|
|
// Locate the standard information, it must be there.
|
|
//
|
|
|
|
if (!NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$STANDARD_INFORMATION,
|
|
&AttributeContext )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
|
|
}
|
|
|
|
ASSERT(NtfsFoundAttribute( &AttributeContext )->Form.Resident.ValueLength ==
|
|
sizeof( STANDARD_INFORMATION ));
|
|
|
|
//
|
|
// Call to change the attribute value.
|
|
//
|
|
|
|
NtfsChangeAttributeValue( IrpContext,
|
|
Fcb,
|
|
FIELD_OFFSET(STANDARD_INFORMATION, Usn),
|
|
&Usn,
|
|
sizeof(Usn),
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
&AttributeContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remember to release these resources as soon as possible now.
|
|
// Note, if we are not sure that we became a transaction (else
|
|
// case below) then our finally clause will do the release.
|
|
//
|
|
// If the system has already gone through shutdown we won't be
|
|
// able to start a transaction. Test that we have a transaction
|
|
// before setting these flags.
|
|
//
|
|
|
|
if (IrpContext->TransactionId != 0) {
|
|
|
|
SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RELEASE_USN_JRNL |
|
|
IRP_CONTEXT_FLAG_RELEASE_MFT );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clear the flag indicating that there are new reasons to report.
|
|
//
|
|
|
|
ClearFlag( ThisUsn->UsnFcbFlags, USN_FCB_FLAG_NEW_REASON );
|
|
|
|
|
|
} finally {
|
|
|
|
//
|
|
// Cleanup the context structure if we are doing a close.
|
|
//
|
|
|
|
if (CleanupContext) {
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
}
|
|
|
|
if (ReleaseFcbs) {
|
|
NtfsReleaseScb( IrpContext, UsnJournal );
|
|
ASSERT( NtfsIsExclusiveScb( Vcb->MftScb ) );
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
}
|
|
}
|
|
|
|
ClearFlag( IrpContext->State, PreserveWaitState );
|
|
}
|
|
|
|
//
|
|
// Go to the next entry if present. If we are at the last entry then walk through all of the
|
|
// entries and clear the flag indicating we have new reasons.
|
|
//
|
|
|
|
if (ThisUsn->NextUsnFcb == NULL) {
|
|
|
|
//
|
|
// Exit if we didn't write any records.
|
|
//
|
|
|
|
if (!WroteUsnRecord) { break; }
|
|
|
|
ThisUsn = &IrpContext->Usn;
|
|
|
|
do {
|
|
|
|
ClearFlag( ThisUsn->UsnFcbFlags, USN_FCB_FLAG_NEW_REASON );
|
|
if (ThisUsn->NextUsnFcb == NULL) { break; }
|
|
|
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|
|
|
} while (TRUE);
|
|
|
|
break;
|
|
}
|
|
|
|
ThisUsn = ThisUsn->NextUsnFcb;
|
|
|
|
} while (TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsSetupUsnJournal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb,
|
|
IN PFCB Fcb,
|
|
IN ULONG CreateIfNotExist,
|
|
IN ULONG Restamp,
|
|
IN PCREATE_USN_JOURNAL_DATA NewJournalData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to setup the Usn Journal - the stream may or may
|
|
not yet exist. This routine is responsible for cleaning up the disk and
|
|
in-memory structures on failure.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the volume being initialized.
|
|
|
|
Fcb - Supplies the file for the Usn Journal.
|
|
|
|
CreateIfNotExist - Indicates that we should use the values in the Vcb instead of on-disk.
|
|
|
|
Restamp - Indicates if we should restamp the journal with a new Id.
|
|
|
|
NewJournalData - Allocation size and delta for Usn journal if we are not reading from disk.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
RTL_GENERIC_TABLE UsnControlTable;
|
|
PSCB UsnJournal;
|
|
PUSN_RECORD UsnRecord, UsnRecordInTable;
|
|
BOOLEAN CleanupControlTable = FALSE;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
|
|
MAP_HANDLE MapHandle;
|
|
USN StartUsn;
|
|
LONGLONG ClusterCount;
|
|
|
|
PUSN_JOURNAL_INSTANCE UsnJournalData;
|
|
USN_JOURNAL_INSTANCE UsnJournalInstance, VcbUsnInstance;
|
|
PUSN_JOURNAL_INSTANCE InstanceToRestore;
|
|
|
|
PBCB Bcb = NULL;
|
|
|
|
LONGLONG SavedReservedSpace;
|
|
LONGLONG RequiredReserved;
|
|
|
|
BOOLEAN FoundMax;
|
|
BOOLEAN NewMax = FALSE;
|
|
BOOLEAN InsufficientReserved = FALSE;
|
|
|
|
BOOLEAN DecrementCloseCount = TRUE;
|
|
|
|
LARGE_INTEGER LastTimeStamp;
|
|
|
|
ULONG TempUlong;
|
|
LONGLONG TempLonglong;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Make sure we don't move to a larger page size.
|
|
//
|
|
|
|
ASSERT( USN_PAGE_BOUNDARY >= PAGE_SIZE );
|
|
|
|
//
|
|
// Open/Create the Usn Journal stream. We should never have an Scb
|
|
// if we are mounting a new volume.
|
|
//
|
|
|
|
ASSERT( (((ULONG) USN_JOURNAL_CACHE_BIAS) & (VACB_MAPPING_GRANULARITY - 1)) == 0 );
|
|
|
|
NtOfsCreateAttribute( IrpContext,
|
|
Fcb,
|
|
JournalStreamName,
|
|
CREATE_OR_OPEN,
|
|
TRUE,
|
|
&UsnJournal );
|
|
|
|
ASSERT( NtfsIsExclusiveScb( UsnJournal ) && NtfsIsExclusiveScb( Vcb->MftScb ) );
|
|
|
|
//
|
|
// Initialize the enumeration context and map handle.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttributeContext );
|
|
NtOfsInitializeMapHandle( &MapHandle );
|
|
|
|
//
|
|
// Let's build the journal instance data. Assume we have current valid
|
|
// values in the Vcb for the Id and lowest valid usn.
|
|
//
|
|
|
|
UsnJournalInstance.MaximumSize = NewJournalData->MaximumSize;
|
|
UsnJournalInstance.AllocationDelta = NewJournalData->AllocationDelta;
|
|
|
|
UsnJournalInstance.JournalId = Vcb->UsnJournalInstance.JournalId;
|
|
UsnJournalInstance.LowestValidUsn = Vcb->UsnJournalInstance.LowestValidUsn;
|
|
|
|
//
|
|
// Capture the current reservation in the Journal Scb and also the
|
|
// current JournalData in the Vcb to restore on error.
|
|
//
|
|
|
|
SavedReservedSpace = UsnJournal->ScbType.Data.TotalReserved;
|
|
|
|
RtlCopyMemory( &VcbUsnInstance,
|
|
&Vcb->UsnJournalInstance,
|
|
sizeof( USN_JOURNAL_INSTANCE ));
|
|
|
|
InstanceToRestore = &VcbUsnInstance;
|
|
|
|
try {
|
|
|
|
//
|
|
// Make sure the Scb is initialized.
|
|
//
|
|
|
|
if (!FlagOn( UsnJournal->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
|
|
|
|
NtfsUpdateScbFromAttribute( IrpContext, UsnJournal, NULL );
|
|
}
|
|
|
|
//
|
|
// Always create the journal non-resident. Otherwise in
|
|
// ConvertToNonResident we always need to check for this case
|
|
// which only happens once per volume.
|
|
//
|
|
|
|
if (FlagOn( UsnJournal->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
|
|
|
|
NtfsLookupAttributeForScb( IrpContext, UsnJournal, NULL, &AttributeContext );
|
|
ASSERT( NtfsIsAttributeResident( NtfsFoundAttribute( &AttributeContext )));
|
|
NtfsConvertToNonresident( IrpContext,
|
|
Fcb,
|
|
NtfsFoundAttribute( &AttributeContext ),
|
|
FALSE,
|
|
&AttributeContext );
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
}
|
|
|
|
//
|
|
// Remember to restamp if an earlier delete operation failed. This flag should
|
|
// never be set if there is a current UsnJournal Scb in the Vcb.
|
|
//
|
|
|
|
|
|
ASSERT( !FlagOn( Vcb->VcbState, VCB_STATE_INCOMPLETE_USN_DELETE ) ||
|
|
(Vcb->UsnJournal == NULL) );
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_INCOMPLETE_USN_DELETE )) {
|
|
|
|
Restamp = TRUE;
|
|
}
|
|
|
|
//
|
|
// If the $Max doesn't exist or we want to restamp then generate the
|
|
// new ID and Lowest ID.
|
|
//
|
|
|
|
if (!(FoundMax = NtfsLookupAttributeByName( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$DATA,
|
|
&$Max,
|
|
NULL,
|
|
FALSE,
|
|
&AttributeContext )) ||
|
|
Restamp ) {
|
|
|
|
NtfsAdvanceUsnJournal( Vcb, &UsnJournalInstance, UsnJournal->Header.FileSize.QuadPart, &NewMax );
|
|
|
|
//
|
|
// Examine the current $Max attribute for validity and either use the current values or
|
|
// generate new values.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Get the size of the $Max attribute. It should always be resident but we will rewrite it in
|
|
// the case where it isn't.
|
|
//
|
|
|
|
if (NtfsIsAttributeResident( NtfsFoundAttribute( &AttributeContext ))) {
|
|
|
|
TempUlong = (NtfsFoundAttribute( &AttributeContext))->Form.Resident.ValueLength;
|
|
|
|
} else {
|
|
|
|
TempUlong = (ULONG) (NtfsFoundAttribute( &AttributeContext))->Form.Nonresident.FileSize;
|
|
NewMax = TRUE;
|
|
}
|
|
|
|
//
|
|
// Map the attribute and check it for consistency.
|
|
//
|
|
|
|
NtfsMapAttributeValue( IrpContext,
|
|
Fcb,
|
|
&UsnJournalData,
|
|
&TempUlong,
|
|
&Bcb,
|
|
&AttributeContext );
|
|
|
|
//
|
|
// Only copy over the range of values we would understand. If the size is not one
|
|
// we recognize then restamp the journal. We handle the V1 case as well as V2 case.
|
|
//
|
|
|
|
if (TempUlong == sizeof( CREATE_USN_JOURNAL_DATA )) {
|
|
|
|
UsnJournalInstance.LowestValidUsn = 0;
|
|
KeQuerySystemTime( (PLARGE_INTEGER) &UsnJournalInstance.JournalId );
|
|
|
|
//
|
|
// Put version 2 on the disk.
|
|
//
|
|
|
|
NewMax = TRUE;
|
|
|
|
//
|
|
// If this is not an overwrite then copy the size and delta from the attribute.
|
|
//
|
|
|
|
if (!CreateIfNotExist) {
|
|
|
|
//
|
|
// Assume we will use the values from the disk.
|
|
//
|
|
|
|
RtlCopyMemory( &UsnJournalInstance,
|
|
UsnJournalData,
|
|
TempUlong );
|
|
}
|
|
|
|
} else if (TempUlong == sizeof( USN_JOURNAL_INSTANCE )) {
|
|
|
|
//
|
|
// Assume we will use the values from the disk.
|
|
//
|
|
|
|
if (CreateIfNotExist) {
|
|
|
|
NewMax = TRUE;
|
|
UsnJournalInstance.LowestValidUsn = UsnJournalData->LowestValidUsn;
|
|
UsnJournalInstance.JournalId = UsnJournalData->JournalId;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Get the data from the disk.
|
|
//
|
|
|
|
RtlCopyMemory( &UsnJournalInstance,
|
|
UsnJournalData,
|
|
TempUlong );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Restamp in this case.
|
|
// We move forward in the file to the next Usn boundary.
|
|
//
|
|
|
|
NtfsAdvanceUsnJournal( Vcb, &UsnJournalInstance, UsnJournal->Header.FileSize.QuadPart, &NewMax );
|
|
}
|
|
|
|
//
|
|
// Put the Bcb back into the context if we removed it.
|
|
//
|
|
|
|
if (NtfsFoundBcb( &AttributeContext ) == NULL) {
|
|
|
|
NtfsFoundBcb( &AttributeContext ) = Bcb;
|
|
Bcb = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check that the file doesn't end on a sparse hole.
|
|
//
|
|
|
|
if (!NewMax &&
|
|
(UsnJournal->Header.AllocationSize.QuadPart != 0) &&
|
|
(UsnJournalInstance.LowestValidUsn != UsnJournal->Header.AllocationSize.QuadPart)) {
|
|
|
|
LCN Lcn;
|
|
LONGLONG ClusterCount;
|
|
|
|
if (!NtfsLookupAllocation( IrpContext,
|
|
UsnJournal,
|
|
LlClustersFromBytesTruncate( Vcb, UsnJournal->Header.AllocationSize.QuadPart - 1 ),
|
|
&Lcn,
|
|
&ClusterCount,
|
|
NULL,
|
|
NULL ) ||
|
|
(Lcn == UNUSED_LCN)) {
|
|
|
|
NtfsAdvanceUsnJournal( Vcb, &UsnJournalInstance, UsnJournal->Header.AllocationSize.QuadPart, &NewMax );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Enforce minimum sizes and allocation deltas, do not let them eat the whole volume,
|
|
// and round them to a Cache Manager View Size. All of these decisions are arbitrary,
|
|
// but hopefully reasonable. An option would be to take the cases other than those
|
|
// dealing with rounding, and return an error.
|
|
//
|
|
|
|
if ((ULONGLONG) UsnJournalInstance.MaximumSize < (ULONGLONG) VcbUsnInstance.MaximumSize) {
|
|
|
|
UsnJournalInstance.MaximumSize = VcbUsnInstance.MaximumSize;
|
|
}
|
|
|
|
if (UsnJournalInstance.MaximumSize < MINIMUM_USN_JOURNAL_SIZE) {
|
|
UsnJournalInstance.MaximumSize = MINIMUM_USN_JOURNAL_SIZE;
|
|
NewMax = TRUE;
|
|
} else {
|
|
|
|
if ((ULONGLONG) UsnJournalInstance.MaximumSize > LlBytesFromClusters(Vcb, Vcb->TotalClusters) / 2) {
|
|
UsnJournalInstance.MaximumSize = LlBytesFromClusters(Vcb, Vcb->TotalClusters) / 2;
|
|
NewMax = TRUE;
|
|
}
|
|
|
|
if ((ULONGLONG) UsnJournalInstance.MaximumSize > USN_MAXIMUM_JOURNAL_SIZE) {
|
|
UsnJournalInstance.MaximumSize = USN_MAXIMUM_JOURNAL_SIZE;
|
|
NewMax = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Round this value down to a cache view boundary.
|
|
//
|
|
|
|
UsnJournalInstance.MaximumSize &= ~(LONGLONG)((VACB_MAPPING_GRANULARITY) - 1);
|
|
|
|
//
|
|
// Now do the allocation delta.
|
|
//
|
|
|
|
if ((ULONGLONG) UsnJournalInstance.AllocationDelta < (ULONGLONG) VcbUsnInstance.AllocationDelta) {
|
|
|
|
UsnJournalInstance.AllocationDelta = VcbUsnInstance.AllocationDelta;
|
|
}
|
|
|
|
if (UsnJournalInstance.AllocationDelta < (MINIMUM_USN_JOURNAL_SIZE / 4)) {
|
|
UsnJournalInstance.AllocationDelta = MINIMUM_USN_JOURNAL_SIZE / 4;
|
|
NewMax = TRUE;
|
|
} else if ((ULONGLONG) UsnJournalInstance.AllocationDelta > (UsnJournalInstance.MaximumSize / 4)) {
|
|
UsnJournalInstance.AllocationDelta = (UsnJournalInstance.MaximumSize / 4);
|
|
NewMax = TRUE;
|
|
}
|
|
|
|
//
|
|
// Round this down to a view boundary as well.
|
|
//
|
|
|
|
UsnJournalInstance.AllocationDelta &= ~(LONGLONG)((VACB_MAPPING_GRANULARITY) - 1);
|
|
|
|
//
|
|
// We now know the desired size of the journal (including allocation delta). Next
|
|
// we need to check that this space is available on disk. Otherwise we can get in
|
|
// a state where every operation on the volume will fail because we need to grow
|
|
// the journal and the space isn't available. The strategy here will be to use
|
|
// the reserved clusters in the Vcb to make sure we have enough space. If the
|
|
// journal already exists and we are simply opening it then the space should
|
|
// be available. It is possible someone could move this volume to NT4 and fill
|
|
// up the disk however. If we can't reserve the space in the current system then
|
|
// update the $Max attribute to indicate that we can't access the journal at this time.
|
|
//
|
|
|
|
//
|
|
// We need to be very precise about the initial reservation. The total allocation we allow
|
|
// ourselves is (MaxSize + Delta * 2). We will reserve the missing space now and adjust it
|
|
// during the TrimUsnJournal phase.
|
|
//
|
|
|
|
RequiredReserved = UsnJournalInstance.MaximumSize + (UsnJournalInstance.AllocationDelta * 2);
|
|
|
|
if (RequiredReserved >= UsnJournal->TotalAllocated) {
|
|
|
|
RequiredReserved -= UsnJournal->TotalAllocated;
|
|
|
|
} else {
|
|
|
|
RequiredReserved = UsnJournalInstance.AllocationDelta;
|
|
}
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
//
|
|
// Check if there is more to reserve and adjust the reservation if necessary.
|
|
//
|
|
|
|
if (RequiredReserved > SavedReservedSpace) {
|
|
|
|
//
|
|
// Check that the reserved clusters are available.
|
|
//
|
|
|
|
if (LlClustersFromBytes( Vcb, (RequiredReserved - SavedReservedSpace) ) + Vcb->TotalReserved > Vcb->FreeClusters) {
|
|
|
|
//
|
|
// We can't reserve the required space. If someone is changing the journal then simply
|
|
// raise the error.
|
|
//
|
|
|
|
if (CreateIfNotExist) {
|
|
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// We are trying to open the journal but can't get the space. Update the
|
|
// $Max to indicate that the ID is changing. We will bail later in this case.
|
|
//
|
|
// We move forward in the file to the next Usn boundary.
|
|
//
|
|
|
|
TempUlong = USN_PAGE_BOUNDARY;
|
|
if (USN_PAGE_BOUNDARY < Vcb->BytesPerCluster) {
|
|
|
|
TempUlong = Vcb->BytesPerCluster;
|
|
}
|
|
|
|
UsnJournalInstance.LowestValidUsn = UsnJournal->Header.FileSize.QuadPart + TempUlong - 1;
|
|
((PLARGE_INTEGER) &UsnJournalInstance.LowestValidUsn)->LowPart &= ~(TempUlong - 1);
|
|
|
|
//
|
|
// Generate a new journal ID.
|
|
//
|
|
|
|
KeQuerySystemTime( (PLARGE_INTEGER) &UsnJournalInstance.JournalId );
|
|
|
|
//
|
|
// Remember that we are restamping and need to rewrite the $Max attribute.
|
|
//
|
|
|
|
NewMax = TRUE;
|
|
|
|
InsufficientReserved = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remove the current reservation and bias with the new reservation.
|
|
//
|
|
|
|
Vcb->TotalReserved -= LlClustersFromBytes( Vcb, SavedReservedSpace );
|
|
Vcb->TotalReserved += LlClustersFromBytes( Vcb, RequiredReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = RequiredReserved;
|
|
SetFlag( UsnJournal->ScbState, SCB_STATE_WRITE_ACCESS_SEEN );
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
|
|
//
|
|
// Check we need to write a new $Max attribute.
|
|
//
|
|
|
|
if (NewMax) {
|
|
|
|
//
|
|
// Delete the existing $Max if present.
|
|
//
|
|
|
|
if (FoundMax) {
|
|
|
|
if (NtfsIsAttributeResident( NtfsFoundAttribute( &AttributeContext ))) {
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
Fcb,
|
|
(DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION),
|
|
&AttributeContext );
|
|
|
|
} else {
|
|
|
|
PSCB MaxScb;
|
|
|
|
MaxScb = NtfsCreateScb( IrpContext,
|
|
Fcb,
|
|
$DATA,
|
|
&$Max,
|
|
FALSE,
|
|
NULL );
|
|
|
|
do {
|
|
|
|
NtfsDeleteAttributeRecord( IrpContext,
|
|
Fcb,
|
|
(DELETE_LOG_OPERATION |
|
|
DELETE_RELEASE_FILE_RECORD |
|
|
DELETE_RELEASE_ALLOCATION),
|
|
&AttributeContext );
|
|
|
|
} while (NtfsLookupNextAttributeForScb( IrpContext, MaxScb, &AttributeContext ));
|
|
}
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
|
|
//
|
|
// Create the new $MAX attribute.
|
|
//
|
|
|
|
NtfsCreateAttributeWithValue( IrpContext,
|
|
UsnJournal->Fcb,
|
|
$DATA,
|
|
&$Max,
|
|
&UsnJournalInstance,
|
|
sizeof( USN_JOURNAL_INSTANCE ),
|
|
0, // attribute flags
|
|
NULL,
|
|
TRUE,
|
|
&AttributeContext );
|
|
}
|
|
|
|
//
|
|
// Check if we are finished with the journal because of reservation problems.
|
|
//
|
|
|
|
if (InsufficientReserved) {
|
|
|
|
//
|
|
// We want to checkpoint the request in order to leave the new $Max on disk.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Now update the Vcb with the new instance values.
|
|
//
|
|
|
|
RtlCopyMemory( &Vcb->UsnJournalInstance,
|
|
&UsnJournalInstance,
|
|
sizeof( USN_JOURNAL_INSTANCE ));
|
|
|
|
//
|
|
// We now have the correct journal values in the Vcb and the reservation in the Scb.
|
|
// The next step is to make sure that the allocation in the journal data is consistent
|
|
// with the lowest Vcn value.
|
|
//
|
|
|
|
if (UsnJournalInstance.LowestValidUsn >= UsnJournal->Header.FileSize.QuadPart) {
|
|
|
|
ASSERT( (Vcb->UsnJournal == NULL) ||
|
|
(Vcb->UsnJournal->Header.FileSize.QuadPart == 0) ||
|
|
(UsnJournalInstance.LowestValidUsn == UsnJournal->Header.FileSize.QuadPart) );
|
|
|
|
//
|
|
// Add allocation if we need to.
|
|
//
|
|
|
|
if (UsnJournalInstance.LowestValidUsn > UsnJournal->Header.AllocationSize.QuadPart) {
|
|
|
|
NtfsAddAllocation( IrpContext,
|
|
NULL,
|
|
UsnJournal,
|
|
LlClustersFromBytesTruncate( Vcb, UsnJournal->Header.AllocationSize.QuadPart ),
|
|
LlClustersFromBytes( Vcb,
|
|
UsnJournalInstance.LowestValidUsn - UsnJournal->Header.AllocationSize.QuadPart ),
|
|
FALSE,
|
|
NULL );
|
|
}
|
|
|
|
//
|
|
// Bump all of the sizes to this value.
|
|
//
|
|
|
|
UsnJournal->Header.ValidDataLength.QuadPart =
|
|
UsnJournal->Header.FileSize.QuadPart =
|
|
UsnJournal->ValidDataToDisk = UsnJournalInstance.LowestValidUsn;
|
|
|
|
NtfsWriteFileSizes( IrpContext,
|
|
UsnJournal,
|
|
&UsnJournal->Header.ValidDataLength.QuadPart,
|
|
TRUE,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
//
|
|
// Throw away the allocation upto this value.
|
|
//
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
NULL,
|
|
UsnJournal,
|
|
0,
|
|
LlClustersFromBytesTruncate( Vcb, UsnJournalInstance.LowestValidUsn ) - 1,
|
|
TRUE,
|
|
FALSE );
|
|
|
|
//
|
|
// Bias the Reserved space again.
|
|
//
|
|
|
|
RequiredReserved = UsnJournalInstance.MaximumSize + (UsnJournalInstance.AllocationDelta * 2);
|
|
|
|
if (RequiredReserved >= UsnJournal->TotalAllocated) {
|
|
|
|
RequiredReserved -= UsnJournal->TotalAllocated;
|
|
|
|
} else {
|
|
|
|
RequiredReserved = UsnJournalInstance.AllocationDelta;
|
|
}
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
Vcb->TotalReserved -= LlClustersFromBytes( Vcb, UsnJournal->ScbType.Data.TotalReserved );
|
|
Vcb->TotalReserved += LlClustersFromBytes( Vcb, RequiredReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = RequiredReserved;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
//
|
|
// Make sure the stream is marked as sparse.
|
|
//
|
|
|
|
if (!FlagOn( UsnJournal->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
|
|
|
|
NtfsSetSparseStream( IrpContext, NULL, UsnJournal );
|
|
NtfsUpdateDuplicateInfo( IrpContext, UsnJournal->Fcb, NULL, Vcb->ExtendDirectory );
|
|
|
|
//
|
|
// No point in restoring the Vcb values now.
|
|
//
|
|
|
|
InstanceToRestore = NULL;
|
|
SavedReservedSpace = UsnJournal->ScbType.Data.TotalReserved;
|
|
}
|
|
|
|
//
|
|
// If this was just an overwrite of parameters (Journal already started), get out.
|
|
//
|
|
|
|
if (Vcb->UsnJournal != NULL) {
|
|
|
|
ASSERT( FlagOn( Vcb->UsnJournal->Fcb->FcbState, FCB_STATE_USN_JOURNAL ));
|
|
SavedReservedSpace = UsnJournal->ScbType.Data.TotalReserved;
|
|
InstanceToRestore = NULL;
|
|
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Note in the Fcb that this is a journal file.
|
|
//
|
|
|
|
SetFlag( UsnJournal->Fcb->FcbState, FCB_STATE_USN_JOURNAL );
|
|
|
|
//
|
|
// Initialize a generic table to scoreboard Fcb entries
|
|
//
|
|
|
|
RtlInitializeGenericTable( &UsnControlTable,
|
|
NtfsUsnTableCompare,
|
|
NtfsUsnTableAllocate,
|
|
NtfsUsnTableFree,
|
|
NULL );
|
|
|
|
CleanupControlTable = TRUE;
|
|
|
|
//
|
|
// Load the run information for the stream. We are looking for the first position
|
|
// to read from the disk.
|
|
//
|
|
|
|
NtfsPreloadAllocation( IrpContext, UsnJournal, 0, MAXLONGLONG );
|
|
|
|
if (UsnJournal->Header.AllocationSize.QuadPart != 0) {
|
|
|
|
VCN CurrentVcn = 0;
|
|
|
|
while (!NtfsLookupAllocation( IrpContext,
|
|
UsnJournal,
|
|
CurrentVcn,
|
|
&TempLonglong,
|
|
&ClusterCount,
|
|
NULL,
|
|
NULL )) {
|
|
|
|
//
|
|
// Check the case where we returned the maximum LCN value.
|
|
//
|
|
|
|
if (CurrentVcn + ClusterCount == MAXLONGLONG) {
|
|
|
|
Vcb->FirstValidUsn = UsnJournal->Header.FileSize.QuadPart;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Find out the number of bytes in this block and check we don't
|
|
// go beyond file size.
|
|
//
|
|
|
|
Vcb->FirstValidUsn += LlBytesFromClusters( Vcb, ClusterCount );
|
|
|
|
if (Vcb->FirstValidUsn >= UsnJournal->Header.FileSize.QuadPart) {
|
|
|
|
Vcb->FirstValidUsn = UsnJournal->Header.FileSize.QuadPart;
|
|
break;
|
|
}
|
|
|
|
CurrentVcn += ClusterCount;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Skip forward if we have restamped the file.
|
|
//
|
|
|
|
if (Vcb->FirstValidUsn < UsnJournalInstance.LowestValidUsn) {
|
|
|
|
Vcb->FirstValidUsn = UsnJournalInstance.LowestValidUsn;
|
|
}
|
|
|
|
//
|
|
// Loop through as many views as required to fill the output buffer.
|
|
//
|
|
|
|
StartUsn = Vcb->LowestOpenUsn;
|
|
if (StartUsn < Vcb->FirstValidUsn) {
|
|
StartUsn = Vcb->FirstValidUsn;
|
|
}
|
|
|
|
//
|
|
// This is where we set up the bias for the Scb. Only do this for cases where
|
|
// there already isn't a data section.
|
|
//
|
|
|
|
if (UsnJournal->NonpagedScb->SegmentObject.DataSectionObject == NULL) {
|
|
|
|
Vcb->UsnCacheBias = Vcb->FirstValidUsn & ~(USN_JOURNAL_CACHE_BIAS - 1);
|
|
|
|
if (Vcb->UsnCacheBias != 0) {
|
|
|
|
Vcb->UsnCacheBias -= USN_JOURNAL_CACHE_BIAS;
|
|
}
|
|
|
|
NtfsCreateInternalAttributeStream( IrpContext, UsnJournal, TRUE, NULL );
|
|
}
|
|
|
|
while (StartUsn < UsnJournal->Header.FileSize.QuadPart) {
|
|
|
|
LONGLONG BiasedStartUsn;
|
|
|
|
//
|
|
// Calculate length to process in this view.
|
|
//
|
|
|
|
TempLonglong = UsnJournal->Header.FileSize.QuadPart - StartUsn;
|
|
if (TempLonglong > (VACB_MAPPING_GRANULARITY - (ULONG)(StartUsn & (VACB_MAPPING_GRANULARITY - 1)))) {
|
|
TempLonglong = VACB_MAPPING_GRANULARITY - (ULONG)(StartUsn & (VACB_MAPPING_GRANULARITY - 1));
|
|
}
|
|
|
|
//
|
|
// Map the view containing the desired Usn.
|
|
//
|
|
|
|
ASSERT( StartUsn >= Vcb->UsnCacheBias );
|
|
BiasedStartUsn = StartUsn - Vcb->UsnCacheBias;
|
|
NtOfsMapAttribute( IrpContext, UsnJournal, BiasedStartUsn, (ULONG)TempLonglong, &UsnRecord, &MapHandle );
|
|
|
|
//
|
|
// Now loop to process this view. TempLonglong is the space left in this view. TempUlong is
|
|
// the space for the next record.
|
|
//
|
|
|
|
while (TempLonglong != 0) {
|
|
|
|
//
|
|
// Calculate size left in current page, and see if we have to move to the
|
|
// next page.
|
|
//
|
|
// Note in this loop we are not going to trust the the contents of the
|
|
// file, so if we see anything broken we raise an error.
|
|
//
|
|
|
|
TempUlong = USN_PAGE_SIZE - (ULONG)(StartUsn & (USN_PAGE_SIZE - 1));
|
|
|
|
if ((TempUlong >= (FIELD_OFFSET(USN_RECORD, FileName) + sizeof(WCHAR))) && (UsnRecord->RecordLength != 0)) {
|
|
|
|
//
|
|
// Get the size of the current record.
|
|
//
|
|
|
|
TempUlong = UsnRecord->RecordLength;
|
|
|
|
//
|
|
// Since the Usn is embedded in the Usn record, we can do a fairly precise
|
|
// test that we got a valid Usn. Also make sure we got a valid RecordSize
|
|
// that does not go beyond FileSize or the end of the page. If we see a
|
|
// bad record, then let's just skip to the end of the page rather than
|
|
// tubing the mount process.
|
|
//
|
|
|
|
if ((TempUlong & (sizeof(ULONGLONG) - 1)) ||
|
|
(TempUlong > TempLonglong) ||
|
|
(TempUlong > (USN_PAGE_SIZE - ((ULONG)StartUsn & (USN_PAGE_SIZE - 1)))) ||
|
|
(StartUsn != UsnRecord->Usn)) {
|
|
|
|
TempUlong = (USN_PAGE_SIZE - ((ULONG)StartUsn & (USN_PAGE_SIZE - 1)));
|
|
|
|
//
|
|
// FileSize may stop before the end of the page, so check for that so
|
|
// we terminate correctly.
|
|
//
|
|
|
|
if (TempUlong > TempLonglong) {
|
|
TempUlong = (ULONG)TempLonglong;
|
|
}
|
|
|
|
//
|
|
// We have to skip over any MajorVersion we do not understand.
|
|
//
|
|
|
|
} else if ((UsnRecord->MajorVersion == 1) ||
|
|
(UsnRecord->MajorVersion == 2)) {
|
|
|
|
//
|
|
// Load up the info from this record.
|
|
//
|
|
|
|
if (!FlagOn(UsnRecord->Reason, USN_REASON_CLOSE)) {
|
|
|
|
UsnRecordInTable = RtlInsertElementGenericTable( &UsnControlTable,
|
|
UsnRecord,
|
|
UsnRecord->RecordLength,
|
|
NULL );
|
|
|
|
UsnRecordInTable->Reason = UsnRecord->Reason;
|
|
|
|
//
|
|
// If this is a close record, then we can delete our element from the
|
|
// generic table. Note if the record is not there this function returns
|
|
// FALSE, and the attempted delete is benign.
|
|
//
|
|
|
|
} else {
|
|
|
|
(VOID)RtlDeleteElementGenericTable( &UsnControlTable, UsnRecord );
|
|
}
|
|
|
|
//
|
|
// Capture each time stamp so that we can stamp our close records
|
|
// with the last one we see.
|
|
//
|
|
|
|
LastTimeStamp = UsnRecord->TimeStamp;
|
|
}
|
|
|
|
//
|
|
// Check for a bogus Usn near the end of a page that would cause us to
|
|
// decrement through length, or a RecordSize of 0, and just skip to the
|
|
// end of the page.
|
|
//
|
|
|
|
} else if ((TempUlong > TempLonglong) || (TempUlong == 0)) {
|
|
|
|
TempUlong = (USN_PAGE_SIZE - ((ULONG)StartUsn & (USN_PAGE_SIZE - 1)));
|
|
|
|
if (TempUlong > TempLonglong) {
|
|
|
|
TempUlong = (ULONG) TempLonglong;
|
|
}
|
|
}
|
|
|
|
StartUsn += TempUlong;
|
|
TempLonglong -= TempUlong;
|
|
UsnRecord = Add2Ptr( UsnRecord, TempUlong );
|
|
}
|
|
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
}
|
|
|
|
//
|
|
// Now write the close records for anyone who is left. We store a counter
|
|
// in TempUlong to limit the number of records we do at a time.
|
|
//
|
|
|
|
for (TempUlong = 0, UsnRecord = RtlEnumerateGenericTable( &UsnControlTable, TRUE );
|
|
UsnRecord != NULL;
|
|
UsnRecord = RtlEnumerateGenericTable( &UsnControlTable, TRUE )) {
|
|
|
|
ULONG UsnRecordReason;
|
|
FILE_REFERENCE UsnRecordFileReferenceNumber;
|
|
ULONG BytesLeftInPage;
|
|
PFILE_RECORD_SEGMENT_HEADER FileRecord;
|
|
NTSTATUS Status;
|
|
|
|
StartUsn = NtOfsQueryLength( UsnJournal );
|
|
StartUsn = UsnJournal->Header.FileSize.QuadPart;
|
|
BytesLeftInPage = USN_PAGE_SIZE - ((ULONG)StartUsn & (USN_PAGE_SIZE - 1));
|
|
|
|
//
|
|
// If there is not enough room left in this page for the
|
|
// current Usn Record, then advance to the next page boundary
|
|
// by writing 0's (these pages not zero-initialized( and update the Usn.
|
|
//
|
|
|
|
if (BytesLeftInPage < UsnRecord->RecordLength) {
|
|
|
|
NtOfsPutData( IrpContext, UsnJournal, -1, BytesLeftInPage, NULL );
|
|
StartUsn += BytesLeftInPage;
|
|
}
|
|
|
|
//
|
|
// Append the record to the UsnJournal. Note that the generic table is unaligned for
|
|
// 64-bit values so we have to carefully copy the larger values.
|
|
//
|
|
|
|
*((ULONGLONG UNALIGNED *) &UsnRecord->Usn) = StartUsn;
|
|
*((ULONGLONG UNALIGNED *) &UsnRecord->TimeStamp) = *((PULONGLONG) &LastTimeStamp);
|
|
|
|
UsnRecord->Reason |= USN_REASON_CLOSE;
|
|
NtOfsPutData( IrpContext,
|
|
UsnJournal,
|
|
-1,
|
|
UsnRecord->RecordLength,
|
|
UsnRecord );
|
|
|
|
//
|
|
// Remember key fields of the Usn record.
|
|
//
|
|
|
|
UsnRecordReason = UsnRecord->Reason;
|
|
*((PULONGLONG) &UsnRecordFileReferenceNumber) = *((ULONGLONG UNALIGNED *) &UsnRecord->FileReferenceNumber);
|
|
|
|
RtlDeleteElementGenericTable( &UsnControlTable, UsnRecord );
|
|
TempUlong += 1;
|
|
|
|
//
|
|
// Now we have to update the Usn in the file record, if it is not deleted.
|
|
// Also, we use try-except to plow on in the event of any errors, so we
|
|
// do not make the volume unmountable. (One legitimate concern would be
|
|
// a hot-fix in the Mft.)
|
|
//
|
|
|
|
if (!FlagOn(UsnRecordReason, USN_REASON_FILE_DELETE)) {
|
|
|
|
//
|
|
// Start by reading the file record and performing some simple tests.
|
|
// We don't want to go down the path where we mark the volume dirty
|
|
// for a file that was already cleaned up by autochk.
|
|
//
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
NtfsReadMftRecord( IrpContext,
|
|
Vcb,
|
|
&UsnRecordFileReferenceNumber,
|
|
FALSE,
|
|
&Bcb,
|
|
&FileRecord,
|
|
NULL );
|
|
|
|
//
|
|
// Proceed only if the file record passes the following tests.
|
|
//
|
|
// - FileRecord is in-use
|
|
// - Sequence numbers match
|
|
// - Standard information is the correct size (we should have done
|
|
// this when we wrote the changes)
|
|
//
|
|
|
|
if ((*(PULONG)(FileRecord)->MultiSectorHeader.Signature == *(PULONG)FileSignature) &&
|
|
FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE ) &&
|
|
(FileRecord->SequenceNumber == UsnRecordFileReferenceNumber.SequenceNumber)) {
|
|
|
|
//
|
|
// Locate the standard information, it must be there. This is the
|
|
// Fcb for the Usn Journal, but the lookup routine only needs to get
|
|
// the Vcb from it, and will special-case the return to us.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
|
|
try {
|
|
|
|
//
|
|
// If we cannot find it for some reason, then leave.
|
|
//
|
|
|
|
if (!NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&UsnRecordFileReferenceNumber,
|
|
$STANDARD_INFORMATION,
|
|
&AttributeContext )) {
|
|
leave;
|
|
}
|
|
|
|
ASSERT(NtfsFoundAttribute( &AttributeContext )->Form.Resident.ValueLength ==
|
|
sizeof( STANDARD_INFORMATION ));
|
|
|
|
//
|
|
// Call to change the attribute value. Again, this is the wrong Fcb,
|
|
// but it is ok since we are not changing the attribute size and will
|
|
// only need to get the Vcb from it.
|
|
//
|
|
|
|
NtfsChangeAttributeValue( IrpContext,
|
|
Fcb,
|
|
FIELD_OFFSET(STANDARD_INFORMATION, Usn),
|
|
&StartUsn,
|
|
sizeof(StartUsn),
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
&AttributeContext );
|
|
|
|
} except( NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
|
|
|
|
|
|
//
|
|
// If we get a log file full then raise this status. There
|
|
// is no reason to continue if we get a log file full.
|
|
//
|
|
|
|
if (Status == STATUS_LOG_FILE_FULL) {
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// OK. We are going to continue. Make sure we clean up the IrpContext.
|
|
//
|
|
|
|
IrpContext->ExceptionStatus = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
}
|
|
|
|
//
|
|
// Checkpoint the transaction periodically so we don't spin on log file full.
|
|
//
|
|
|
|
if (TempUlong > GENERATE_CLOSE_RECORD_LIMIT) {
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
SavedReservedSpace = UsnJournal->ScbType.Data.TotalReserved;
|
|
InstanceToRestore = NULL;
|
|
TempUlong = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Everything has succeeded to this point. Now make sure the DELETE_USN flag is cleared on
|
|
// disk if present.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_INCOMPLETE_USN_DELETE )) {
|
|
|
|
NtfsSetVolumeInfoFlagState( IrpContext,
|
|
Vcb,
|
|
VOLUME_DELETE_USN_UNDERWAY,
|
|
FALSE,
|
|
TRUE );
|
|
}
|
|
|
|
InstanceToRestore = NULL;
|
|
SavedReservedSpace = UsnJournal->ScbType.Data.TotalReserved;
|
|
|
|
Vcb->UsnJournal = UsnJournal;
|
|
DecrementCloseCount = FALSE;
|
|
|
|
NtfsLockVcb( IrpContext, Vcb );
|
|
SetFlag( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE );
|
|
ClearFlag( Vcb->VcbState, VCB_STATE_INCOMPLETE_USN_DELETE );
|
|
NtfsUnlockVcb( IrpContext, Vcb );
|
|
|
|
} finally {
|
|
|
|
//
|
|
// Clean up any remaining entries in the control table in case we failed
|
|
// while processing it.
|
|
//
|
|
|
|
if (CleanupControlTable) {
|
|
|
|
while ((UsnRecord = RtlEnumerateGenericTable( &UsnControlTable, TRUE )) != NULL) {
|
|
|
|
RtlDeleteElementGenericTable( &UsnControlTable, UsnRecord );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Restore any changes we might have made to the Vcb.
|
|
//
|
|
|
|
if (InstanceToRestore) {
|
|
|
|
RtlCopyMemory( &Vcb->UsnJournalInstance,
|
|
InstanceToRestore,
|
|
sizeof( USN_JOURNAL_INSTANCE ));
|
|
}
|
|
|
|
//
|
|
// Back out the reservation change if necessary.
|
|
//
|
|
|
|
if (UsnJournal->ScbType.Data.TotalReserved != SavedReservedSpace) {
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
Vcb->TotalReserved += LlClustersFromBytes( Vcb, SavedReservedSpace );
|
|
Vcb->TotalReserved -= LlClustersFromBytes( Vcb, UsnJournal->ScbType.Data.TotalReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = SavedReservedSpace;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttributeContext );
|
|
NtfsUnpinBcb( IrpContext, &Bcb );
|
|
NtOfsReleaseMap( IrpContext, &MapHandle );
|
|
|
|
//
|
|
// If we got an error and the Usn journal is not going to be created then fix up the
|
|
// new Scb so we won't access it during volume flush operations, etc. Otherwise we
|
|
// will think the volume is corrupt because there is no attribute for the Scb.
|
|
//
|
|
|
|
if (DecrementCloseCount) {
|
|
|
|
NtOfsCloseAttribute( IrpContext, UsnJournal );
|
|
}
|
|
|
|
if (Vcb->UsnJournal == NULL) {
|
|
|
|
#ifdef NTFSDBG
|
|
|
|
//
|
|
// Compensate again for misclassification of usnjournal during delete
|
|
//
|
|
|
|
if (IrpContext->OwnershipState == NtfsOwns_ExVcb_Mft_Extend_Journal) {
|
|
IrpContext->OwnershipState = NtfsOwns_ExVcb_Mft_Extend_File;
|
|
}
|
|
#endif
|
|
|
|
UsnJournal->Header.AllocationSize.QuadPart =
|
|
UsnJournal->Header.FileSize.QuadPart =
|
|
UsnJournal->ValidDataToDisk =
|
|
UsnJournal->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
UsnJournal->AttributeTypeCode = $UNUSED;
|
|
SetFlag( UsnJournal->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
|
|
//
|
|
// Clear the system file flag out of the Fcb.
|
|
//
|
|
|
|
ClearFlag( UsnJournal->Fcb->FcbState, FCB_STATE_SYSTEM_FILE );
|
|
|
|
ASSERT( ExIsResourceAcquiredSharedLite( &Vcb->Resource ));
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
UsnJournal,
|
|
NULL,
|
|
TRUE,
|
|
0,
|
|
NULL );
|
|
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsTrimUsnJournal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVCB Vcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine may be called to check if the Usn Journal is beyond the designated
|
|
size goal, and if so to delete the front of the file to bring it within the goal.
|
|
This may require first generating a few close records for files that are still open
|
|
and have their last record within this range. Such files that are modified again
|
|
will simply look like they were opened again.
|
|
|
|
This routine is called with certain checkpoint flags for the volume set. This is to
|
|
serialize with the DeleteUsnJournal path. We must clear them and signal other
|
|
checkpointers to proceed.
|
|
|
|
Arguments:
|
|
|
|
Vcb - Supplies the Vcb on which the Usn Journal is to be trimmed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFCB Fcb;
|
|
PFCB_USN_RECORD FcbUsnRecord;
|
|
PSCB UsnJournal = Vcb->UsnJournal;
|
|
USN FirstValidUsn = Vcb->FirstValidUsn;
|
|
ULONG Done = FALSE;
|
|
|
|
LONGLONG SavedReserved;
|
|
LONGLONG RequiredReserved;
|
|
LONGLONG SavedBias;
|
|
BOOLEAN AcquiredMft = FALSE;
|
|
BOOLEAN DerefFcb = FALSE;
|
|
|
|
//
|
|
// Purge file record cache - may not be necc. here, examine this post nt5
|
|
//
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
|
|
//
|
|
// See if it is time to trim the UsnJournal.
|
|
//
|
|
|
|
NtfsAcquireResourceShared( IrpContext, UsnJournal, TRUE );
|
|
while ((USN)(FirstValidUsn +
|
|
Vcb->UsnJournalInstance.MaximumSize +
|
|
Vcb->UsnJournalInstance.AllocationDelta) < (USN)UsnJournal->Header.FileSize.QuadPart) {
|
|
|
|
FirstValidUsn += Vcb->UsnJournalInstance.AllocationDelta;
|
|
}
|
|
NtfsReleaseResource( IrpContext, UsnJournal );
|
|
|
|
//
|
|
// Get to work if we have a new Usn to trim to.
|
|
//
|
|
|
|
if (FirstValidUsn != Vcb->FirstValidUsn) {
|
|
|
|
//
|
|
// Use try-finally to catch any log file full condtions or allocation failures.
|
|
// Since these are the only possible error condition, we know what resources to
|
|
// free on exit.
|
|
//
|
|
|
|
try {
|
|
|
|
do {
|
|
|
|
Fcb = NULL;
|
|
|
|
//
|
|
// Purge file record cache before acquiring vcb everytime
|
|
//
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
|
|
//
|
|
// Synchronize with the Fcb table and Usn Journal so that we can
|
|
// see if the next Fcb has to have a close record generated.
|
|
//
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ExAcquireFastMutex( UsnJournal->Header.FastMutex );
|
|
|
|
if (!IsListEmpty(&Vcb->ModifiedOpenFiles)) {
|
|
FcbUsnRecord = (PFCB_USN_RECORD)CONTAINING_RECORD( Vcb->ModifiedOpenFiles.Flink,
|
|
FCB_USN_RECORD,
|
|
ModifiedOpenFilesLinks );
|
|
|
|
//
|
|
// If the Usn record for this Fcb is older than where we want to delete
|
|
// to, then reference it. Otherwise signal we are done by clearing
|
|
// the Fcb pointer.
|
|
//
|
|
|
|
if (FcbUsnRecord->Fcb->Usn < FirstValidUsn) {
|
|
Fcb = FcbUsnRecord->Fcb;
|
|
Fcb->ReferenceCount += 1;
|
|
DerefFcb = TRUE;
|
|
} else {
|
|
Fcb = NULL;
|
|
}
|
|
}
|
|
|
|
ExReleaseFastMutex( UsnJournal->Header.FastMutex );
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
|
|
//
|
|
// Do we have to generate another close record?
|
|
//
|
|
|
|
if (Fcb != NULL) {
|
|
|
|
//
|
|
// We must lock out other activity on this file since we are about
|
|
// to reset the Usn reasons.
|
|
//
|
|
|
|
if (Fcb->PagingIoResource != NULL) {
|
|
ExAcquireResourceExclusiveLite( Fcb->PagingIoResource, TRUE );
|
|
}
|
|
NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, ACQUIRE_NO_DELETE_CHECK );
|
|
|
|
//
|
|
// Skip over system files.
|
|
//
|
|
|
|
if (!FlagOn(Fcb->FcbState, FCB_STATE_SYSTEM_FILE)) {
|
|
|
|
//
|
|
// Post the close to our IrpContext.
|
|
//
|
|
|
|
NtfsPostUsnChange( IrpContext, Fcb, USN_REASON_CLOSE );
|
|
|
|
//
|
|
// If we did not actually post a change, something is wrong,
|
|
// because when a close change is written, the Fcb is removed from
|
|
// the list.
|
|
//
|
|
|
|
ASSERT(IrpContext->Usn.CurrentUsnFcb != NULL);
|
|
|
|
//
|
|
// Now generate the close record and checkpoint the transaction.
|
|
//
|
|
|
|
NtfsWriteUsnJournalChanges( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
}
|
|
|
|
//
|
|
// Now we will dereference the Fcb.
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
Fcb->ReferenceCount -= 1;
|
|
DerefFcb = FALSE;
|
|
|
|
//
|
|
// We may be required to delete this guy. This frees the Fcb Table.
|
|
//
|
|
|
|
if (IsListEmpty( &Fcb->ScbQueue ) && (Fcb->ReferenceCount == 0) && (Fcb->CloseCount == 0)) {
|
|
|
|
BOOLEAN AcquiredFcbTable = TRUE;
|
|
|
|
NtfsDeleteFcb( IrpContext, &Fcb, &AcquiredFcbTable );
|
|
|
|
ASSERT(!AcquiredFcbTable);
|
|
Fcb = (PFCB)1;
|
|
|
|
//
|
|
// Otherwise free the table and Fcb resources.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
if (Fcb->PagingIoResource != NULL) {
|
|
ExReleaseResourceLite( Fcb->PagingIoResource );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we can drop the Vcb before looping back.
|
|
//
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
|
|
} while (Fcb != NULL);
|
|
|
|
} finally {
|
|
|
|
//
|
|
// We got an error if Fcb is not NULL
|
|
//
|
|
|
|
if (Fcb != NULL) {
|
|
|
|
if (DerefFcb) {
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
Fcb->ReferenceCount -= 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
}
|
|
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
if (Fcb->PagingIoResource != NULL) {
|
|
ExReleaseResourceLite( Fcb->PagingIoResource );
|
|
}
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// If we raised then we need to clear the checkpoint flags.
|
|
//
|
|
|
|
if (AbnormalTermination()) {
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->CheckpointFlags,
|
|
VCB_CHECKPOINT_SYNC_FLAGS | VCB_DUMMY_CHECKPOINT_POSTED);
|
|
|
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now synchronize for deleting the allocation and purging pages from
|
|
// the cache.
|
|
//
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
|
|
NtfsAcquireExclusiveScb( IrpContext, UsnJournal );
|
|
|
|
//
|
|
// Clear the checkpoint flags at this point.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->CheckpointFlags,
|
|
VCB_CHECKPOINT_SYNC_FLAGS | VCB_DUMMY_CHECKPOINT_POSTED);
|
|
|
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
|
|
try {
|
|
|
|
LONGLONG BiasedFirstValidUsn;
|
|
LONGLONG NewBias;
|
|
|
|
SavedReserved = UsnJournal->ScbType.Data.TotalReserved;
|
|
SavedBias = Vcb->UsnCacheBias;
|
|
|
|
//
|
|
// Make sure to preserve our reservation. We need to make sure anything we
|
|
// deallocate is available to us.
|
|
//
|
|
|
|
RequiredReserved = Vcb->UsnJournalInstance.AllocationDelta * 2 + Vcb->UsnJournalInstance.MaximumSize;
|
|
|
|
if (SavedReserved < RequiredReserved) {
|
|
|
|
//
|
|
// Bias the reservation with the maximum amount.
|
|
//
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
Vcb->TotalReserved -= LlClustersFromBytesTruncate( Vcb, SavedReserved );
|
|
Vcb->TotalReserved += LlClustersFromBytesTruncate( Vcb, RequiredReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = RequiredReserved;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
NtfsDeleteAllocation( IrpContext,
|
|
UsnJournal->FileObject,
|
|
UsnJournal,
|
|
0,
|
|
LlClustersFromBytes(Vcb, FirstValidUsn) - 1,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
//
|
|
// Do a final checkpoint, especially since this IrpContext gets reused.
|
|
//
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Adjust the current reserved amount more precisely.
|
|
//
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
if (UsnJournal->TotalAllocated > RequiredReserved) {
|
|
|
|
SavedReserved = Vcb->UsnJournalInstance.AllocationDelta;
|
|
|
|
} else {
|
|
|
|
SavedReserved = RequiredReserved - UsnJournal->TotalAllocated;
|
|
}
|
|
|
|
//
|
|
// Remove the current reservation and bias with the new reservation.
|
|
//
|
|
|
|
Vcb->TotalReserved -= LlClustersFromBytesTruncate( Vcb, UsnJournal->ScbType.Data.TotalReserved );
|
|
Vcb->TotalReserved += LlClustersFromBytesTruncate( Vcb, SavedReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = SavedReserved;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
|
|
//
|
|
// If the nearly impossible case that the length wraps, then our
|
|
// purge will be too small, which simply means some unused pages
|
|
// will have to leave memory on their own!
|
|
//
|
|
|
|
BiasedFirstValidUsn = Vcb->FirstValidUsn - Vcb->UsnCacheBias;
|
|
|
|
CcPurgeCacheSection( &UsnJournal->NonpagedScb->SegmentObject,
|
|
(PLARGE_INTEGER)&BiasedFirstValidUsn,
|
|
(ULONG)(FirstValidUsn - Vcb->FirstValidUsn) - 1,
|
|
FALSE );
|
|
|
|
|
|
//
|
|
// Adjust bias now if at threshold - the flush causes everything in
|
|
// cache and logfile to disk and we hold the journal exclusive. So
|
|
// all in memory stuff will now reflect the new bias
|
|
//
|
|
|
|
NewBias = FirstValidUsn & ~(USN_JOURNAL_CACHE_BIAS - 1);
|
|
if (NewBias != 0) {
|
|
NewBias -= USN_JOURNAL_CACHE_BIAS;
|
|
}
|
|
|
|
if (NewBias != Vcb->UsnCacheBias) {
|
|
|
|
//
|
|
// Flush And Purge releases all resources in exclusive list so acquire
|
|
// the mft an extra time beforehand and restore back afterwards
|
|
//
|
|
|
|
NtfsAcquireResourceExclusive( IrpContext, Vcb->MftScb, TRUE );
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
AcquiredMft = TRUE;
|
|
|
|
NtfsFlushAndPurgeScb( IrpContext, UsnJournal, NULL );
|
|
Vcb->UsnCacheBias = NewBias;
|
|
SavedBias = NewBias;
|
|
}
|
|
|
|
//
|
|
// If we reach here, then we can advance FirstValidUsn. (Otherwise
|
|
// any retryable conditions will just resume work on this range.
|
|
//
|
|
|
|
Vcb->FirstValidUsn = FirstValidUsn;
|
|
|
|
} finally {
|
|
|
|
//
|
|
// Restore the error if we raised while deallocating.
|
|
//
|
|
|
|
if (SavedBias != Vcb->UsnCacheBias) {
|
|
Vcb->UsnCacheBias = SavedBias;
|
|
}
|
|
|
|
if (SavedReserved != UsnJournal->ScbType.Data.TotalReserved) {
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
Vcb->TotalReserved += LlClustersFromBytesTruncate( Vcb, SavedReserved );
|
|
Vcb->TotalReserved -= LlClustersFromBytesTruncate( Vcb, RequiredReserved );
|
|
UsnJournal->ScbType.Data.TotalReserved = SavedReserved;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
NtfsReleaseScb( IrpContext, UsnJournal );
|
|
|
|
if (AcquiredMft) {
|
|
NtfsReleaseResource( IrpContext, Vcb->MftScb );
|
|
} else {
|
|
NtfsReleaseScb( IrpContext, Vcb->MftScb );
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Clear the checkpoint flags at this point.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->CheckpointFlags,
|
|
VCB_CHECKPOINT_SYNC_FLAGS | VCB_DUMMY_CHECKPOINT_POSTED);
|
|
|
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsQueryUsnJournal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the worker routine which returns the information about the current instance
|
|
of the Usn journal.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for the request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Result for this request.
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
|
|
PUSN_JOURNAL_DATA JournalData;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Always make this request synchronous.
|
|
//
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
//
|
|
// Get the current stack location and extract the output
|
|
// buffer information. The output parameter will receive
|
|
// the compressed state of the file/directory.
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Get a pointer to the output buffer. Look at the system buffer field in th
|
|
// irp first. Then the Irp Mdl.
|
|
//
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
JournalData = (PUSN_JOURNAL_DATA) Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
} else if (Irp->MdlAddress != NULL) {
|
|
|
|
JournalData = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority );
|
|
|
|
if (JournalData == NULL) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INSUFFICIENT_RESOURCES );
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_USER_BUFFER );
|
|
return STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
|
|
//
|
|
// Make sure the output buffer is large enough for the journal data.
|
|
//
|
|
|
|
if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof( USN_JOURNAL_DATA )) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_USER_BUFFER );
|
|
return STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
|
|
//
|
|
// Decode the file object. We only support this call for volume opens.
|
|
//
|
|
|
|
TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
|
|
|
|
if ((Ccb == NULL) || !FlagOn( Ccb->AccessFlags, MANAGE_VOLUME_ACCESS)) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_ACCESS_DENIED );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Acquire shared access to the Scb and check that the volume is still mounted.
|
|
//
|
|
|
|
NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
|
|
|
|
if (!FlagOn(Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED)) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_VOLUME_DISMOUNTED );
|
|
return STATUS_VOLUME_DISMOUNTED;
|
|
}
|
|
|
|
//
|
|
// Indicate if the journal is being deleted or has not started.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_DELETE )) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_JOURNAL_DELETE_IN_PROGRESS );
|
|
return STATUS_JOURNAL_DELETE_IN_PROGRESS;
|
|
}
|
|
|
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE )) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_JOURNAL_NOT_ACTIVE );
|
|
return STATUS_JOURNAL_NOT_ACTIVE;
|
|
}
|
|
|
|
//
|
|
// Otherwise serialize with the Usn journal and copy the data from the journal Scb
|
|
// and Vcb.
|
|
//
|
|
|
|
NtfsAcquireSharedScb( IrpContext, Vcb->UsnJournal );
|
|
|
|
JournalData->UsnJournalID = Vcb->UsnJournalInstance.JournalId;
|
|
JournalData->FirstUsn = Vcb->FirstValidUsn;
|
|
JournalData->NextUsn = Vcb->UsnJournal->Header.FileSize.QuadPart;
|
|
JournalData->LowestValidUsn = Vcb->UsnJournalInstance.LowestValidUsn;
|
|
JournalData->MaxUsn = MAXFILESIZE;
|
|
JournalData->MaximumSize = Vcb->UsnJournalInstance.MaximumSize;
|
|
JournalData->AllocationDelta = Vcb->UsnJournalInstance.AllocationDelta;
|
|
|
|
NtfsReleaseScb( IrpContext, Vcb->UsnJournal );
|
|
|
|
ASSERT( JournalData->FirstUsn >= JournalData->LowestValidUsn );
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
Irp->IoStatus.Information = sizeof( USN_JOURNAL_DATA );
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_SUCCESS );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtfsDeleteUsnJournal (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when the user want to delete the current usn journal. This will
|
|
initiate the work to scan the Mft and reset all usn values to zero and remove the
|
|
UsnJournal file from the disk.
|
|
|
|
Arguments:
|
|
|
|
Irp - This is the Irp for the request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Result for this request.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PDELETE_USN_JOURNAL_DATA DeleteData;
|
|
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PSCB Scb;
|
|
PCCB Ccb;
|
|
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
BOOLEAN CheckpointHeld = FALSE;
|
|
BOOLEAN AcquiredNotify = FALSE;
|
|
PSCB ReleaseUsnJournal = NULL;
|
|
|
|
PLIST_ENTRY Links;
|
|
|
|
PAGED_CODE();
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// We always wait in this path.
|
|
//
|
|
|
|
SetFlag( IrpContext->State, IRP_CONTEXT_STATE_WAIT );
|
|
|
|
//
|
|
// Perform a check on the input buffer.
|
|
//
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
DeleteData = (PDELETE_USN_JOURNAL_DATA) Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
} else if (Irp->MdlAddress != NULL) {
|
|
|
|
DeleteData = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority );
|
|
|
|
if (DeleteData == NULL) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INSUFFICIENT_RESOURCES );
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_USER_BUFFER );
|
|
return STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
|
|
if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof( DELETE_USN_JOURNAL_DATA )) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_USER_BUFFER );
|
|
return STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
|
|
//
|
|
// Decode the file object type
|
|
//
|
|
|
|
NtfsDecodeFileObject( IrpContext,
|
|
IrpSp->FileObject,
|
|
&Vcb,
|
|
&Fcb,
|
|
&Scb,
|
|
&Ccb,
|
|
TRUE );
|
|
|
|
if ((Ccb == NULL) || !FlagOn( Ccb->AccessFlags, MANAGE_VOLUME_ACCESS)) {
|
|
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_ACCESS_DENIED );
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// We only support deleting and waiting for delete.
|
|
//
|
|
|
|
if (DeleteData->DeleteFlags == 0) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_SUCCESS );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (FlagOn( DeleteData->DeleteFlags, ~USN_DELETE_VALID_FLAGS )) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_INVALID_PARAMETER );
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
|
|
NtfsCompleteRequest( IrpContext, Irp, STATUS_MEDIA_WRITE_PROTECTED );
|
|
return STATUS_MEDIA_WRITE_PROTECTED;
|
|
}
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Serialize with chkpoints and acquire the Vcb. We need to carefully remove
|
|
// the journal from the Vcb.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
|
|
while (FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS )) {
|
|
|
|
//
|
|
// Release the checkpoint event because we cannot stop the log file now.
|
|
//
|
|
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
NtfsWaitOnCheckpointNotify( IrpContext, Vcb );
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
}
|
|
|
|
SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
|
|
NtfsResetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
CheckpointHeld = TRUE;
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
VcbAcquired = TRUE;
|
|
|
|
//
|
|
// Check that the volume is still mounted.
|
|
//
|
|
|
|
if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
|
|
|
|
Status = STATUS_VOLUME_DISMOUNTED;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// If the user wants to delete the journal then make sure the delete hasn't
|
|
// already started.
|
|
//
|
|
|
|
if (FlagOn( DeleteData->DeleteFlags, USN_DELETE_FLAG_DELETE )) {
|
|
|
|
//
|
|
// If the journal is already being deleted and this caller wanted to
|
|
// do the delete then let him know it has already begun.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_DELETE )) {
|
|
|
|
Status = STATUS_JOURNAL_DELETE_IN_PROGRESS;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Proceed with the delete if there is a Usn journal on disk.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_JOURNAL_PRESENT ) ||
|
|
(Vcb->UsnJournal != NULL)) {
|
|
|
|
PSCB UsnJournal = Vcb->UsnJournal;
|
|
|
|
//
|
|
// If the journal is running then the caller needs to match the journal ID.
|
|
//
|
|
|
|
if ((UsnJournal != NULL) &&
|
|
(DeleteData->UsnJournalID != Vcb->UsnJournalInstance.JournalId)) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// Write the bit to disk to indicate that the journal is being deleted.
|
|
// Checkpoint the transaction.
|
|
//
|
|
|
|
NtfsSetVolumeInfoFlagState( IrpContext,
|
|
Vcb,
|
|
VOLUME_DELETE_USN_UNDERWAY,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// We are going to proceed with the delete. Clear the flag in the Vcb that
|
|
// indicates the journal is active. Then acquire and drop all of the files in
|
|
// order to serialize with anyone using the journal.
|
|
//
|
|
|
|
ClearFlag( Vcb->VcbState, VCB_STATE_USN_JOURNAL_ACTIVE );
|
|
|
|
NtfsAcquireAllFiles( IrpContext,
|
|
Vcb,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
|
|
ReleaseUsnJournal = UsnJournal;
|
|
if (UsnJournal != NULL) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, UsnJournal );
|
|
}
|
|
|
|
//
|
|
// Set the delete flag in the Vcb and remove the journal from the Vcb.
|
|
//
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_USN_DELETE );
|
|
NtfsSetSegmentNumber( &Vcb->DeleteUsnData.DeleteUsnFileReference,
|
|
0,
|
|
MASTER_FILE_TABLE_NUMBER );
|
|
|
|
Vcb->DeleteUsnData.DeleteUsnFileReference.SequenceNumber = 0;
|
|
Vcb->DeleteUsnData.DeleteState = 0;
|
|
Vcb->DeleteUsnData.PriorJournalScb = Vcb->UsnJournal;
|
|
Vcb->UsnJournal = NULL;
|
|
|
|
if (UsnJournal != NULL) {
|
|
|
|
//
|
|
// Let's purge the data in the Usn journal and clear the bias
|
|
// and file reference numbers in the Vcb.
|
|
//
|
|
|
|
CcPurgeCacheSection( &UsnJournal->NonpagedScb->SegmentObject,
|
|
NULL,
|
|
0,
|
|
FALSE );
|
|
|
|
ClearFlag( UsnJournal->ScbPersist, SCB_PERSIST_USN_JOURNAL );
|
|
}
|
|
|
|
Vcb->UsnCacheBias = 0;
|
|
*((PLONGLONG) &Vcb->UsnJournalReference) = 0;
|
|
|
|
//
|
|
// Release the checkpoint if held.
|
|
//
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
|
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
CheckpointHeld = FALSE;
|
|
|
|
//
|
|
// Walk through the Irps waiting for new Usn data and cause them to be completed.
|
|
//
|
|
|
|
if (UsnJournal != NULL) {
|
|
|
|
PWAIT_FOR_NEW_LENGTH Waiter, NextWaiter;
|
|
|
|
ExAcquireFastMutex( UsnJournal->Header.FastMutex );
|
|
Waiter = (PWAIT_FOR_NEW_LENGTH) UsnJournal->ScbType.Data.WaitForNewLength.Flink;
|
|
|
|
while (Waiter != (PWAIT_FOR_NEW_LENGTH) &UsnJournal->ScbType.Data.WaitForNewLength) {
|
|
|
|
NextWaiter = (PWAIT_FOR_NEW_LENGTH) Waiter->WaitList.Flink;
|
|
|
|
//
|
|
// We want to complete all of the Irps on the waiting list. If cancel
|
|
// has already been called on the Irp we don't have to do anything.
|
|
// Otherwise complete the async Irps and signal the event on
|
|
// the sync irps.
|
|
//
|
|
|
|
if (NtfsClearCancelRoutine( Waiter->Irp )) {
|
|
|
|
//
|
|
// If this is an async request then complete the Irp.
|
|
//
|
|
|
|
if (FlagOn( Waiter->Flags, NTFS_WAIT_FLAG_ASYNC )) {
|
|
|
|
//
|
|
// Make sure we decrement the reference count in the Scb.
|
|
// Then remove the waiter from the queue and complete the Irp.
|
|
//
|
|
|
|
InterlockedDecrement( &UsnJournal->CloseCount );
|
|
RemoveEntryList( &Waiter->WaitList );
|
|
|
|
NtfsCompleteRequest( NULL, Waiter->Irp, STATUS_JOURNAL_DELETE_IN_PROGRESS );
|
|
NtfsFreePool( Waiter );
|
|
|
|
//
|
|
// This is a synch Irp. All we can do is set the event and note the status
|
|
// code.
|
|
//
|
|
|
|
} else {
|
|
|
|
Waiter->Status = STATUS_JOURNAL_DELETE_IN_PROGRESS;
|
|
KeSetEvent( &Waiter->Event, 0, FALSE );
|
|
}
|
|
}
|
|
|
|
Waiter = NextWaiter;
|
|
}
|
|
|
|
|
|
//
|
|
// Walk through all of the Fcb Usn records and deallocate them.
|
|
//
|
|
|
|
Links = Vcb->ModifiedOpenFiles.Flink;
|
|
|
|
while (Vcb->ModifiedOpenFiles.Flink != &Vcb->ModifiedOpenFiles) {
|
|
|
|
RemoveEntryList( Links );
|
|
Links->Flink = NULL;
|
|
|
|
//
|
|
// Look to see if we need to remove the TimeOut link as well.
|
|
//
|
|
|
|
Links = &(CONTAINING_RECORD( Links, FCB_USN_RECORD, ModifiedOpenFilesLinks ))->TimeOutLinks;
|
|
|
|
if (Links->Flink != NULL) {
|
|
|
|
RemoveEntryList( Links );
|
|
}
|
|
|
|
Links = Vcb->ModifiedOpenFiles.Flink;
|
|
}
|
|
|
|
ExReleaseFastMutex( UsnJournal->Header.FastMutex );
|
|
|
|
//
|
|
// Make sure remove our reference on the Usn journal.
|
|
//
|
|
|
|
NtOfsCloseAttributeSafe( IrpContext, UsnJournal );
|
|
ReleaseUsnJournal = NULL;
|
|
}
|
|
|
|
//
|
|
// If this caller wants to wait for this then acquire the notify
|
|
// mutex now.
|
|
//
|
|
|
|
if (FlagOn( DeleteData->DeleteFlags, USN_DELETE_FLAG_NOTIFY )) {
|
|
|
|
NtfsAcquireUsnNotify( Vcb );
|
|
AcquiredNotify = TRUE;
|
|
}
|
|
|
|
//
|
|
// Post the work item to do the rest of the delete.
|
|
//
|
|
|
|
NtfsPostSpecial( IrpContext, Vcb, NtfsDeleteUsnSpecial, &Vcb->DeleteUsnData );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if our caller wants to wait for the delete to complete.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_USN_DELETE ) &&
|
|
FlagOn( DeleteData->DeleteFlags, USN_DELETE_FLAG_NOTIFY )) {
|
|
|
|
if (!AcquiredNotify) {
|
|
|
|
NtfsAcquireUsnNotify( Vcb );
|
|
AcquiredNotify = TRUE;
|
|
}
|
|
|
|
Status = STATUS_PENDING;
|
|
if (!NtfsSetCancelRoutine( Irp,
|
|
NtfsCancelDeleteUsnJournal,
|
|
0,
|
|
TRUE )) {
|
|
|
|
Status = STATUS_CANCELLED;
|
|
|
|
//
|
|
// Add it to the work queue if we were able to set the
|
|
// cancel routine.
|
|
//
|
|
|
|
} else {
|
|
|
|
InsertTailList( &Vcb->NotifyUsnDeleteIrps,
|
|
&Irp->Tail.Overlay.ListEntry );
|
|
}
|
|
|
|
NtfsReleaseUsnNotify( Vcb );
|
|
AcquiredNotify = FALSE;
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (AcquiredNotify) {
|
|
|
|
NtfsReleaseUsnNotify( Vcb );
|
|
}
|
|
|
|
//
|
|
// Release the Usn journal if held.
|
|
//
|
|
|
|
if (ReleaseUsnJournal) {
|
|
|
|
NtfsReleaseScb( IrpContext, ReleaseUsnJournal );
|
|
}
|
|
|
|
//
|
|
// Release the Vcb if held.
|
|
//
|
|
|
|
if (VcbAcquired) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// Release the checkpoint if held.
|
|
//
|
|
|
|
if (CheckpointHeld) {
|
|
|
|
NtfsAcquireCheckpoint( IrpContext, Vcb );
|
|
ClearFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
|
|
NtfsSetCheckpointNotify( IrpContext, Vcb );
|
|
NtfsReleaseCheckpoint( IrpContext, Vcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Complete the irp as appropriate.
|
|
//
|
|
|
|
NtfsCompleteRequest( IrpContext,
|
|
(Status == STATUS_PENDING) ? NULL : Irp,
|
|
Status );
|
|
return Status;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDeleteUsnSpecial (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to perform the work of deleting a Usn journal for a volume.
|
|
It is called after the original entry point has done the preliminary work of stopping
|
|
future journal activity and cleaning up active journal requests. Once we reach this
|
|
point then this routine will make sure the Mft values are reset, delete the journal
|
|
file itself and wake up anyone waiting for the delete journal to complete.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - context of the call
|
|
|
|
Context - DELETE_USN_CONTEXT structure used to manage the delete.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PNTFS_DELETE_JOURNAL_DATA DeleteData = (PNTFS_DELETE_JOURNAL_DATA) Context;
|
|
ULONG AcquiredVcb = FALSE;
|
|
PVCB Vcb = IrpContext->Vcb;
|
|
PFCB UsnFcb = NULL;
|
|
BOOLEAN AcquiredExtendDirectory = FALSE;
|
|
|
|
PIRP UsnNotifyIrp;
|
|
|
|
PLIST_ENTRY Links;
|
|
PSCB Scb;
|
|
PFCB Fcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Use a try-except to catch errors.
|
|
//
|
|
|
|
try {
|
|
|
|
if (NtfsIsVolumeReadOnly( Vcb )) {
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = STATUS_MEDIA_WRITE_PROTECTED;
|
|
NtfsRaiseStatus( IrpContext, STATUS_MEDIA_WRITE_PROTECTED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Make sure to walk the Mft to set the Usn value back to zero.
|
|
//
|
|
|
|
if (!FlagOn( DeleteData->DeleteState, DELETE_USN_RESET_MFT )) {
|
|
|
|
try {
|
|
|
|
Status = NtfsIterateMft( IrpContext,
|
|
IrpContext->Vcb,
|
|
&DeleteData->DeleteUsnFileReference,
|
|
NtfsDeleteUsnWorker,
|
|
Context );
|
|
|
|
} except (NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status ) && (Status != STATUS_END_OF_FILE)) {
|
|
|
|
//
|
|
// If the operation is going to fail then decide if this is retryable.
|
|
//
|
|
|
|
if (Status == STATUS_VOLUME_DISMOUNTED) {
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = STATUS_VOLUME_DISMOUNTED;
|
|
|
|
} else if ((Status != STATUS_LOG_FILE_FULL) &&
|
|
(Status != STATUS_CANT_WAIT)) {
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = Status;
|
|
|
|
//
|
|
// Set all the flags for delete operations so we stop at this point.
|
|
//
|
|
|
|
SetFlag( DeleteData->DeleteState,
|
|
DELETE_USN_RESET_MFT | DELETE_USN_REMOVE_JOURNAL | DELETE_USN_FINAL_CLEANUP );
|
|
|
|
Status = STATUS_CANT_WAIT;
|
|
}
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
SetFlag( DeleteData->DeleteState, DELETE_USN_RESET_MFT );
|
|
}
|
|
|
|
NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
|
|
AcquiredVcb = TRUE;
|
|
|
|
//
|
|
// If the volume is no longer available then raise STATUS_VOLUME_DISMOUNTED. Someone
|
|
// else will find all of the waiters.
|
|
//
|
|
|
|
if (!NtfsIsVcbAvailable( Vcb )) {
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = STATUS_VOLUME_DISMOUNTED;
|
|
NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// The next step is to remove the file if present.
|
|
//
|
|
|
|
if (!FlagOn( DeleteData->DeleteState, DELETE_USN_REMOVE_JOURNAL )) {
|
|
|
|
try {
|
|
|
|
if (Vcb->ExtendDirectory != NULL) {
|
|
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ExtendDirectory );
|
|
AcquiredExtendDirectory = TRUE;
|
|
|
|
UsnFcb = NtfsInitializeFileInExtendDirectory( IrpContext,
|
|
Vcb,
|
|
&NtfsUsnJrnlName,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
if (UsnFcb != NULL) {
|
|
|
|
//
|
|
// For lock order acquire in canonical order after unsafe try
|
|
//
|
|
|
|
if (!NtfsAcquireExclusiveFcb( IrpContext, UsnFcb, NULL, ACQUIRE_NO_DELETE_CHECK | ACQUIRE_DONT_WAIT)) {
|
|
NtfsReleaseScb( IrpContext, Vcb->ExtendDirectory );
|
|
NtfsAcquireExclusiveFcb( IrpContext, UsnFcb, NULL, ACQUIRE_NO_DELETE_CHECK );
|
|
NtfsAcquireExclusiveScb( IrpContext, Vcb->ExtendDirectory );
|
|
}
|
|
|
|
NtfsDeleteFile( IrpContext,
|
|
UsnFcb,
|
|
Vcb->ExtendDirectory,
|
|
&AcquiredExtendDirectory,
|
|
NULL,
|
|
NULL );
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
ClearFlag( UsnFcb->FcbState, FCB_STATE_SYSTEM_FILE );
|
|
SetFlag( UsnFcb->FcbState, FCB_STATE_FILE_DELETED );
|
|
|
|
//
|
|
// Walk all of the Scbs for this file and mark them
|
|
// deleted. This will keep the lazy writer from trying to
|
|
// flush them.
|
|
//
|
|
|
|
Links = UsnFcb->ScbQueue.Flink;
|
|
|
|
while (Links != &UsnFcb->ScbQueue) {
|
|
|
|
Scb = CONTAINING_RECORD( Links, SCB, FcbLinks );
|
|
|
|
//
|
|
// Recover the reservation for the Scb now instead of waiting for it
|
|
// to go away.
|
|
//
|
|
|
|
if ((Scb->AttributeTypeCode == $DATA) &&
|
|
(Scb->ScbType.Data.TotalReserved != 0)) {
|
|
|
|
NtfsAcquireReservedClusters( Vcb );
|
|
|
|
Vcb->TotalReserved -= LlClustersFromBytes( Vcb,
|
|
Scb->ScbType.Data.TotalReserved );
|
|
Scb->ScbType.Data.TotalReserved = 0;
|
|
NtfsReleaseReservedClusters( Vcb );
|
|
}
|
|
|
|
Scb->ValidDataToDisk =
|
|
Scb->Header.AllocationSize.QuadPart =
|
|
Scb->Header.FileSize.QuadPart =
|
|
Scb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
Scb->AttributeTypeCode = $UNUSED;
|
|
SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
|
|
|
|
Links = Links->Flink;
|
|
}
|
|
|
|
//
|
|
// Now teardown the Fcb.
|
|
//
|
|
|
|
NtfsTeardownStructures( IrpContext,
|
|
UsnFcb,
|
|
NULL,
|
|
FALSE,
|
|
ACQUIRE_NO_DELETE_CHECK,
|
|
NULL );
|
|
}
|
|
}
|
|
|
|
} except (NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
|
|
|
|
//
|
|
// We hit some failure and can't complete the operation.
|
|
// Remember the error, set the flags in the delete Usn structure
|
|
// and raise CANT_WAIT so we can abort and then do the final cleanup.
|
|
//
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = Status;
|
|
|
|
//
|
|
// Set all the flags for delete operations so we stop at this point.
|
|
//
|
|
|
|
SetFlag( DeleteData->DeleteState,
|
|
DELETE_USN_RESET_MFT | DELETE_USN_REMOVE_JOURNAL | DELETE_USN_FINAL_CLEANUP );
|
|
|
|
Status = STATUS_CANT_WAIT;
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
SetFlag( DeleteData->DeleteState, DELETE_USN_REMOVE_JOURNAL );
|
|
}
|
|
|
|
if (!FlagOn( DeleteData->DeleteState, DELETE_USN_FINAL_CLEANUP )) {
|
|
|
|
//
|
|
// Clear the on-disk flag indicating the delete is in progress.
|
|
//
|
|
|
|
try {
|
|
|
|
NtfsSetVolumeInfoFlagState( IrpContext,
|
|
Vcb,
|
|
VOLUME_DELETE_USN_UNDERWAY,
|
|
FALSE,
|
|
TRUE );
|
|
|
|
} except (NtfsCleanupExceptionFilter( IrpContext, GetExceptionInformation(), &Status )) {
|
|
|
|
//
|
|
// We hit some failure and can't complete the operation.
|
|
// Remember the error, set the flags in the delete Usn structure
|
|
// and raise CANT_WAIT so we can abort and then do the final cleanup.
|
|
//
|
|
|
|
Vcb->DeleteUsnData.FinalStatus = Status;
|
|
|
|
//
|
|
// Set all the flags for delete operations so we stop at this point.
|
|
//
|
|
|
|
SetFlag( DeleteData->DeleteState,
|
|
DELETE_USN_RESET_MFT | DELETE_USN_REMOVE_JOURNAL | DELETE_USN_FINAL_CLEANUP );
|
|
|
|
Status = STATUS_CANT_WAIT;
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we don't own any resources at this point.
|
|
//
|
|
|
|
NtfsPurgeFileRecordCache( IrpContext );
|
|
NtfsCheckpointCurrentTransaction( IrpContext );
|
|
|
|
//
|
|
// Finally, now that we have written the forget record, we can free
|
|
// any exclusive Scbs that we have been holding.
|
|
//
|
|
|
|
while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
|
|
|
|
Fcb = (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
|
|
FCB,
|
|
ExclusiveFcbLinks );
|
|
|
|
NtfsReleaseFcb( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// Remember any saved status code.
|
|
//
|
|
|
|
if (Vcb->DeleteUsnData.FinalStatus != STATUS_SUCCESS) {
|
|
|
|
Status = Vcb->DeleteUsnData.FinalStatus;
|
|
|
|
//
|
|
// Since we failed make sure to leave the flag set in the Vcb which indicates the
|
|
// incomplete delete.
|
|
//
|
|
|
|
SetFlag( Vcb->VcbState, VCB_STATE_INCOMPLETE_USN_DELETE );
|
|
}
|
|
|
|
//
|
|
// Cleanup the context and flags in the Vcb.
|
|
//
|
|
|
|
RtlZeroMemory( &Vcb->DeleteUsnData, sizeof( NTFS_DELETE_JOURNAL_DATA ));
|
|
RtlZeroMemory( &Vcb->UsnJournalInstance, sizeof( USN_JOURNAL_INSTANCE ));
|
|
Vcb->FirstValidUsn = 0;
|
|
Vcb->LowestOpenUsn = 0;
|
|
|
|
ClearFlag( Vcb->VcbState, VCB_STATE_USN_JOURNAL_PRESENT | VCB_STATE_USN_DELETE );
|
|
|
|
//
|
|
// Finally complete all of the waiting Irps in the Usn notify queue.
|
|
//
|
|
|
|
NtfsAcquireUsnNotify( Vcb );
|
|
|
|
Links = Vcb->NotifyUsnDeleteIrps.Flink;
|
|
|
|
while (Links != &Vcb->NotifyUsnDeleteIrps) {
|
|
|
|
UsnNotifyIrp = CONTAINING_RECORD( Links,
|
|
IRP,
|
|
Tail.Overlay.ListEntry );
|
|
|
|
//
|
|
// Remember to move forward in any case.
|
|
//
|
|
|
|
Links = Links->Flink;
|
|
|
|
//
|
|
// Clear the notify routine and detect if cancel has
|
|
// already been called.
|
|
//
|
|
|
|
if (NtfsClearCancelRoutine( UsnNotifyIrp )) {
|
|
|
|
RemoveEntryList( &UsnNotifyIrp->Tail.Overlay.ListEntry );
|
|
NtfsCompleteRequest( NULL, UsnNotifyIrp, Status );
|
|
}
|
|
}
|
|
|
|
NtfsReleaseUsnNotify( Vcb );
|
|
|
|
} except( NtfsExceptionFilter( IrpContext, GetExceptionInformation())) {
|
|
|
|
Status = IrpContext->TopLevelIrpContext->ExceptionStatus;
|
|
}
|
|
|
|
if (AcquiredVcb) {
|
|
|
|
NtfsReleaseVcb( IrpContext, Vcb );
|
|
}
|
|
|
|
//
|
|
// If this is a fatal failure then do any final cleanup.
|
|
//
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
|
|
}
|
|
|
|
return;
|
|
|
|
UNREFERENCED_PARAMETER( Context );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS
|
|
NtfsUsnTableCompare (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
PVOID FirstStruct,
|
|
PVOID SecondStruct
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine to compare two File References
|
|
in Usn Records.
|
|
|
|
Arguments:
|
|
|
|
Table - Supplies the generic table being queried. Not used.
|
|
|
|
FirstStruct - Supplies the first Usn Record to compare
|
|
|
|
SecondStruct - Supplies the second Usn Record to compare
|
|
|
|
Return Value:
|
|
|
|
RTL_GENERIC_COMPARE_RESULTS - The results of comparing the two
|
|
input structures
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (*((PLONGLONG) &((PUSN_RECORD) FirstStruct)->FileReferenceNumber) <
|
|
*((PLONGLONG) &((PUSN_RECORD) SecondStruct)->FileReferenceNumber)) {
|
|
|
|
return GenericLessThan;
|
|
}
|
|
|
|
if (*((PLONGLONG) &((PUSN_RECORD) FirstStruct)->FileReferenceNumber) >
|
|
*((PLONGLONG) &((PUSN_RECORD) SecondStruct)->FileReferenceNumber)) {
|
|
|
|
return GenericGreaterThan;
|
|
}
|
|
|
|
return GenericEqual;
|
|
|
|
UNREFERENCED_PARAMETER( Table );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
PVOID
|
|
NtfsUsnTableAllocate (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
CLONG ByteSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine to allocate memory
|
|
|
|
Arguments:
|
|
|
|
Table - Supplies the generic table being used
|
|
|
|
ByteSize - Supplies the number of bytes to allocate
|
|
|
|
Return Value:
|
|
|
|
PVOID - Returns a pointer to the allocated data
|
|
|
|
--*/
|
|
|
|
{
|
|
UNREFERENCED_PARAMETER( Table );
|
|
|
|
PAGED_CODE();
|
|
|
|
return NtfsAllocatePool( PagedPool, ByteSize );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsUsnTableFree (
|
|
IN PRTL_GENERIC_TABLE Table,
|
|
IN PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a generic table support routine to free memory
|
|
|
|
Arguments:
|
|
|
|
Table - Supplies the generic table being used
|
|
|
|
Buffer - Supplies pointer to the buffer to be freed
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
UNREFERENCED_PARAMETER( Table );
|
|
|
|
PAGED_CODE();
|
|
|
|
NtfsFreePool( Buffer );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsCancelReadUsnJournal (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine may be called by the I/O system to cancel an outstanding
|
|
Irp in NtfsReadUsnJournal.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - DeviceObject from I/O system
|
|
|
|
Irp - Supplies the pointer to the Irp being canceled.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PWAIT_FOR_NEW_LENGTH WaitForNewLength;
|
|
|
|
IoSetCancelRoutine( Irp, NULL );
|
|
IoReleaseCancelSpinLock( Irp->CancelIrql );
|
|
|
|
//
|
|
// Capture the Wait block out of the Status field. We know the Irp can't
|
|
// go away at this point.
|
|
//
|
|
|
|
WaitForNewLength = (PWAIT_FOR_NEW_LENGTH) Irp->IoStatus.Information;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
//
|
|
// Take a different action depending on whether we are completing the irp
|
|
// or simply signaling the cancel.
|
|
//
|
|
|
|
|
|
//
|
|
// This is the async case. We can simply complete this irp.
|
|
//
|
|
|
|
if (FlagOn( WaitForNewLength->Flags, NTFS_WAIT_FLAG_ASYNC )) {
|
|
|
|
//
|
|
// Acquire the mutex in order to remove this from the list and complete
|
|
// the Irp.
|
|
//
|
|
|
|
ExAcquireFastMutex( WaitForNewLength->Stream->Header.FastMutex );
|
|
RemoveEntryList( &WaitForNewLength->WaitList );
|
|
ExReleaseFastMutex( WaitForNewLength->Stream->Header.FastMutex );
|
|
|
|
InterlockedDecrement( &WaitForNewLength->Stream->CloseCount );
|
|
|
|
NtfsCompleteRequest( NULL, Irp, STATUS_CANCELLED );
|
|
NtfsFreePool( WaitForNewLength );
|
|
|
|
//
|
|
// If there is not an Irp we simply signal the event and let someone else
|
|
// do the work. This is the synchronous case.
|
|
//
|
|
|
|
} else {
|
|
|
|
WaitForNewLength->Status = STATUS_CANCELLED;
|
|
KeSetEvent( &WaitForNewLength->Event, 0, FALSE );
|
|
}
|
|
|
|
return;
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsCancelDeleteUsnJournal (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine may be called by the I/O system to cancel an outstanding
|
|
Irp waiting for the usn journal to be deleted.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - DeviceObject from I/O system
|
|
|
|
Irp - Supplies the pointer to the Irp being canceled.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PFILE_OBJECT FileObject;
|
|
PVCB Vcb;
|
|
|
|
//
|
|
// Block out future cancels.
|
|
//
|
|
|
|
IoSetCancelRoutine( Irp, NULL );
|
|
|
|
IoReleaseCancelSpinLock( Irp->CancelIrql );
|
|
|
|
//
|
|
// Get the current Irp stack location and save some references.
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Capture the Vcb so we can do the necessary synchronization.
|
|
//
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
Vcb = ((PSCB)(FileObject->FsContext))->Vcb;
|
|
|
|
//
|
|
// Acquire the list and remove the Irp. Complete the Irp with
|
|
// STATUS_CANCELLED.
|
|
//
|
|
|
|
NtfsAcquireUsnNotify( Vcb );
|
|
RemoveEntryList( &Irp->Tail.Overlay.ListEntry );
|
|
NtfsReleaseUsnNotify( Vcb );
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
NtfsCompleteRequest( NULL, Irp, STATUS_CANCELLED );
|
|
|
|
return;
|
|
|
|
UNREFERENCED_PARAMETER( DeviceObject );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
NtfsDeleteUsnWorker (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routines resets the Usn in the file record for the Fcb to zero.
|
|
|
|
Arguments:
|
|
|
|
IrpContext - context of the call
|
|
|
|
Fcb - Fcb for the file record to clear
|
|
|
|
Context - Unused
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PVOID UsnRecord;
|
|
|
|
ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
|
|
PATTRIBUTE_RECORD_HEADER Attribute;
|
|
STANDARD_INFORMATION NewStandardInformation;
|
|
USN Usn = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Initialize the search context.
|
|
//
|
|
|
|
NtfsInitializeAttributeContext( &AttrContext );
|
|
|
|
//
|
|
// Use a try-except to catch all of the errors.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Use a try-finally to facilitate cleanup.
|
|
//
|
|
|
|
try {
|
|
|
|
//
|
|
// Look up the standard information attribute and modify the usn field if
|
|
// the attribute is found and it is a large standard attribute.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO ) &&
|
|
NtfsLookupAttributeByCode( IrpContext,
|
|
Fcb,
|
|
&Fcb->FileReference,
|
|
$STANDARD_INFORMATION,
|
|
&AttrContext )) {
|
|
|
|
Attribute = NtfsFoundAttribute( &AttrContext );
|
|
|
|
if (((PSTANDARD_INFORMATION) NtfsAttributeValue( Attribute ))->Usn != 0) {
|
|
|
|
RtlCopyMemory( &NewStandardInformation,
|
|
NtfsAttributeValue( Attribute ),
|
|
sizeof( STANDARD_INFORMATION ));
|
|
|
|
NewStandardInformation.Usn = 0;
|
|
|
|
NtfsChangeAttributeValue( IrpContext,
|
|
Fcb,
|
|
0,
|
|
&NewStandardInformation,
|
|
sizeof( STANDARD_INFORMATION ),
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
FALSE,
|
|
&AttrContext );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the Fcb reflects this change.
|
|
//
|
|
|
|
NtfsLockFcb( IrpContext, Fcb );
|
|
|
|
Fcb->Usn = 0;
|
|
UsnRecord = Fcb->FcbUsnRecord;
|
|
Fcb->FcbUsnRecord = NULL;
|
|
|
|
NtfsUnlockFcb( IrpContext, Fcb );
|
|
|
|
if (UsnRecord != NULL) {
|
|
|
|
NtfsFreePool( UsnRecord );
|
|
}
|
|
|
|
} finally {
|
|
|
|
//
|
|
// Be sure to clean up the context.
|
|
//
|
|
|
|
NtfsCleanupAttributeContext( IrpContext, &AttrContext );
|
|
}
|
|
|
|
//
|
|
// We want to swallow any expected errors except LOG_FILE_FULL and CANT_WAIT.
|
|
//
|
|
|
|
} except ((FsRtlIsNtstatusExpected( Status = GetExceptionCode()) &&
|
|
(Status != STATUS_LOG_FILE_FULL) &&
|
|
(Status != STATUS_CANT_WAIT)) ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH) {
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
//
|
|
// Always return success from this routine.
|
|
//
|
|
|
|
IrpContext->ExceptionStatus = STATUS_SUCCESS;
|
|
return STATUS_SUCCESS;
|
|
|
|
UNREFERENCED_PARAMETER( Context );
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsValidateUsnPage (
|
|
IN PUSN_RECORD UsnRecord,
|
|
IN USN PageUsn,
|
|
IN USN *UserStartUsn OPTIONAL,
|
|
IN LONGLONG UsnFileSize,
|
|
OUT PBOOLEAN ValidUserStartUsn OPTIONAL,
|
|
OUT USN *NextUsn
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the offsets within a single page of the usn journal. This allows the caller to
|
|
then walk safely through the page.
|
|
|
|
Arguments:
|
|
|
|
UsnRecord - Pointer to the start of the Usn page.
|
|
|
|
PageUsn - This is the Usn for the first record of the page.
|
|
|
|
UserStartUsn - If specified then do an additional check that the user's specified usn in fact
|
|
lies correctly on this page. The output boolean must also be specified if this is.
|
|
|
|
UsnFileSize - This is the current size of the usn journal. If we are looking at the last page then
|
|
we only check to this point.
|
|
|
|
ValidUserStartUsn - Address to result of check on user specified start Usn.
|
|
|
|
NextUsn - This is the Usn past the valid portion of the page. It will point to a position on the
|
|
current page unless the last record on the page completely fills the page. If the page isn't valid
|
|
then it points to the position where the invalid record was detected.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the page is valid until a legal terminating condition. FALSE if there is internal
|
|
corruption on the page.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG RemainingPageBytes;
|
|
ULONG RecordLength;
|
|
BOOLEAN ValidPage = TRUE;
|
|
BOOLEAN FoundEntry = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Verify a few input values.
|
|
//
|
|
|
|
ASSERT( UsnFileSize > PageUsn );
|
|
ASSERT( !FlagOn( *((PULONG) &UsnRecord), USN_PAGE_SIZE - 1 ));
|
|
ASSERT( !ARGUMENT_PRESENT( UserStartUsn ) || ARGUMENT_PRESENT( ValidUserStartUsn ));
|
|
ASSERT( !ARGUMENT_PRESENT( ValidUserStartUsn ) || ARGUMENT_PRESENT( UserStartUsn ));
|
|
|
|
//
|
|
// Compute the Usn past the valid data on this page. It is either the end of the journal or
|
|
// the next page of the journal.
|
|
//
|
|
|
|
RemainingPageBytes = USN_PAGE_SIZE;
|
|
|
|
if (UsnFileSize < (PageUsn + USN_PAGE_SIZE)) {
|
|
|
|
RemainingPageBytes = (ULONG) (UsnFileSize - PageUsn);
|
|
}
|
|
|
|
//
|
|
// Assume the user's Usn is invalid unless it wasn't specified.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( ValidUserStartUsn )) {
|
|
|
|
ValidUserStartUsn = (PBOOLEAN) NtfsAllocateFromStack( sizeof( BOOLEAN ));
|
|
*ValidUserStartUsn = TRUE;
|
|
|
|
} else {
|
|
|
|
*ValidUserStartUsn = FALSE;
|
|
}
|
|
|
|
//
|
|
// Keep track of our current position in the page with the user's pointer.
|
|
//
|
|
|
|
*NextUsn = PageUsn;
|
|
|
|
//
|
|
// Check each entry in the page for the following.
|
|
//
|
|
// 1 - Fixed portion of the header won't fit within the remaining bytes on the page.
|
|
// 2 - Record header is zeroed.
|
|
// 3 - Record length is not quad-aligned.
|
|
// 4 - Record length is larger than the remaining bytes on the page.
|
|
// 5 - Usn on the page doesn't match the computed value.
|
|
//
|
|
|
|
while (RemainingPageBytes != 0) {
|
|
|
|
//
|
|
// Not enough bytes even for the full Usn header.
|
|
//
|
|
|
|
if (RemainingPageBytes < (FIELD_OFFSET( USN_RECORD, FileName ) + sizeof( WCHAR ))) {
|
|
|
|
//
|
|
// If there is at least a ulong it better be zeroed.
|
|
//
|
|
|
|
if ((RemainingPageBytes >= sizeof( ULONG )) &&
|
|
(UsnRecord->RecordLength != 0)) {
|
|
|
|
ValidPage = FALSE;
|
|
|
|
//
|
|
// If the user's Usn points to this offset then it is valid.
|
|
//
|
|
|
|
} else if (!(*ValidUserStartUsn) &&
|
|
(*NextUsn == *UserStartUsn)) {
|
|
|
|
*ValidUserStartUsn = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// There should be at least one entry on the page. We attempt to detect
|
|
// a local loss of data through zeroing but won't check to the end of
|
|
// the page.
|
|
//
|
|
|
|
RecordLength = UsnRecord->RecordLength;
|
|
if (RecordLength == 0) {
|
|
|
|
//
|
|
// Fail if we haven't found at least one entry.
|
|
//
|
|
|
|
if (!FoundEntry) {
|
|
|
|
ValidPage = FALSE;
|
|
|
|
//
|
|
// We know we should be dealing with the tail of the page. It should
|
|
// be zeroed through the fixed portion of a Usn record. Theoretically
|
|
// it should be zeroed to the end of the page but we will assume that we
|
|
// are only looking for local corruption. If we lost data through the
|
|
// end of the page we can't detect it anyway.
|
|
//
|
|
|
|
} else {
|
|
|
|
PCHAR CurrentByte = (PCHAR) UsnRecord;
|
|
ULONG Count = FIELD_OFFSET( USN_RECORD, FileName ) + sizeof( WCHAR );
|
|
|
|
while (Count != 0) {
|
|
|
|
if (*CurrentByte != 0) {
|
|
|
|
ValidPage = FALSE;
|
|
break;
|
|
}
|
|
|
|
Count -= 1;
|
|
CurrentByte += 1;
|
|
}
|
|
|
|
//
|
|
// If the page is valid then check if the user's Usn is at this point. It is
|
|
// legal for him to specify the point where the zeroes begin.
|
|
//
|
|
|
|
if (ValidPage &&
|
|
!(*ValidUserStartUsn) &&
|
|
(*NextUsn == *UserStartUsn)) {
|
|
|
|
*ValidUserStartUsn = TRUE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Invalid if record length is not-quad aligned or is larger than
|
|
// remaining bytes on the page.
|
|
//
|
|
|
|
if (FlagOn( RecordLength, sizeof( ULONGLONG ) - 1 ) ||
|
|
(RecordLength > RemainingPageBytes)) {
|
|
|
|
ValidPage = FALSE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now check that the Usn is the expected value.
|
|
//
|
|
|
|
if (UsnRecord->Usn != *NextUsn) {
|
|
|
|
ValidPage = FALSE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Remember that we found a valid entry.
|
|
//
|
|
|
|
FoundEntry = TRUE;
|
|
|
|
//
|
|
// If the user's Usn matches this one then remember his is valid.
|
|
//
|
|
|
|
if (!(*ValidUserStartUsn) &&
|
|
(*NextUsn == *UserStartUsn)) {
|
|
|
|
*ValidUserStartUsn = TRUE;
|
|
}
|
|
|
|
//
|
|
// Advance to the next record in the page.
|
|
//
|
|
|
|
UsnRecord = Add2Ptr( UsnRecord, RecordLength );
|
|
|
|
RemainingPageBytes -= RecordLength;
|
|
*NextUsn += RecordLength;
|
|
}
|
|
|
|
return ValidPage;
|
|
}
|