/*++ Copyright (c) 1996 Microsoft Corporation Module Name: VAttrSup.c Abstract: This module implements the attribute routines for NtOfs Author: Tom Miller [TomM] 10-Apr-1996 Revision History: --*/ #include "NtfsProc.h" // // Define a tag for general pool allocations from this module // #undef MODULE_POOL_TAG #define MODULE_POOL_TAG ('vFtN') #undef NtOfsMapAttribute NTFSAPI VOID NtOfsMapAttribute ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PVOID *Buffer, OUT PMAP_HANDLE MapHandle ); #undef NtOfsPreparePinWrite NTFSAPI VOID NtOfsPreparePinWrite ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PVOID *Buffer, OUT PMAP_HANDLE MapHandle ); #undef NtOfsPinRead NTFSAPI VOID NtOfsPinRead( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PMAP_HANDLE MapHandle ); #undef NtOfsReleaseMap NTFSAPI VOID NtOfsReleaseMap ( IN PIRP_CONTEXT IrpContext, IN PMAP_HANDLE MapHandle ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtOfsCreateAttribute) #pragma alloc_text(PAGE, NtOfsCreateAttributeEx) #pragma alloc_text(PAGE, NtOfsCloseAttribute) #pragma alloc_text(PAGE, NtOfsDeleteAttribute) #pragma alloc_text(PAGE, NtOfsQueryLength) #pragma alloc_text(PAGE, NtOfsSetLength) #pragma alloc_text(PAGE, NtfsHoldIrpForNewLength) #pragma alloc_text(PAGE, NtOfsPostNewLength) #pragma alloc_text(PAGE, NtOfsFlushAttribute) #pragma alloc_text(PAGE, NtOfsPutData) #pragma alloc_text(PAGE, NtOfsMapAttribute) #pragma alloc_text(PAGE, NtOfsReleaseMap) #endif NTFSAPI NTSTATUS NtOfsCreateAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN UNICODE_STRING Name, IN CREATE_OPTIONS CreateOptions, IN ULONG LogNonresidentToo, OUT PSCB *ReturnScb ) /*++ Routine Description: This routine may be called to create / open a named data attribute within a given file, which may or may not be recoverable. Arguments: Fcb - File in which the attribute is to be created. It is acquired exclusive Name - Name of the attribute for all related Scbs and attributes on disk. CreateOptions - Standard create flags. LogNonresidentToo - Supplies nonzero if updates to the attribute should be logged. ReturnScb - Returns an Scb as handle for the attribute. Return Value: STATUS_OBJECT_NAME_COLLISION -- if CreateNew and attribute already exists STATUS_OBJECT_NAME_NOT_FOUND -- if OpenExisting and attribute does not exist --*/ { return NtOfsCreateAttributeEx( IrpContext, Fcb, Name, $DATA, CreateOptions, LogNonresidentToo, ReturnScb ); } NTFSAPI NTSTATUS NtOfsCreateAttributeEx ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN UNICODE_STRING Name, IN ATTRIBUTE_TYPE_CODE AttributeTypeCode, IN CREATE_OPTIONS CreateOptions, IN ULONG LogNonresidentToo, OUT PSCB *ReturnScb ) /*++ Routine Description: This routine may be called to create / open a named data attribute within a given file, which may or may not be recoverable. Arguments: Fcb - File in which the attribute is to be created. It is acquired exclusive Name - Name of the attribute for all related Scbs and attributes on disk. CreateOptions - Standard create flags. LogNonresidentToo - Supplies nonzero if updates to the attribute should be logged. ReturnScb - Returns an Scb as handle for the attribute. Return Value: STATUS_OBJECT_NAME_COLLISION -- if CreateNew and attribute already exists STATUS_OBJECT_NAME_NOT_FOUND -- if OpenExisting and attribute does not exist --*/ { ATTRIBUTE_ENUMERATION_CONTEXT LocalContext; BOOLEAN FoundAttribute; NTSTATUS Status = STATUS_SUCCESS; PSCB Scb = NULL; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT( NtfsIsExclusiveFcb( Fcb )); PAGED_CODE(); if (AttributeTypeCode != $DATA && AttributeTypeCode != $LOGGED_UTILITY_STREAM) { ASSERTMSG( "Invalid attribute type code in NtOfsCreateAttributeEx", FALSE ); *ReturnScb = NULL; return STATUS_INVALID_PARAMETER; } // // Now, just create the Data Attribute. // NtfsInitializeAttributeContext( &LocalContext ); try { // // First see if the attribute already exists, by searching for the root // attribute. // FoundAttribute = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, AttributeTypeCode, &Name, NULL, TRUE, &LocalContext ); // // If it is not there, and the CreateOptions allow, then let's create // the attribute root now. (First cleaning up the attribute context from // the lookup). // if (!FoundAttribute && (CreateOptions <= CREATE_OR_OPEN)) { // // Make sure we acquire the quota resource before creating the stream. Just // in case we need the Mft during the create. // if (NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode ) && NtfsPerformQuotaOperation( Fcb )) { // // The quota index must be acquired before the mft scb is acquired. // ASSERT( !NtfsIsExclusiveScb( Fcb->Vcb->MftScb ) || ExIsResourceAcquiredSharedLite( Fcb->Vcb->QuotaTableScb->Fcb->Resource )); NtfsAcquireQuotaControl( IrpContext, Fcb->QuotaControl ); } NtfsCleanupAttributeContext( IrpContext, &LocalContext ); NtfsCreateAttributeWithValue( IrpContext, Fcb, AttributeTypeCode, &Name, NULL, 0, 0, NULL, TRUE, &LocalContext ); // // If the attribute is already there, and we were asked to create it, then // return an error. // } else if (FoundAttribute && (CreateOptions == CREATE_NEW)) { Status = STATUS_OBJECT_NAME_COLLISION; leave; // // If the attribute is not there, and we were supposed to open existing, then // return an error. // } else if (!FoundAttribute) { Status = STATUS_OBJECT_NAME_NOT_FOUND; leave; } // // Otherwise create/find the Scb and reference it. // Scb = NtfsCreateScb( IrpContext, Fcb, AttributeTypeCode, &Name, FALSE, &FoundAttribute ); // // Make sure things are correctly reference counted // NtfsIncrementCloseCounts( Scb, TRUE, FALSE ); // // If we created the Scb, then get the no modified write set correctly. // ASSERT( !FoundAttribute || (LogNonresidentToo == BooleanFlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE)) ); if (!FoundAttribute && LogNonresidentToo) { SetFlag( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE ); Scb->Header.ValidDataLength.QuadPart = MAXLONGLONG; } // // Make sure the stream can be mapped internally. Defer this for the Usn journal // until we set up the journal bias. // if ((Scb->FileObject == NULL) && !FlagOn( Scb->ScbPersist, SCB_PERSIST_USN_JOURNAL )) { NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE, NULL ); } NtfsUpdateScbFromAttribute( IrpContext, Scb, NtfsFoundAttribute(&LocalContext) ); } finally { if (AbnormalTermination( )) { if (Scb != NULL) { NtOfsCloseAttribute( IrpContext, Scb ); } } NtfsCleanupAttributeContext( IrpContext, &LocalContext ); } *ReturnScb = Scb; return Status; } NTFSAPI VOID NtOfsCloseAttribute ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb ) /*++ Routine Description: This routine may be called to close a previously returned handle on an attribute. Arguments: Scb - Supplies an Scb as the previously returned handle for this attribute. Return Value: None. --*/ { ASSERT( NtfsIsExclusiveFcb( Scb->Fcb )); PAGED_CODE(); // // We either need the caller to empty this list before closing (as assumed here), // or possibly empty it here. At this point it seems better to assume that the // caller must take action to insure any waiting threads will shutdown and not // touch the stream anymore, then call NtOfsPostNewLength to flush the queue. // If the queue is nonempty here, maybe the caller didn't think this through! // ASSERT( IsListEmpty( &Scb->ScbType.Data.WaitForNewLength ) || (Scb->CloseCount > 1) ); NtfsDecrementCloseCounts( IrpContext, Scb, NULL, TRUE, FALSE, TRUE ); } NTFSAPI VOID NtOfsDeleteAttribute ( IN PIRP_CONTEXT IrpContext, IN PFCB Fcb, IN PSCB Scb ) /*++ Routine Description: This routine may be called to delete an attribute with type code $LOGGED_UTILITY_STREAM. Arguments: Fcb - Supplies an Fcb as the previously returned object handle for the file Scb - Supplies an Scb as the previously returned handle for this attribute. Return Value: None (Deleting a nonexistant index is benign). --*/ { ATTRIBUTE_ENUMERATION_CONTEXT LocalContext; BOOLEAN FoundAttribute; ASSERT_IRP_CONTEXT( IrpContext ); PAGED_CODE(); NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, 0 ); try { // // Make sure we aren't deleting a data stream. We do this after // initializing the attribute context to make the finally clause simpler. // This test can be removed if some trusted component using the NtOfs // API has a legitimate need to delete other types of attributes. // NtfsInitializeAttributeContext( &LocalContext ); if (Scb->AttributeTypeCode != $LOGGED_UTILITY_STREAM) { leave; } // // First see if there is some attribute allocation, and if so truncate it // away allowing this operation to be broken up. // if (NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, Scb->AttributeTypeCode, &Scb->AttributeName, NULL, FALSE, &LocalContext ) && !NtfsIsAttributeResident( NtfsFoundAttribute( &LocalContext ))) { ASSERT( Scb->FileObject != NULL ); NtfsDeleteAllocation( IrpContext, NULL, Scb, 0, MAXLONGLONG, TRUE, TRUE ); } NtfsCleanupAttributeContext( IrpContext, &LocalContext ); // // Initialize the attribute context on each trip through the loop. // NtfsInitializeAttributeContext( &LocalContext ); // // Now there should be a single attribute record, so look it up and delete it. // FoundAttribute = NtfsLookupAttributeByName( IrpContext, Fcb, &Fcb->FileReference, Scb->AttributeTypeCode, &Scb->AttributeName, NULL, TRUE, &LocalContext ); // // If this stream is subject to quota, make sure the quota has been enlarged. // NtfsDeleteAttributeRecord( IrpContext, Fcb, (DELETE_LOG_OPERATION | DELETE_RELEASE_FILE_RECORD | DELETE_RELEASE_ALLOCATION), &LocalContext ); SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ); } finally { NtfsReleaseFcb( IrpContext, Fcb ); NtfsCleanupAttributeContext( IrpContext, &LocalContext ); } return; } NTFSAPI LONGLONG NtOfsQueryLength ( IN PSCB Scb ) /*++ Routine Description: This routine may be called to query the Length (FileSize) of an attribute. Arguments: Scb - Supplies an Scb as the previously returned handle for this attribute. Length - Returns the current Length of the attribute. Return Value: None (Deleting a nonexistant index is benign). --*/ { LONGLONG Length; PAGED_CODE(); ExAcquireFastMutex( Scb->Header.FastMutex ); Length = Scb->Header.FileSize.QuadPart; ExReleaseFastMutex( Scb->Header.FastMutex ); return Length; } NTFSAPI VOID NtOfsSetLength ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Length ) /*++ Routine Description: This routine may be called to set the Length (FileSize) of an attribute. Arguments: Scb - Supplies an Scb as the previously returned handle for this attribute. Length - Supplies the new Length for the attribute. Return Value: None (Deleting a nonexistant index is benign). --*/ { ATTRIBUTE_ENUMERATION_CONTEXT AttrContext; PFILE_OBJECT FileObject = Scb->FileObject; PFCB Fcb = Scb->Fcb; PVCB Vcb = Scb->Vcb; BOOLEAN DoingIoAtEof = FALSE; BOOLEAN Truncating = FALSE; BOOLEAN CleanupAttrContext = FALSE; ASSERT_IRP_CONTEXT( IrpContext ); ASSERT_SCB( Scb ); ASSERT( NtfsIsExclusiveScb( Scb )); ASSERT(FileObject != NULL); PAGED_CODE(); try { // // If this is a resident attribute we will try to keep it resident. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) { // // If the new file size is larger than a file record then convert // to non-resident and use the non-resident code below. Otherwise // call ChangeAttributeValue which may also convert to nonresident. // NtfsInitializeAttributeContext( &AttrContext ); CleanupAttrContext = TRUE; NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext ); // // Either convert or change the attribute value. // if (Length >= Scb->Vcb->BytesPerFileRecordSegment) { NtfsConvertToNonresident( IrpContext, Fcb, NtfsFoundAttribute( &AttrContext ), FALSE, &AttrContext ); } else { ULONG AttributeOffset; // // We are sometimes called by MM during a create section, so // for right now the best way we have of detecting a create // section is whether or not the requestor mode is kernel. // if ((ULONG)Length > Scb->Header.FileSize.LowPart) { AttributeOffset = Scb->Header.FileSize.LowPart; } else { AttributeOffset = (ULONG) Length; } // // ****TEMP Ideally we would do this simple case by hand. // NtfsChangeAttributeValue( IrpContext, Fcb, AttributeOffset, NULL, (ULONG)Length - AttributeOffset, TRUE, FALSE, FALSE, FALSE, &AttrContext ); ExAcquireFastMutex( Scb->Header.FastMutex ); Scb->Header.FileSize.QuadPart = Length; // // If the file went non-resident, then the allocation size in // the Scb is correct. Otherwise we quad-align the new file size. // if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) { Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.FileSize.LowPart ); if (Scb->Header.ValidDataLength.QuadPart != MAXLONGLONG) { Scb->Header.ValidDataLength.QuadPart = Length; } Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart; } else { SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE ); } ExReleaseFastMutex( Scb->Header.FastMutex ); // // Now update Cc. // CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); // // ****TEMP**** This hack is awaiting our actually doing this change // in CcSetFileSizes. // *((PLONGLONG)(Scb->NonpagedScb->SegmentObject.SharedCacheMap) + 5) = Length; leave; } } // // Nonresident path // // Now determine where the new file size lines up with the // current file layout. The two cases we need to consider are // where the new file size is less than the current file size and // valid data length, in which case we need to shrink them. // Or we new file size is greater than the current allocation, // in which case we need to extend the allocation to match the // new file size. // if (Length > Scb->Header.AllocationSize.QuadPart) { LONGLONG NewAllocationSize = Length; BOOLEAN AskForMore = TRUE; // // See if this is the Usn Journal to enforce allocation granularity. // // **** Temporary - this support should be generalized with an Scb field // settable by all callers. // if (Scb == Vcb->UsnJournal) { LONGLONG MaxAllocation; // // Limit ourselves to 128 runs. We don't want to commit in the // middle of the allocation. // NewAllocationSize = MAXIMUM_RUNS_AT_ONCE * Vcb->BytesPerCluster; // // Don't use more than 1/4 of the free space on the volume. // MaxAllocation = Int64ShllMod32( Vcb->FreeClusters, Vcb->ClusterShift - 2 ); if (NewAllocationSize > MaxAllocation) { // // Round down to the Max. Don't worry if there is nothing, our code // below will catch this case and the allocation package always rounds // to a compression unit boundary. // NewAllocationSize = MaxAllocation; } // // Don't grow by more than the Usn delta. // if (NewAllocationSize > (LONGLONG) Vcb->UsnJournalInstance.AllocationDelta) { NewAllocationSize = (LONGLONG) Vcb->UsnJournalInstance.AllocationDelta; } NewAllocationSize += (LONGLONG) Scb->Header.AllocationSize.QuadPart; // // Handle possible weird case. // if (NewAllocationSize < Length) { NewAllocationSize = Length; } // // Always pad the allocation to a compression unit boundary. // ASSERT( Scb->CompressionUnit != 0 ); NewAllocationSize += Scb->CompressionUnit - 1; NewAllocationSize &= ~((LONGLONG) (Scb->CompressionUnit - 1)); AskForMore = FALSE; } else if (Scb->Header.PagingIoResource == NULL) { // // If the file is sparse then make sure we allocate a full compression unit. // Otherwise we can end up with a partially allocated chunk in the Usn // Journal. // if (Scb->CompressionUnit != 0) { NewAllocationSize += Scb->CompressionUnit - 1; ((PLARGE_INTEGER) &NewAllocationSize)->LowPart &= ~(Scb->CompressionUnit - 1); } AskForMore = FALSE; } // // Add the allocation. Never ask for extra for logged streams. // NtfsAddAllocation( IrpContext, FileObject, Scb, LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ), LlClustersFromBytes(Scb->Vcb, (NewAllocationSize - Scb->Header.AllocationSize.QuadPart)), AskForMore, NULL ); ExAcquireFastMutex( Scb->Header.FastMutex ); // // Otherwise see if we have to knock these numbers down... // } else { ExAcquireFastMutex( Scb->Header.FastMutex ); if ((Length < Scb->Header.ValidDataLength.QuadPart) && (Scb->Header.ValidDataLength.QuadPart != MAXLONGLONG)) { Scb->Header.ValidDataLength.QuadPart = Length; } if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) && (Length < Scb->ValidDataToDisk)) { Scb->ValidDataToDisk = Length; } } // // Now put the new size in the Scb. // Scb->Header.FileSize.QuadPart = Length; ExReleaseFastMutex( Scb->Header.FastMutex ); // // Call our common routine to modify the file sizes. We are now // done with Length and NewValidDataLength, and we have // PagingIo + main exclusive (so no one can be working on this Scb). // NtfsWriteFileSizes uses the sizes in the Scb, and this is the // one place where in Ntfs where we wish to use a different value // for ValidDataLength. Therefore, we save the current ValidData // and plug it with our desired value and restore on return. // NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, FALSE, TRUE, TRUE ); // // Now update Cc. // NtfsSetCcFileSizes( FileObject, Scb, (PCC_FILE_SIZES)&Scb->Header.AllocationSize ); } finally { if (CleanupAttrContext) { NtfsCleanupAttributeContext( IrpContext, &AttrContext ); } } } NTFSAPI NTSTATUS NtfsHoldIrpForNewLength ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN PIRP Irp, IN LONGLONG Length, IN PDRIVER_CANCEL CancelRoutine, IN PVOID CapturedData OPTIONAL, OUT PVOID *CopyCapturedData OPTIONAL, IN ULONG CapturedDataLength ) /*++ RoutineDescription: This routine may be called to wait until the designated stream exceeds the specified length. Arguments: Scb - Supplies the stream to wait on. Irp - Supplies the address of the Irp to hold Length - Supplies the length to be exceeded. To wait for any file extend, supply the last seen FileSize. To wait for N new bytes wait for last seen FileSize + N. CancelRoutine - Routine to register as the cancel routine. CapturedData - Specified if caller wishes to have auxillary data captured to pool. CopyCapturedData - Address to store copy of the captured data. CapturedDataLength - Length of the auxillary data to capture. Must be 0 if CapturedData not specified. Return value: NTSTATUS - Status of posting this request. STATUS_CANCELLED if the irp has been cancelled before we could register a callback, STATUS_PENDING if the request was posted without problem. Any other error indicates the irp wasn't posted and our caller needs to clean it up. --*/ { PWAIT_FOR_NEW_LENGTH WaitForNewLength; NTSTATUS Status = STATUS_PENDING; PAGED_CODE(); // // Allocate and initialize a wait block. // WaitForNewLength = NtfsAllocatePool( NonPagedPool, QuadAlign(sizeof(WAIT_FOR_NEW_LENGTH)) + CapturedDataLength ); RtlZeroMemory( WaitForNewLength, sizeof(WAIT_FOR_NEW_LENGTH) ); if (ARGUMENT_PRESENT(CapturedData)) { RtlCopyMemory( Add2Ptr(WaitForNewLength, QuadAlign(sizeof(WAIT_FOR_NEW_LENGTH))), CapturedData, CapturedDataLength ); CapturedData = Add2Ptr(WaitForNewLength, QuadAlign(sizeof(WAIT_FOR_NEW_LENGTH))); *CopyCapturedData = CapturedData; } WaitForNewLength->Irp = Irp; WaitForNewLength->Length = Length; WaitForNewLength->Stream = Scb; WaitForNewLength->Status = STATUS_SUCCESS; WaitForNewLength->Flags = NTFS_WAIT_FLAG_ASYNC; // // Prepare the Irp for hanging around. Only make this call once per Irp. We occasionally // wake up a waiting Irp and then find we don't have enough data to return. In that // case we don't want to clean up the 'borrowed' IrpContext and the Irp has already // been prepared. // if (IrpContext->OriginatingIrp == Irp) { NtfsPrePostIrp( IrpContext, Irp ); } // // Synchronize to queue and initialize the wait block. // ExAcquireFastMutex( Scb->Header.FastMutex ); if (NtfsSetCancelRoutine( Irp, CancelRoutine, (ULONG_PTR) WaitForNewLength, TRUE )) { InsertTailList( &Scb->ScbType.Data.WaitForNewLength, &WaitForNewLength->WaitList ); IoMarkIrpPending( Irp ); // // The irp has already been marked for cancel. // } else { Status = STATUS_CANCELLED; NtfsFreePool( WaitForNewLength ); } ExReleaseFastMutex( Scb->Header.FastMutex ); // // Mark the Irp pending and get out. // return Status; } NTFSAPI NTSTATUS NtOfsWaitForNewLength ( IN PSCB Scb, IN LONGLONG Length, IN ULONG Async, IN PIRP Irp, IN PDRIVER_CANCEL CancelRoutine, IN PLARGE_INTEGER Timeout OPTIONAL ) /*++ RoutineDescription: This routine may be called to wait until the designated stream exceeds the specified length. Arguments: Scb - Supplies the stream to wait on. Length - Supplies the length to be exceeded. To wait for any file extend, supply the last seen FileSize. To wait for N new bytes wait for last seen FileSize + N. Async - Indicates if we want to complete this request in another thread in the case of cancel. Irp - Supplies Irp of current request, so that wait can be skipped if Irp has been cancelled. CancelRoutine - This is the cancel routine to store in the Irp. TimeOut - Supplies an standard optional timeout spec, in case the caller wants to set a max time to wait. Return value: NTSTATUS - Status to proceed with the request. It may be STATUS_SUCCESS, STATUS_TIMEOUT or STATUS_CANCELLED. It may also be some other error specific to this type of request. In general the caller may wish to ignore the status code since they own the Irp now and are responsible for completing it. --*/ { PWAIT_FOR_NEW_LENGTH WaitForNewLength; LONGLONG OriginalLength = Scb->Header.FileSize.QuadPart; NTSTATUS Status = STATUS_SUCCESS; PAGED_CODE(); // // Allocate and initialize a wait block. // WaitForNewLength = NtfsAllocatePool( NonPagedPool, sizeof( WAIT_FOR_NEW_LENGTH )); WaitForNewLength->Irp = Irp; WaitForNewLength->Length = Length; WaitForNewLength->Stream = Scb; WaitForNewLength->Status = STATUS_SUCCESS; // // Take different action if this is async or sync. // if (Async) { WaitForNewLength->Flags = NTFS_WAIT_FLAG_ASYNC; } else { WaitForNewLength->Flags = 0; KeInitializeEvent( &WaitForNewLength->Event, NotificationEvent, FALSE ); } // // Test if we need to wait at all. // ExAcquireFastMutex( Scb->Header.FastMutex ); // // Has the length already changed? If not we must wait. // if (Scb->Header.FileSize.QuadPart <= Length) { // // Now set up the cancel routine. Return cancel if the user has // already cancelled this. Otherwise set up to wait. // if (NtfsSetCancelRoutine( Irp, CancelRoutine, (ULONG_PTR) WaitForNewLength, Async )) { InsertTailList( &Scb->ScbType.Data.WaitForNewLength, &WaitForNewLength->WaitList ); ExReleaseFastMutex( Scb->Header.FastMutex ); // // Now wait for someone to signal the length change. // if (!Async) { do { Status = KeWaitForSingleObject( &WaitForNewLength->Event, Executive, (KPROCESSOR_MODE)(ARGUMENT_PRESENT(Irp) ? Irp->RequestorMode : KernelMode), TRUE, Timeout ); // // If the system timed out but there was no change in the file length then // we want to wait for the first change of the file. Wait again but without // a timeout and a length of the current size + 1. This satisfies the timeout // semantics which are don't wait for the full user length request to be satisfied // if it doesn't occur within the timeout period. Return either what has changed // in that time or the first change which occurs if nothing changed within the // timeout period. // if ((Status == STATUS_TIMEOUT) && ARGUMENT_PRESENT( Timeout ) && (Scb->Header.FileSize.QuadPart == OriginalLength)) { Timeout = NULL; WaitForNewLength->Length = OriginalLength + 1; // // Set the status to STATUS_KERNEL_APC so we will retry. // Status = STATUS_KERNEL_APC; continue; } } while (Status == STATUS_KERNEL_APC); // // Make sure to clear the cancel routine. We don't care if // a cancel is underway here. // ExAcquireFastMutex( Scb->Header.FastMutex ); // // Make a timeout look like STATUS_SUCCESS. Otherwise return the error. // if (Status == STATUS_TIMEOUT) { Status = STATUS_SUCCESS; // // Clear the cancel routine. // NtfsClearCancelRoutine( WaitForNewLength->Irp ); } else { // // If the wait completed with success then check for the error // in the wait block. // if (Status == STATUS_SUCCESS) { Status = WaitForNewLength->Status; // // Clear the cancel routine. // } else { NtfsClearCancelRoutine( WaitForNewLength->Irp ); } } RemoveEntryList( &WaitForNewLength->WaitList ); ExReleaseFastMutex( Scb->Header.FastMutex ); NtfsFreePool( WaitForNewLength ); // // The current thread is finished with the Irp. // } else { Status = STATUS_PENDING; } // // The irp has already been marked for cancel. // } else { ExReleaseFastMutex( Scb->Header.FastMutex ); NtfsFreePool( WaitForNewLength ); Status = STATUS_CANCELLED; } } else { ExReleaseFastMutex( Scb->Header.FastMutex ); NtfsFreePool( WaitForNewLength ); } return Status; } VOID NtOfsPostNewLength ( IN PIRP_CONTEXT IrpContext OPTIONAL, IN PSCB Scb, IN BOOLEAN WakeAll ) /*++ RoutineDescription: This routine may be called to wake one or more waiters based on the desired FileSize change, or to unconditionally wake all waiters (such as for a shutdown condition). NOTE: The caller must have the FsRtl header mutex acquired when calling this routine. Arguments: Scb - Supplies the stream to act on. WakeAll - Supplies TRUE if all waiters should be unconditionally woken. Return value: None. --*/ { PWAIT_FOR_NEW_LENGTH WaitForNewLength, WaiterToWake; ASSERT(FIELD_OFFSET(WAIT_FOR_NEW_LENGTH, WaitList) == 0); PAGED_CODE(); ExAcquireFastMutex( Scb->Header.FastMutex ); WaitForNewLength = (PWAIT_FOR_NEW_LENGTH)Scb->ScbType.Data.WaitForNewLength.Flink; while (WaitForNewLength != (PWAIT_FOR_NEW_LENGTH)&Scb->ScbType.Data.WaitForNewLength) { // // If we are supposed to wake this guy, then move our pointer to the next guy // first, then wake him, setting his event after removing him from the list, // since setting the event will cause him to eventually reuse the stack space // containing the wait block. // if ((Scb->Header.FileSize.QuadPart > WaitForNewLength->Length) || WakeAll) { WaiterToWake = WaitForNewLength; WaitForNewLength = (PWAIT_FOR_NEW_LENGTH)WaitForNewLength->WaitList.Flink; // // If this is for an asynchronous Irp, then remove him from the list and // drop the mutex to do further processing. We only do further processing // if there is not currently a cancel thread active for this Irp. // // NOTE: This code currently relies on the fact that there is just one // caller to the routine to hold an Irp. If more such caller's // surface, then the routine address would have to be stored in // the wait context. // // If cancel is active then we will skip over this Irp. // if (NtfsClearCancelRoutine( WaiterToWake->Irp )) { if (FlagOn( WaiterToWake->Flags, NTFS_WAIT_FLAG_ASYNC )) { // // Make sure we decrement the reference count in the Scb. // InterlockedDecrement( &Scb->CloseCount ); RemoveEntryList( &WaiterToWake->WaitList ); ExReleaseFastMutex( Scb->Header.FastMutex ); // // Nothing really should go wrong, unless we get an I/O error, // none the less, we want to stop any exceptions and complete // the request ourselves rather than impact our caller. // if (ARGUMENT_PRESENT( IrpContext )) { try { NtfsReadUsnJournal( IrpContext, WaiterToWake->Irp, FALSE ); } except(NtfsExceptionFilter( NULL, GetExceptionInformation())) { NtfsCompleteRequest( NULL, WaiterToWake->Irp, GetExceptionCode() ); } // // Assume the only caller with no IrpContext is cancelling the request. // } else { NtfsCompleteRequest( NULL, WaiterToWake->Irp, STATUS_CANCELLED ); } // // Free the wait block and go back to the beginning of the list. // Is it possible that we can into a continuous loop here? We may // need a strategy to recognize which entries we have visited // in this loop. // NtfsFreePool( WaiterToWake ); ExAcquireFastMutex( Scb->Header.FastMutex ); WaitForNewLength = (PWAIT_FOR_NEW_LENGTH)Scb->ScbType.Data.WaitForNewLength.Flink; } else { KeSetEvent( &WaiterToWake->Event, 0, FALSE ); } } } else { WaitForNewLength = (PWAIT_FOR_NEW_LENGTH)WaitForNewLength->WaitList.Flink; } } ExReleaseFastMutex( Scb->Header.FastMutex ); } NTFSAPI VOID NtOfsFlushAttribute ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN ULONG Purge ) /*++ Routine Description: This routine flushes the specified attribute, and optionally purges it from the cache. Arguments: Scb - Supplies an Scb as the previously returned handle for this attribute. Purge - Supplies TRUE if the attribute is to be purged. Return Value: None (Deleting a nonexistant index is benign). --*/ { PAGED_CODE(); if (Purge) { NtfsFlushAndPurgeScb( IrpContext, Scb, NULL ); } else { NtfsFlushUserStream( IrpContext, Scb, NULL, 0 ); } } NTFSAPI VOID NtOfsPutData ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, IN PVOID Data OPTIONAL ) /*++ Routine Description: This routine is called to update a range of a recoverable stream. Note this update cannot extend the filesize unless its a write to eof put (Offset = -1) Arguments: Scb - Scb for the stream to zero. Offset - Offset in stream to update. Length - Length of stream to update in bytes. Data - Data to update stream with if specified, else range should be zeroed. Return Value: None. --*/ { ULONG OriginalLength = Length; BOOLEAN WriteToEof = FALSE; BOOLEAN MovingBackwards = TRUE; PAGED_CODE(); ASSERT( FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE ) ); // // Handle Put to end of file. // if (Offset < 0) { WriteToEof = TRUE; Offset = Scb->Header.FileSize.QuadPart; NtOfsSetLength( IrpContext, Scb, Offset + Length ); } ASSERT((Offset + Length) <= Scb->Header.FileSize.QuadPart); // // First handle the resident case. // if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) { ATTRIBUTE_ENUMERATION_CONTEXT Context; PFILE_RECORD_SEGMENT_HEADER FileRecord; PATTRIBUTE_RECORD_HEADER Attribute; ULONG RecordOffset, AttributeOffset; PVCB Vcb = Scb->Vcb; NtfsInitializeAttributeContext( &Context ); try { // // Lookup and pin the attribute. // NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context ); NtfsPinMappedAttribute( IrpContext, Vcb, &Context ); // // Extract the relevant pointers and calculate offsets. // FileRecord = NtfsContainingFileRecord(&Context); Attribute = NtfsFoundAttribute(&Context); RecordOffset = PtrOffset(FileRecord, Attribute); AttributeOffset = Attribute->Form.Resident.ValueOffset + (ULONG)Offset; // // Log the change while we still have the old data. // FileRecord->Lsn = NtfsWriteLog( IrpContext, Vcb->MftScb, NtfsFoundBcb(&Context), UpdateResidentValue, Data, Length, UpdateResidentValue, Add2Ptr(Attribute, Attribute->Form.Resident.ValueOffset + (ULONG)Offset), Length, NtfsMftOffset(&Context), RecordOffset, AttributeOffset, Vcb->BytesPerFileRecordSegment ); // // Now update this data by calling the same routine as restart. // NtfsRestartChangeValue( IrpContext, FileRecord, RecordOffset, AttributeOffset, Data, Length, FALSE ); // // If there is a stream for this attribute, then we must update it in the // cache, copying from the attribute itself in order to handle the zeroing // (Data == NULL) case. // if (Scb->FileObject != NULL) { CcCopyWrite( Scb->FileObject, (PLARGE_INTEGER)&Offset, Length, TRUE, Add2Ptr(Attribute, AttributeOffset) ); } // // Optionally update ValidDataLength // Offset += Length; if (Offset > Scb->Header.ValidDataLength.QuadPart) { Scb->Header.ValidDataLength.QuadPart = Offset; } } finally { NtfsCleanupAttributeContext( IrpContext, &Context ); } // // Now handle the nonresident case. // } else { PVOID Buffer; PVOID SubData = NULL; LONGLONG NewValidDataLength = Offset + Length; PBCB Bcb = NULL; ULONG PageOffset = (ULONG)Offset & (PAGE_SIZE - 1); ULONGLONG SubOffset; ULONG SubLength; ASSERT(Scb->FileObject != NULL); // // If we are starting beyond ValidDataLength, then recurse to // zero what we need. // if (Offset > Scb->Header.FileSize.QuadPart) { ASSERT((Offset - Scb->Header.FileSize.QuadPart) <= MAXULONG); NtOfsPutData( IrpContext, Scb, Scb->Header.FileSize.QuadPart, (ULONG)(Offset - Scb->Header.FileSize.QuadPart), NULL ); } try { // // Now loop until there are no more pages with new data // to log. We'll start assuming a backwards copy // while (Length != 0) { if (MovingBackwards) { // // Calculate the last page of the transfer - if its the 1st page start at offset // SubOffset = max( Offset, BlockAlignTruncate( Offset + Length - 1, PAGE_SIZE ) ); SubLength = (ULONG)(Offset + Length - SubOffset); // // This guarantees we can truncate to a 32 bit value // ASSERT( Offset + Length - SubOffset <= PAGE_SIZE ); } else { SubOffset = Offset + OriginalLength - Length; SubLength = min( PAGE_SIZE - ((ULONG)SubOffset & (PAGE_SIZE - 1)), Length ); } if (Data != NULL) { SubData = Add2Ptr( Data, SubOffset - Offset ); } #ifdef BENL_DBG if(BlockAlignTruncate( Offset + Length - 1, PAGE_SIZE ) != BlockAlignTruncate( Offset, PAGE_SIZE )) { // KdPrint(( "NTFS: pin %I64x %x from %I64x %x\n", SubOffset, SubLength, Offset, Length )); } #endif // // Pin the page // NtfsPinStream( IrpContext, Scb, SubOffset, SubLength, &Bcb, &Buffer ); // // Doublecheck the direction of copy based on the relative position of the // source (data) and destination (buffer). We don't care if the source is null // We'll only switch once from backwards to forwards // if (MovingBackwards && ((PCHAR)Buffer < (PCHAR)SubData) && (Data != NULL)) { // // Start over with the opposite direction // MovingBackwards = FALSE; NtfsUnpinBcb( IrpContext, &Bcb ); continue; } // // Now log the changes to this page. // (VOID) NtfsWriteLog( IrpContext, Scb, Bcb, UpdateNonresidentValue, SubData, SubLength, WriteToEof ? Noop : UpdateNonresidentValue, WriteToEof ? NULL : Buffer, WriteToEof ? 0 : SubLength, BlockAlignTruncate( SubOffset, PAGE_SIZE ), (ULONG)(SubOffset & (PAGE_SIZE - 1)), 0, (ULONG)(SubOffset & (PAGE_SIZE - 1)) + SubLength ); // // Move the data into place. // if (Data != NULL) { RtlMoveMemory( Buffer, SubData, SubLength ); } else { RtlZeroMemory( Buffer, SubLength ); } // // Unpin the page and decrement the length // NtfsUnpinBcb( IrpContext, &Bcb ); Length -= SubLength; } // // Optionally update ValidDataLength // if (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) { Scb->Header.ValidDataLength.QuadPart = NewValidDataLength; NtfsWriteFileSizes( IrpContext, Scb, &NewValidDataLength, TRUE, TRUE, TRUE ); // // See if we have to wake anyone. // if (!IsListEmpty(&Scb->ScbType.Data.WaitForNewLength)) { NtfsPostToNewLengthQueue( IrpContext, Scb ); } } } finally { NtfsUnpinBcb( IrpContext, &Bcb ); } } } // // The following prototypes are here only for someone external to Ntfs (such as EFS) // trying to link to Ntfs using ntfsexp.h. // NTFSAPI VOID NtOfsMapAttribute ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PVOID *Buffer, OUT PMAP_HANDLE MapHandle ) /*++ Routine Description: NtOfsMapAttribute maps the given region of an Scb. Its a thin wrapper around CcMapData. Arguments: IrpContext - Supplies the irpcontext associated with the current operation Scb - Scb to map data from Offset - offset into data Length - length of region to be pinned Buffer - returned buffer with pinned data virtual address MapHandle - returned map handle used to manage the pinned region. Return Value: None --*/ { PAGED_CODE( ); UNREFERENCED_PARAMETER( IrpContext ); CcMapData( Scb->FileObject, (PLARGE_INTEGER)&Offset, Length, TRUE, &MapHandle->Bcb, Buffer ); #ifdef MAPCOUNT_DBG IrpContext->MapCount++; #endif MapHandle->FileOffset = Offset; MapHandle->Length = Length; MapHandle->Buffer = *(PVOID *)Buffer; } NTFSAPI VOID NtOfsPreparePinWrite ( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PVOID *Buffer, OUT PMAP_HANDLE MapHandle ) /*++ Routine Description: NtOfsPreparePinWrite maps and pins a portion of the specified attribute and returns a pointer to the memory. This is equivalent to doing a NtOfsMapAttribute followed by NtOfsPinRead and NtOfsDirty but is more efficient. Arguments: IrpContext - Supplies the irpcontext associated with the current operation Scb - Scb to pin in preparation for a write Offset - offset into data Length - length of region to be pinned Buffer - returned buffer with pinned data virtual address MapHandle - returned map handle used to manage the pinned region. Return Value: None --*/ { UNREFERENCED_PARAMETER( IrpContext ); if ((Offset + Length) > Scb->Header.AllocationSize.QuadPart) { ExRaiseStatus(STATUS_END_OF_FILE); } CcPreparePinWrite( Scb->FileObject, (PLARGE_INTEGER)&Offset, Length, FALSE, TRUE, &MapHandle->Bcb, Buffer ); #ifdef MAPCOUNT_DBG IrpContext->MapCount++; #endif MapHandle->FileOffset = Offset; MapHandle->Length = Length; MapHandle->Buffer = Buffer; } NTFSAPI VOID NtOfsPinRead( IN PIRP_CONTEXT IrpContext, IN PSCB Scb, IN LONGLONG Offset, IN ULONG Length, OUT PMAP_HANDLE MapHandle ) /*++ Routine Description: NtOfsPinRead pins a section of a map and read in all pages from the mapped attribute. Offset and Length must describe a byte range which is equal to or included by the original mapped range. Arguments: IrpContext - Supplies the irpcontext associated with the current operation Scb - Scb to pin data for reads in Offset - offset into data Length - length of region to be pinned MapHandle - returned map handle used to manage the pinned region. Return Value: None --*/ { UNREFERENCED_PARAMETER( IrpContext ); ASSERT( MapHandle->Bcb != NULL ); ASSERT( (Offset >= MapHandle->FileOffset) && ((Offset + Length) <= (MapHandle->FileOffset + MapHandle->Length)) ); CcPinMappedData( Scb->FileObject, (PLARGE_INTEGER)&Offset, Length, TRUE, &MapHandle->Bcb ); MapHandle->FileOffset = Offset; MapHandle->Length = Length; } NTFSAPI VOID NtOfsReleaseMap ( IN PIRP_CONTEXT IrpContext, IN PMAP_HANDLE MapHandle ) /*++ Routine Description: This routine unmaps/unpins a mapped portion of an attribute. Arguments: IrpContext - Supplies the irpcontext associated with the current operation MapHandle - Supplies map handle containing the bcb to be released. Return Value: None --*/ { UNREFERENCED_PARAMETER( IrpContext ); if (MapHandle->Bcb != NULL) { CcUnpinData( MapHandle->Bcb ); #ifdef MAPCOUNT_DBG IrpContext->MapCount--; #endif MapHandle->Bcb = NULL; } }