reactos/drivers/filesystems/udfs/write.cpp
2019-04-02 10:53:23 +02:00

1228 lines
50 KiB
C++

////////////////////////////////////////////////////////////////////
// Copyright (C) Alexander Telyatnikov, Ivan Keliukh, Yegor Anchishkin, SKIF Software, 1999-2013. Kiev, Ukraine
// All rights reserved
// This file was released under the GPLv2 on June 2015.
////////////////////////////////////////////////////////////////////
/*************************************************************************
*
* File: Write.cpp
*
* Module: UDF File System Driver (Kernel mode execution only)
*
* Description:
* Contains code to handle the "Write" dispatch entry point.
*
*************************************************************************/
#include "udffs.h"
// define the file specific bug-check id
#define UDF_BUG_CHECK_ID UDF_FILE_WRITE
#ifndef UDF_READ_ONLY_BUILD
/*************************************************************************
*
* Function: UDFWrite()
*
* Description:
* The I/O Manager will invoke this routine to handle a write
* request
*
* Expected Interrupt Level (for execution) :
*
* IRQL_PASSIVE_LEVEL (invocation at higher IRQL will cause execution
* to be deferred to a worker thread context)
*
* Return Value: STATUS_SUCCESS/Error
*
*************************************************************************/
NTSTATUS
NTAPI
UDFWrite(
PDEVICE_OBJECT DeviceObject, // the logical volume device object
PIRP Irp // I/O Request Packet
)
{
NTSTATUS RC = STATUS_SUCCESS;
PtrUDFIrpContext PtrIrpContext = NULL;
BOOLEAN AreWeTopLevel = FALSE;
TmPrint(("UDFWrite: , thrd:%8.8x\n",PsGetCurrentThread()));
FsRtlEnterFileSystem();
ASSERT(DeviceObject);
ASSERT(Irp);
// set the top level context
AreWeTopLevel = UDFIsIrpTopLevel(Irp);
ASSERT(!UDFIsFSDevObj(DeviceObject));
_SEH2_TRY {
// get an IRP context structure and issue the request
PtrIrpContext = UDFAllocateIrpContext(Irp, DeviceObject);
if(PtrIrpContext) {
RC = UDFCommonWrite(PtrIrpContext, Irp);
} else {
RC = STATUS_INSUFFICIENT_RESOURCES;
Irp->IoStatus.Status = RC;
Irp->IoStatus.Information = 0;
// complete the IRP
IoCompleteRequest(Irp, IO_DISK_INCREMENT);
}
} _SEH2_EXCEPT (UDFExceptionFilter(PtrIrpContext, _SEH2_GetExceptionInformation())) {
RC = UDFExceptionHandler(PtrIrpContext, Irp);
UDFLogEvent(UDF_ERROR_INTERNAL_ERROR, RC);
} _SEH2_END;
if (AreWeTopLevel) {
IoSetTopLevelIrp(NULL);
}
FsRtlExitFileSystem();
return(RC);
} // end UDFWrite()
/*************************************************************************
*
* Function: UDFCommonWrite()
*
* Description:
* The actual work is performed here. This routine may be invoked in one'
* of the two possible contexts:
* (a) in the context of a system worker thread
* (b) in the context of the original caller
*
* Expected Interrupt Level (for execution) :
*
* IRQL_PASSIVE_LEVEL
*
* Return Value: STATUS_SUCCESS/Error
*
*************************************************************************/
NTSTATUS
UDFCommonWrite(
PtrUDFIrpContext PtrIrpContext,
PIRP Irp)
{
NTSTATUS RC = STATUS_SUCCESS;
PIO_STACK_LOCATION IrpSp = NULL;
LARGE_INTEGER ByteOffset;
ULONG WriteLength = 0, TruncatedLength = 0;
SIZE_T NumberBytesWritten = 0;
PFILE_OBJECT FileObject = NULL;
PtrUDFFCB Fcb = NULL;
PtrUDFCCB Ccb = NULL;
PVCB Vcb = NULL;
PtrUDFNTRequiredFCB NtReqFcb = NULL;
PERESOURCE PtrResourceAcquired = NULL;
PERESOURCE PtrResourceAcquired2 = NULL;
PVOID SystemBuffer = NULL;
// PVOID TmpBuffer = NULL;
// uint32 KeyValue = 0;
PIRP TopIrp;
LONGLONG ASize;
LONGLONG OldVDL;
ULONG Res1Acq = 0;
ULONG Res2Acq = 0;
BOOLEAN CacheLocked = FALSE;
BOOLEAN CanWait = FALSE;
BOOLEAN PagingIo = FALSE;
BOOLEAN NonBufferedIo = FALSE;
BOOLEAN SynchronousIo = FALSE;
BOOLEAN IsThisADeferredWrite = FALSE;
BOOLEAN WriteToEOF = FALSE;
BOOLEAN Resized = FALSE;
BOOLEAN RecursiveWriteThrough = FALSE;
BOOLEAN WriteFileSizeToDirNdx = FALSE;
BOOLEAN ZeroBlock = FALSE;
BOOLEAN VcbAcquired = FALSE;
BOOLEAN ZeroBlockDone = FALSE;
TmPrint(("UDFCommonWrite: irp %x\n", Irp));
_SEH2_TRY {
TopIrp = IoGetTopLevelIrp();
switch((ULONG_PTR)TopIrp) {
case FSRTL_FSP_TOP_LEVEL_IRP:
UDFPrint((" FSRTL_FSP_TOP_LEVEL_IRP\n"));
break;
case FSRTL_CACHE_TOP_LEVEL_IRP:
UDFPrint((" FSRTL_CACHE_TOP_LEVEL_IRP\n"));
break;
case FSRTL_MOD_WRITE_TOP_LEVEL_IRP:
UDFPrint((" FSRTL_MOD_WRITE_TOP_LEVEL_IRP\n"));
break;
case FSRTL_FAST_IO_TOP_LEVEL_IRP:
UDFPrint((" FSRTL_FAST_IO_TOP_LEVEL_IRP\n"));
BrutePoint();
break;
case NULL:
UDFPrint((" NULL TOP_LEVEL_IRP\n"));
break;
default:
if(TopIrp == Irp) {
UDFPrint((" TOP_LEVEL_IRP\n"));
} else {
UDFPrint((" RECURSIVE_IRP, TOP = %x\n", TopIrp));
}
break;
}
// First, get a pointer to the current I/O stack location
IrpSp = IoGetCurrentIrpStackLocation(Irp);
ASSERT(IrpSp);
MmPrint((" Enter Irp, MDL=%x\n", Irp->MdlAddress));
if(Irp->MdlAddress) {
UDFTouch(Irp->MdlAddress);
}
FileObject = IrpSp->FileObject;
ASSERT(FileObject);
// If this happens to be a MDL write complete request, then
// allocated MDL can be freed. This may cause a recursive write
// back into the FSD.
if (IrpSp->MinorFunction & IRP_MN_COMPLETE) {
// Caller wants to tell the Cache Manager that a previously
// allocated MDL can be freed.
UDFMdlComplete(PtrIrpContext, Irp, IrpSp, FALSE);
// The IRP has been completed.
try_return(RC = STATUS_SUCCESS);
}
// If this is a request at IRQL DISPATCH_LEVEL, then post the request
if (IrpSp->MinorFunction & IRP_MN_DPC) {
try_return(RC = STATUS_PENDING);
}
// Get the FCB and CCB pointers
Ccb = (PtrUDFCCB)(FileObject->FsContext2);
ASSERT(Ccb);
Fcb = Ccb->Fcb;
ASSERT(Fcb);
Vcb = Fcb->Vcb;
if(Fcb->FCBFlags & UDF_FCB_DELETED) {
ASSERT(FALSE);
try_return(RC = STATUS_TOO_LATE);
}
// is this operation allowed ?
if(Vcb->VCBFlags & UDF_VCB_FLAGS_MEDIA_READ_ONLY) {
try_return(RC = STATUS_ACCESS_DENIED);
}
Vcb->VCBFlags |= UDF_VCB_SKIP_EJECT_CHECK;
// Disk based file systems might decide to verify the logical volume
// (if required and only if removable media are supported) at this time
// As soon as Tray is locked, we needn't call UDFVerifyVcb()
ByteOffset = IrpSp->Parameters.Write.ByteOffset;
CanWait = (PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE;
PagingIo = (Irp->Flags & IRP_PAGING_IO) ? TRUE : FALSE;
NonBufferedIo = (Irp->Flags & IRP_NOCACHE) ? TRUE : FALSE;
SynchronousIo = (FileObject->Flags & FO_SYNCHRONOUS_IO) ? TRUE : FALSE;
UDFPrint((" Flags: %s; %s; %s; %s; Irp(W): %8.8x\n",
CanWait ? "Wt" : "nw", PagingIo ? "Pg" : "np",
NonBufferedIo ? "NBuf" : "buff", SynchronousIo ? "Snc" : "Asc",
Irp->Flags));
NtReqFcb = Fcb->NTRequiredFCB;
Res1Acq = UDFIsResourceAcquired(&(NtReqFcb->MainResource));
if(!Res1Acq) {
Res1Acq = PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_RES1_ACQ;
}
Res2Acq = UDFIsResourceAcquired(&(NtReqFcb->PagingIoResource));
if(!Res2Acq) {
Res2Acq = PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_RES2_ACQ;
}
if(!NonBufferedIo &&
(Fcb->NodeIdentifier.NodeType != UDF_NODE_TYPE_VCB)) {
if((Fcb->NodeIdentifier.NodeType != UDF_NODE_TYPE_VCB) &&
UDFIsAStream(Fcb->FileInfo)) {
UDFNotifyFullReportChange( Vcb, Fcb->FileInfo,
FILE_NOTIFY_CHANGE_STREAM_WRITE,
FILE_ACTION_MODIFIED_STREAM);
} else {
UDFNotifyFullReportChange( Vcb, Fcb->FileInfo,
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS,
FILE_ACTION_MODIFIED);
}
}
// Get some of the parameters supplied to us
WriteLength = IrpSp->Parameters.Write.Length;
if (WriteLength == 0) {
// a 0 byte write can be immediately succeeded
if (SynchronousIo && !PagingIo && NT_SUCCESS(RC)) {
// NT expects changing CurrentByteOffset to zero in this case
FileObject->CurrentByteOffset.QuadPart = 0;
}
try_return(RC);
}
// If this is the normal file we have to check for
// write access according to the current state of the file locks.
if (!PagingIo &&
!FsRtlCheckLockForWriteAccess( &(NtReqFcb->FileLock), Irp) ) {
try_return( RC = STATUS_FILE_LOCK_CONFLICT );
}
// **********
// Is this a write of the volume itself ?
// **********
if (Fcb->NodeIdentifier.NodeType == UDF_NODE_TYPE_VCB) {
// Yup, we need to send this on to the disk driver after
// validation of the offset and length.
Vcb = (PVCB)(Fcb);
if(!CanWait)
try_return(RC = STATUS_PENDING);
// I dislike the idea of writing to not locked media
if(!(Vcb->VCBFlags & UDF_VCB_FLAGS_VOLUME_LOCKED)) {
try_return(RC = STATUS_ACCESS_DENIED);
}
if(PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_FLUSH2_REQUIRED) {
UDFPrint((" UDF_IRP_CONTEXT_FLUSH2_REQUIRED\n"));
PtrIrpContext->IrpContextFlags &= ~UDF_IRP_CONTEXT_FLUSH2_REQUIRED;
if(!(Vcb->VCBFlags & UDF_VCB_FLAGS_RAW_DISK)) {
UDFCloseAllSystemDelayedInDir(Vcb, Vcb->RootDirFCB->FileInfo);
}
#ifdef UDF_DELAYED_CLOSE
UDFCloseAllDelayed(Vcb);
#endif //UDF_DELAYED_CLOSE
}
// Acquire the volume resource exclusive
UDFAcquireResourceExclusive(&(Vcb->VCBResource), TRUE);
PtrResourceAcquired = &(Vcb->VCBResource);
// I dislike the idea of writing to mounted media too, but M$ has another point of view...
if(Vcb->VCBFlags & UDF_VCB_FLAGS_VOLUME_MOUNTED) {
// flush system cache
UDFFlushLogicalVolume(NULL, NULL, Vcb, 0);
}
#if defined(_MSC_VER) && !defined(__clang__)
/* FIXME */
if(PagingIo) {
CollectStatistics(Vcb, MetaDataWrites);
CollectStatisticsEx(Vcb, MetaDataWriteBytes, NumberBytesWritten);
}
#endif
// Forward the request to the lower level driver
// Lock the callers buffer
if (!NT_SUCCESS(RC = UDFLockCallersBuffer(PtrIrpContext, Irp, TRUE, WriteLength))) {
try_return(RC);
}
SystemBuffer = UDFGetCallersBuffer(PtrIrpContext, Irp);
if(!SystemBuffer)
try_return(RC = STATUS_INVALID_USER_BUFFER);
// Indicate, that volume contents can change after this operation
// This flag will force VerifyVolume in future
UDFPrint((" set UnsafeIoctl\n"));
Vcb->VCBFlags |= UDF_VCB_FLAGS_UNSAFE_IOCTL;
// Make sure, that volume will never be quick-remounted
// It is very important for ChkUdf utility.
Vcb->SerialNumber--;
// Perform actual Write
RC = UDFTWrite(Vcb, SystemBuffer, WriteLength,
(ULONG)(ByteOffset.QuadPart >> Vcb->BlockSizeBits),
&NumberBytesWritten);
UDFUnlockCallersBuffer(PtrIrpContext, Irp, SystemBuffer);
try_return(RC);
}
if(Vcb->VCBFlags & UDF_VCB_FLAGS_VOLUME_READ_ONLY) {
try_return(RC = STATUS_ACCESS_DENIED);
}
// back pressure for very smart and fast system cache ;)
if(!NonBufferedIo) {
// cached IO
if(Vcb->VerifyCtx.QueuedCount ||
Vcb->VerifyCtx.ItemCount >= UDF_MAX_VERIFY_CACHE) {
UDFVVerify(Vcb, UFD_VERIFY_FLAG_WAIT);
}
} else {
if(Vcb->VerifyCtx.ItemCount > UDF_SYS_CACHE_STOP_THR) {
UDFVVerify(Vcb, UFD_VERIFY_FLAG_WAIT);
}
}
// The FSD (if it is a "nice" FSD) should check whether it is
// convenient to allow the write to proceed by utilizing the
// CcCanIWrite() function call. If it is not convenient to perform
// the write at this time, we should defer the request for a while.
// The check should not however be performed for non-cached write
// operations. To determine whether we are retrying the operation
// or now, use Flags in the IrpContext structure we have created
IsThisADeferredWrite = (PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_DEFERRED_WRITE) ? TRUE : FALSE;
if (!NonBufferedIo) {
MmPrint((" CcCanIWrite()\n"));
if (!CcCanIWrite(FileObject, WriteLength, CanWait, IsThisADeferredWrite)) {
// Cache Manager and/or the VMM does not want us to perform
// the write at this time. Post the request.
PtrIrpContext->IrpContextFlags |= UDF_IRP_CONTEXT_DEFERRED_WRITE;
UDFPrint(("UDFCommonWrite: Defer write\n"));
MmPrint((" CcDeferWrite()\n"));
CcDeferWrite(FileObject, UDFDeferredWriteCallBack, PtrIrpContext, Irp, WriteLength, IsThisADeferredWrite);
try_return(RC = STATUS_PENDING);
}
}
// If the write request is directed to a page file,
// send the request directly to the disk
if (Fcb->FCBFlags & UDF_FCB_PAGE_FILE) {
NonBufferedIo = TRUE;
}
// We can continue. Check whether this write operation is targeted
// to a directory object in which case the UDF FSD will disallow
// the write request.
if (Fcb->FCBFlags & UDF_FCB_DIRECTORY) {
RC = STATUS_INVALID_DEVICE_REQUEST;
try_return(RC);
}
// Validate start offset and length supplied.
// Here is a special check that determines whether the caller wishes to
// begin the write at current end-of-file (whatever the value of that
// offset might be)
if(ByteOffset.HighPart == (LONG)0xFFFFFFFF) {
if(ByteOffset.LowPart == FILE_WRITE_TO_END_OF_FILE) {
WriteToEOF = TRUE;
ByteOffset = NtReqFcb->CommonFCBHeader.FileSize;
} else
if(ByteOffset.LowPart == FILE_USE_FILE_POINTER_POSITION) {
ByteOffset = FileObject->CurrentByteOffset;
}
}
// Check if this volume has already been shut down. If it has, fail
// this write request.
if (Vcb->VCBFlags & UDF_VCB_FLAGS_SHUTDOWN) {
try_return(RC = STATUS_TOO_LATE);
}
// Paging I/O write operations are special. If paging i/o write
// requests begin beyond end-of-file, the request should be no-oped
// If paging i/o
// requests extend beyond current end of file, they should be truncated
// to current end-of-file.
if(PagingIo && (WriteToEOF || ((ByteOffset.QuadPart + WriteLength) > NtReqFcb->CommonFCBHeader.FileSize.QuadPart))) {
if (ByteOffset.QuadPart > NtReqFcb->CommonFCBHeader.FileSize.QuadPart) {
TruncatedLength = 0;
} else {
TruncatedLength = (ULONG)(NtReqFcb->CommonFCBHeader.FileSize.QuadPart - ByteOffset.QuadPart);
}
if(!TruncatedLength) try_return(RC = STATUS_SUCCESS);
} else {
TruncatedLength = WriteLength;
}
#if defined(_MSC_VER) && !defined(__clang__)
/* FIXME */
if(PagingIo) {
CollectStatistics(Vcb, UserFileWrites);
CollectStatisticsEx(Vcb, UserFileWriteBytes, NumberBytesWritten);
}
#endif
// There are certain complications that arise when the same file stream
// has been opened for cached and non-cached access. The FSD is then
// responsible for maintaining a consistent view of the data seen by
// the caller.
// If this happens to be a non-buffered I/O, we should __try to flush the
// cached data (if some other file object has already initiated caching
// on the file stream). We should also __try to purge the cached
// information though the purge will probably fail if the file has been
// mapped into some process' virtual address space
// WARNING !!! we should not flush data beyond valid data length
if ( NonBufferedIo &&
!PagingIo &&
NtReqFcb->SectionObject.DataSectionObject &&
TruncatedLength &&
(ByteOffset.QuadPart < NtReqFcb->CommonFCBHeader.FileSize.QuadPart)) {
if(!Res1Acq) {
// Try to acquire the FCB MainResource exclusively
if(!UDFAcquireResourceExclusive(&(NtReqFcb->MainResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
PtrResourceAcquired = &(NtReqFcb->MainResource);
}
if(!Res2Acq) {
// We hold PagingIo shared around the flush to fix a
// cache coherency problem.
UDFAcquireSharedStarveExclusive(&(NtReqFcb->PagingIoResource), TRUE );
PtrResourceAcquired2 = &(NtReqFcb->PagingIoResource);
}
// Flush and then attempt to purge the cache
if((ByteOffset.QuadPart + TruncatedLength) > NtReqFcb->CommonFCBHeader.FileSize.QuadPart) {
NumberBytesWritten = TruncatedLength;
} else {
NumberBytesWritten = (ULONG)(NtReqFcb->CommonFCBHeader.FileSize.QuadPart - ByteOffset.QuadPart);
}
MmPrint((" CcFlushCache()\n"));
CcFlushCache(&(NtReqFcb->SectionObject), &ByteOffset, NumberBytesWritten, &(Irp->IoStatus));
if(PtrResourceAcquired2) {
UDFReleaseResource(&(NtReqFcb->PagingIoResource));
PtrResourceAcquired2 = NULL;
}
// If the flush failed, return error to the caller
if (!NT_SUCCESS(RC = Irp->IoStatus.Status)) {
NumberBytesWritten = 0;
try_return(RC);
}
if(!Res2Acq) {
// Acquiring and immediately dropping the resource serializes
// us behind any other writes taking place (either from the
// lazy writer or modified page writer).
UDFAcquireResourceExclusive(&(NtReqFcb->PagingIoResource), TRUE );
UDFReleaseResource(&(NtReqFcb->PagingIoResource));
}
// Attempt the purge and ignore the return code
MmPrint((" CcPurgeCacheSection()\n"));
CcPurgeCacheSection(&(NtReqFcb->SectionObject), &ByteOffset,
NumberBytesWritten, FALSE);
NumberBytesWritten = 0;
// We are finished with our flushing and purging
if(PtrResourceAcquired) {
UDFReleaseResource(PtrResourceAcquired);
PtrResourceAcquired = NULL;
}
}
// Determine if we were called by the lazywriter.
// We reuse 'IsThisADeferredWrite' here to decrease stack usage
IsThisADeferredWrite = (NtReqFcb->LazyWriterThreadID == HandleToUlong(PsGetCurrentThreadId()));
// Acquire the appropriate FCB resource
if(PagingIo) {
// PagingIoResource is already acquired exclusive
// on LazyWrite condition (see UDFAcqLazyWrite())
ASSERT(NonBufferedIo);
if(!IsThisADeferredWrite) {
if(!Res2Acq) {
// Try to acquire the FCB PagingIoResource exclusive
if(!UDFAcquireResourceExclusive(&(NtReqFcb->PagingIoResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
// Remember the resource that was acquired
PtrResourceAcquired2 = &(NtReqFcb->PagingIoResource);
}
}
} else {
// Try to acquire the FCB MainResource shared
if(NonBufferedIo) {
if(!Res2Acq) {
if(!UDFAcquireResourceExclusive(&(NtReqFcb->PagingIoResource), CanWait)) {
//if(!UDFAcquireSharedWaitForExclusive(&(NtReqFcb->PagingIoResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
PtrResourceAcquired2 = &(NtReqFcb->PagingIoResource);
}
} else {
if(!Res1Acq) {
UDF_CHECK_PAGING_IO_RESOURCE(NtReqFcb);
if(!UDFAcquireResourceExclusive(&(NtReqFcb->MainResource), CanWait)) {
//if(!UDFAcquireResourceShared(&(NtReqFcb->MainResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
PtrResourceAcquired = &(NtReqFcb->MainResource);
}
}
// Remember the resource that was acquired
}
// Set the flag indicating if Fast I/O is possible
NtReqFcb->CommonFCBHeader.IsFastIoPossible = UDFIsFastIoPossible(Fcb);
/* if(NtReqFcb->CommonFCBHeader.IsFastIoPossible == FastIoIsPossible) {
NtReqFcb->CommonFCBHeader.IsFastIoPossible = FastIoIsQuestionable;
}*/
if ( (Irp->Flags & IRP_SYNCHRONOUS_PAGING_IO) &&
(PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_NOT_TOP_LEVEL)) {
// This clause determines if the top level request was
// in the FastIo path.
if ((ULONG_PTR)TopIrp > FSRTL_MAX_TOP_LEVEL_IRP_FLAG) {
PIO_STACK_LOCATION IrpStack;
ASSERT( TopIrp->Type == IO_TYPE_IRP );
IrpStack = IoGetCurrentIrpStackLocation(TopIrp);
// Finally this routine detects if the Top irp was a
// write to this file and thus we are the writethrough.
if ((IrpStack->MajorFunction == IRP_MJ_WRITE) &&
(IrpStack->FileObject->FsContext == FileObject->FsContext)) {
RecursiveWriteThrough = TRUE;
PtrIrpContext->IrpContextFlags |= UDF_IRP_CONTEXT_WRITE_THROUGH;
}
}
}
// Here is the deal with ValidDataLength and FileSize:
//
// Rule 1: PagingIo is never allowed to extend file size.
//
// Rule 2: Only the top level requestor may extend Valid
// Data Length. This may be paging IO, as when a
// a user maps a file, but will never be as a result
// of cache lazy writer writes since they are not the
// top level request.
//
// Rule 3: If, using Rules 1 and 2, we decide we must extend
// file size or valid data, we take the Fcb exclusive.
// Check whether the current request will extend the file size,
// or the valid data length (if the FSD supports the concept of a
// valid data length associated with the file stream). In either case,
// inform the Cache Manager at this time using CcSetFileSizes() about
// the new file length. Note that real FSD implementations will have to
// first allocate enough on-disk space at this point (before they
// inform the Cache Manager about the new size) to ensure that the write
// will subsequently not fail due to lack of disk space.
OldVDL = NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart;
ZeroBlock = (ByteOffset.QuadPart > OldVDL);
if (!PagingIo &&
!RecursiveWriteThrough &&
!IsThisADeferredWrite) {
BOOLEAN ExtendFS;
ExtendFS = (ByteOffset.QuadPart + TruncatedLength > NtReqFcb->CommonFCBHeader.FileSize.QuadPart);
if( WriteToEOF || ZeroBlock || ExtendFS) {
// we are extending the file;
if(!CanWait)
try_return(RC = STATUS_PENDING);
// CanWait = TRUE;
// Release any resources acquired above ...
if (PtrResourceAcquired2) {
UDFReleaseResource(PtrResourceAcquired2);
PtrResourceAcquired2 = NULL;
}
if (PtrResourceAcquired) {
UDFReleaseResource(PtrResourceAcquired);
PtrResourceAcquired = NULL;
}
if(!UDFAcquireResourceShared(&(Vcb->VCBResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
VcbAcquired = TRUE;
if(!Res1Acq) {
// Try to acquire the FCB MainResource exclusively
UDF_CHECK_PAGING_IO_RESOURCE(NtReqFcb);
if(!UDFAcquireResourceExclusive(&(NtReqFcb->MainResource), CanWait)) {
try_return(RC = STATUS_PENDING);
}
// Remember the resource that was acquired
PtrResourceAcquired = &(NtReqFcb->MainResource);
}
if(!Res2Acq) {
// allocate space...
AdPrint((" Try to acquire PagingIoRes\n"));
UDFAcquireResourceExclusive(&(NtReqFcb->PagingIoResource), TRUE );
PtrResourceAcquired2 = &(NtReqFcb->PagingIoResource);
}
AdPrint((" PagingIoRes Ok, Resizing...\n"));
if(ExtendFS) {
RC = UDFResizeFile__(Vcb, Fcb->FileInfo, ByteOffset.QuadPart + TruncatedLength);
if(!NT_SUCCESS(RC)) {
if(PtrResourceAcquired2) {
UDFReleaseResource(&(NtReqFcb->PagingIoResource));
PtrResourceAcquired2 = NULL;
}
try_return(RC);
}
Resized = TRUE;
// ... and inform the Cache Manager about it
NtReqFcb->CommonFCBHeader.FileSize.QuadPart = ByteOffset.QuadPart + TruncatedLength;
NtReqFcb->CommonFCBHeader.AllocationSize.QuadPart = UDFGetFileAllocationSize(Vcb, Fcb->FileInfo);
if(!Vcb->LowFreeSpace) {
NtReqFcb->CommonFCBHeader.AllocationSize.QuadPart += (PAGE_SIZE*9-1);
} else {
NtReqFcb->CommonFCBHeader.AllocationSize.QuadPart += (PAGE_SIZE-1);
}
NtReqFcb->CommonFCBHeader.AllocationSize.LowPart &= ~(PAGE_SIZE-1);
}
UDFPrint(("UDFCommonWrite: Set size %x (alloc size %x)\n", ByteOffset.LowPart + TruncatedLength, NtReqFcb->CommonFCBHeader.AllocationSize.LowPart));
if (CcIsFileCached(FileObject)) {
if(ExtendFS) {
MmPrint((" CcSetFileSizes()\n"));
CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&(NtReqFcb->CommonFCBHeader.AllocationSize));
NtReqFcb->NtReqFCBFlags |= UDF_NTREQ_FCB_MODIFIED;
}
// Attempt to Zero newly added fragment
// and ignore the return code
// This should be done to inform cache manager
// that given extent has no cached data
// (Otherwise, CM sometimes thinks that it has)
if(ZeroBlock) {
NtReqFcb->NtReqFCBFlags |= UDF_NTREQ_FCB_MODIFIED;
ThPrint((" UDFZeroDataEx(1)\n"));
UDFZeroDataEx(NtReqFcb,
OldVDL,
/*ByteOffset.QuadPart*/ NtReqFcb->CommonFCBHeader.FileSize.QuadPart - OldVDL,
CanWait, Vcb, FileObject);
#ifdef UDF_DBG
ZeroBlockDone = TRUE;
#endif //UDF_DBG
}
}
if (PtrResourceAcquired2) {
UDFReleaseResource(PtrResourceAcquired2);
PtrResourceAcquired2 = NULL;
}
// Inform any pending IRPs (notify change directory).
if(UDFIsAStream(Fcb->FileInfo)) {
UDFNotifyFullReportChange( Vcb, Fcb->FileInfo,
FILE_NOTIFY_CHANGE_STREAM_SIZE,
FILE_ACTION_MODIFIED_STREAM);
} else {
UDFNotifyFullReportChange( Vcb, Fcb->FileInfo,
FILE_NOTIFY_CHANGE_SIZE,
FILE_ACTION_MODIFIED);
}
}
}
#ifdef UDF_DISABLE_SYSTEM_CACHE_MANAGER
NonBufferedIo = TRUE;
#endif
if(Fcb && Fcb->FileInfo && Fcb->FileInfo->Dloc) {
AdPrint(("UDFCommonWrite: DataLoc %x, Mapping %x\n", Fcb->FileInfo->Dloc->DataLoc, Fcb->FileInfo->Dloc->DataLoc.Mapping));
}
// Branch here for cached vs non-cached I/O
if (!NonBufferedIo) {
// The caller wishes to perform cached I/O. Initiate caching if
// this is the first cached I/O operation using this file object
if (!FileObject->PrivateCacheMap) {
// This is the first cached I/O operation. You must ensure
// that the FCB Common FCB Header contains valid sizes at this time
UDFPrint(("UDFCommonWrite: Init system cache\n"));
MmPrint((" CcInitializeCacheMap()\n"));
CcInitializeCacheMap(FileObject, (PCC_FILE_SIZES)(&(NtReqFcb->CommonFCBHeader.AllocationSize)),
FALSE, // We will not utilize pin access for this file
&(UDFGlobalData.CacheMgrCallBacks), // callbacks
NtReqFcb); // The context used in callbacks
MmPrint((" CcSetReadAheadGranularity()\n"));
CcSetReadAheadGranularity(FileObject, Vcb->SystemCacheGran);
}
if(ZeroBlock && !ZeroBlockDone) {
ThPrint((" UDFZeroDataEx(2)\n"));
UDFZeroDataEx(NtReqFcb,
OldVDL,
/*ByteOffset.QuadPart*/ ByteOffset.QuadPart + TruncatedLength - OldVDL,
CanWait, Vcb, FileObject);
if(ByteOffset.LowPart & (PAGE_SIZE-1)) {
}
}
WriteFileSizeToDirNdx = (PtrIrpContext->IrpContextFlags & UDF_IRP_CONTEXT_WRITE_THROUGH) ?
TRUE : FALSE;
// Check and see if this request requires a MDL returned to the caller
if (IrpSp->MinorFunction & IRP_MN_MDL) {
// Caller does want a MDL returned. Note that this mode
// implies that the caller is prepared to block
MmPrint((" CcPrepareMdlWrite()\n"));
// CcPrepareMdlWrite(FileObject, &ByteOffset, TruncatedLength, &(Irp->MdlAddress), &(Irp->IoStatus));
// NumberBytesWritten = Irp->IoStatus.Information;
// RC = Irp->IoStatus.Status;
NumberBytesWritten = 0;
RC = STATUS_INVALID_PARAMETER;
try_return(RC);
}
if(NtReqFcb->SectionObject.DataSectionObject &&
TruncatedLength >= 0x10000 &&
ByteOffset.LowPart &&
!(ByteOffset.LowPart & 0x00ffffff)) {
//if(WinVer_Id() < WinVer_2k) {
//LARGE_INTEGER flush_offs;
//flush_offs.QuadPart = ByteOffset.QuadPart - 0x100*0x10000;
MmPrint((" CcFlushCache() 16Mb\n"));
//CcFlushCache(&(NtReqFcb->SectionObject), &ByteOffset, 0x100*0x10000, &(Irp->IoStatus));
// there was a nice idea: flush just previous part. But it doesn't work
CcFlushCache(&(NtReqFcb->SectionObject), NULL, 0, &(Irp->IoStatus));
//}
}
// This is a regular run-of-the-mill cached I/O request. Let the
// Cache Manager worry about it!
// First though, we need a buffer pointer (address) that is valid
// We needn't call CcZeroData 'cause udf_info.cpp will care about it
SystemBuffer = UDFGetCallersBuffer(PtrIrpContext, Irp);
if(!SystemBuffer)
try_return(RC = STATUS_INVALID_USER_BUFFER);
ASSERT(SystemBuffer);
NtReqFcb->NtReqFCBFlags |= UDF_NTREQ_FCB_MODIFIED;
PerfPrint(("UDFCommonWrite: CcCopyWrite %x bytes at %x\n", TruncatedLength, ByteOffset.LowPart));
MmPrint((" CcCopyWrite()\n"));
if(!CcCopyWrite(FileObject, &(ByteOffset), TruncatedLength, CanWait, SystemBuffer)) {
// The caller was not prepared to block and data is not immediately
// available in the system cache
// Mark Irp Pending ...
try_return(RC = STATUS_PENDING);
}
UDFUnlockCallersBuffer(PtrIrpContext, Irp, SystemBuffer);
// We have the data
RC = STATUS_SUCCESS;
NumberBytesWritten = TruncatedLength;
try_return(RC);
} else {
MmPrint((" Write NonBufferedIo\n"));
// We needn't call CcZeroData here (like in Fat driver)
// 'cause we've already done it above
// (see call to UDFZeroDataEx() )
if (!RecursiveWriteThrough &&
!IsThisADeferredWrite &&
(OldVDL < ByteOffset.QuadPart)) {
#ifdef UDF_DBG
ASSERT(!ZeroBlockDone);
#endif //UDF_DBG
UDFZeroDataEx(NtReqFcb,
OldVDL,
/*ByteOffset.QuadPart*/ ByteOffset.QuadPart - OldVDL,
CanWait, Vcb, FileObject);
}
if(OldVDL < (ByteOffset.QuadPart + TruncatedLength)) {
NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart = ByteOffset.QuadPart + TruncatedLength;
}
#if 1
if((ULONG_PTR)TopIrp == FSRTL_MOD_WRITE_TOP_LEVEL_IRP) {
UDFPrint(("FSRTL_MOD_WRITE_TOP_LEVEL_IRP => CanWait\n"));
CanWait = TRUE;
} else
if((ULONG_PTR)TopIrp == FSRTL_CACHE_TOP_LEVEL_IRP) {
UDFPrint(("FSRTL_CACHE_TOP_LEVEL_IRP => CanWait\n"));
CanWait = TRUE;
}
if(NtReqFcb->AcqSectionCount || NtReqFcb->AcqFlushCount) {
MmPrint((" AcqCount (%d/%d)=> CanWait ?\n", NtReqFcb->AcqSectionCount, NtReqFcb->AcqFlushCount));
CanWait = TRUE;
} else
{}
/* if((TopIrp != Irp)) {
UDFPrint(("(TopIrp != Irp) => CanWait\n"));
CanWait = TRUE;
} else*/
#endif
if(KeGetCurrentIrql() > PASSIVE_LEVEL) {
MmPrint((" !PASSIVE_LEVEL\n"));
CanWait = FALSE;
PtrIrpContext->IrpContextFlags |= UDF_IRP_CONTEXT_FORCED_POST;
}
// Successful check will cause WCache lock
if(!CanWait && UDFIsFileCached__(Vcb, Fcb->FileInfo, ByteOffset.QuadPart, TruncatedLength, TRUE)) {
UDFPrint(("UDFCommonWrite: Cached => CanWait\n"));
CacheLocked = TRUE;
CanWait = TRUE;
}
// Send the request to lower level drivers
if(!CanWait) {
UDFPrint(("UDFCommonWrite: Post physical write %x bytes at %x\n", TruncatedLength, ByteOffset.LowPart));
try_return(RC = STATUS_PENDING);
}
if(!Res2Acq) {
if(UDFAcquireResourceExclusiveWithCheck(&(NtReqFcb->PagingIoResource))) {
PtrResourceAcquired2 = &(NtReqFcb->PagingIoResource);
}
}
PerfPrint(("UDFCommonWrite: Physical write %x bytes at %x\n", TruncatedLength, ByteOffset.LowPart));
// Lock the callers buffer
if (!NT_SUCCESS(RC = UDFLockCallersBuffer(PtrIrpContext, Irp, TRUE, TruncatedLength))) {
try_return(RC);
}
SystemBuffer = UDFGetCallersBuffer(PtrIrpContext, Irp);
if(!SystemBuffer) {
try_return(RC = STATUS_INVALID_USER_BUFFER);
}
NtReqFcb->NtReqFCBFlags |= UDF_NTREQ_FCB_MODIFIED;
RC = UDFWriteFile__(Vcb, Fcb->FileInfo, ByteOffset.QuadPart, TruncatedLength,
CacheLocked, (PCHAR)SystemBuffer, &NumberBytesWritten);
UDFUnlockCallersBuffer(PtrIrpContext, Irp, SystemBuffer);
#if defined(_MSC_VER) && !defined(__clang__)
/* FIXME */
if(PagingIo) {
CollectStatistics(Vcb, UserDiskWrites);
} else {
CollectStatistics2(Vcb, NonCachedDiskWrites);
}
#endif
WriteFileSizeToDirNdx = TRUE;
try_return(RC);
}
try_exit: NOTHING;
} _SEH2_FINALLY {
if(CacheLocked) {
WCacheEODirect__(&(Vcb->FastCache), Vcb);
}
// Release any resources acquired here ...
if(PtrResourceAcquired2) {
UDFReleaseResource(PtrResourceAcquired2);
}
if(PtrResourceAcquired) {
if(NtReqFcb &&
(PtrResourceAcquired ==
&(NtReqFcb->MainResource))) {
UDF_CHECK_PAGING_IO_RESOURCE(NtReqFcb);
}
UDFReleaseResource(PtrResourceAcquired);
}
if(VcbAcquired) {
UDFReleaseResource(&(Vcb->VCBResource));
}
// Post IRP if required
if(RC == STATUS_PENDING) {
// Lock the callers buffer here. Then invoke a common routine to
// perform the post operation.
if (!(IrpSp->MinorFunction & IRP_MN_MDL)) {
RC = UDFLockCallersBuffer(PtrIrpContext, Irp, FALSE, WriteLength);
ASSERT(NT_SUCCESS(RC));
}
if(PagingIo) {
if(Res1Acq) {
PtrIrpContext->IrpContextFlags |= UDF_IRP_CONTEXT_RES1_ACQ;
}
if(Res2Acq) {
PtrIrpContext->IrpContextFlags |= UDF_IRP_CONTEXT_RES2_ACQ;
}
}
// Perform the post operation which will mark the IRP pending
// and will return STATUS_PENDING back to us
RC = UDFPostRequest(PtrIrpContext, Irp);
} else {
// For synchronous I/O, the FSD must maintain the current byte offset
// Do not do this however, if I/O is marked as paging-io
if (SynchronousIo && !PagingIo && NT_SUCCESS(RC)) {
FileObject->CurrentByteOffset.QuadPart = ByteOffset.QuadPart + NumberBytesWritten;
}
// If the write completed successfully and this was not a paging-io
// operation, set a flag in the CCB that indicates that a write was
// performed and that the file time should be updated at cleanup
if (NT_SUCCESS(RC) && !PagingIo) {
Ccb->CCBFlags |= UDF_CCB_MODIFIED;
// If the file size was changed, set a flag in the FCB indicating that
// this occurred.
FileObject->Flags |= FO_FILE_MODIFIED;
if(Resized) {
if(!WriteFileSizeToDirNdx) {
FileObject->Flags |= FO_FILE_SIZE_CHANGED;
} else {
ASize = UDFGetFileAllocationSize(Vcb, Fcb->FileInfo);
UDFSetFileSizeInDirNdx(Vcb, Fcb->FileInfo, &ASize);
}
}
// Update ValidDataLength
if(!IsThisADeferredWrite &&
NtReqFcb) {
if(NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart < (ByteOffset.QuadPart + NumberBytesWritten)) {
NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart =
min(NtReqFcb->CommonFCBHeader.FileSize.QuadPart,
ByteOffset.QuadPart + NumberBytesWritten);
}
}
}
// If the request failed, and we had done some nasty stuff like
// extending the file size (including informing the Cache Manager
// about the new file size), and allocating on-disk space etc., undo
// it at this time.
// Can complete the IRP here if no exception was encountered
if(!_SEH2_AbnormalTermination() &&
Irp) {
Irp->IoStatus.Status = RC;
Irp->IoStatus.Information = NumberBytesWritten;
// complete the IRP
MmPrint((" Complete Irp, MDL=%x\n", Irp->MdlAddress));
if(Irp->MdlAddress) {
UDFTouch(Irp->MdlAddress);
}
IoCompleteRequest(Irp, IO_DISK_INCREMENT);
}
// Free up the Irp Context
UDFReleaseIrpContext(PtrIrpContext);
} // can we complete the IRP ?
} _SEH2_END; // end of "__finally" processing
UDFPrint(("\n"));
return(RC);
} // end UDFCommonWrite()
/*************************************************************************
*
* Function: UDFDeferredWriteCallBack()
*
* Description:
* Invoked by the cache manager in the context of a worker thread.
* Typically, you can simply post the request at this point (just
* as you would have if the original request could not block) to
* perform the write in the context of a system worker thread.
*
* Expected Interrupt Level (for execution) :
*
* IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
VOID
NTAPI
UDFDeferredWriteCallBack(
IN PVOID Context1, // Should be PtrIrpContext
IN PVOID Context2 // Should be Irp
)
{
UDFPrint(("UDFDeferredWriteCallBack\n"));
// We should typically simply post the request to our internal
// queue of posted requests (just as we would if the original write
// could not be completed because the caller could not block).
// Once we post the request, return from this routine. The write
// will then be retried in the context of a system worker thread
UDFPostRequest((PtrUDFIrpContext)Context1, (PIRP)Context2);
} // end UDFDeferredWriteCallBack()
/*************************************************************************
*
*************************************************************************/
#define USE_CcCopyWrite_TO_ZERO
VOID
UDFPurgeCacheEx_(
PtrUDFNTRequiredFCB NtReqFcb,
LONGLONG Offset,
LONGLONG Length,
//#ifndef ALLOW_SPARSE
BOOLEAN CanWait,
//#endif //ALLOW_SPARSE
PVCB Vcb,
PFILE_OBJECT FileObject
)
{
ULONG Off_l;
#ifdef USE_CcCopyWrite_TO_ZERO
ULONG PgLen;
#endif //USE_CcCopyWrite_TO_ZERO
// We'll just purge cache section here,
// without call to CcZeroData()
// 'cause udf_info.cpp will care about it
#define PURGE_BLOCK_SZ 0x10000000
// NOTE: if FS engine doesn't suport
// sparse/unrecorded areas, CcZeroData must be called
// In this case we'll see some recursive WRITE requests
_SEH2_TRY {
MmPrint((" UDFPurgeCacheEx_(): Offs: %I64x, ", Offset));
MmPrint((" Len: %lx\n", Length));
SECTION_OBJECT_POINTERS* SectionObject = &(NtReqFcb->SectionObject);
if(Length) {
LONGLONG Offset0, OffsetX, VDL;
Offset0 = Offset;
if((Off_l = ((ULONG)Offset0 & (PAGE_SIZE-1)))) {
// Offset, Offset0
// v
// ...|dddddddddddd00000|....
// |<- Off_l ->|
#ifndef USE_CcCopyWrite_TO_ZERO
*((PULONG)&Offset0) &= ~(PAGE_SIZE-1);
MmPrint((" CcFlushCache(s) Offs %I64x, Len %x\n", Offset0, Off_l));
CcFlushCache( SectionObject, (PLARGE_INTEGER)&Offset0, Off_l, NULL );
#else //USE_CcCopyWrite_TO_ZERO
// ...|ddddd000000000000|....
// |<- PgLen ->|
PgLen = PAGE_SIZE - Off_l; /*(*((PULONG)&Offset) & (PAGE_SIZE-1))*/
//
if(PgLen > Length)
PgLen = (ULONG)Length;
MmPrint((" ZeroCache (CcWrite) Offs %I64x, Len %x\n", Offset, PgLen));
#ifdef DBG
if(FileObject && Vcb) {
ASSERT(CanWait);
#endif //DBG
if (PgLen) {
if (SectionObject->SharedCacheMap) {
CcCopyWrite(FileObject, (PLARGE_INTEGER)&Offset, PgLen, TRUE || CanWait, Vcb->ZBuffer);
}
Offset += PgLen;
Length -= PgLen;
}
#ifdef DBG
} else {
MmPrint((" Can't use CcWrite to zero cache\n"));
}
#endif //DBG
#endif //USE_CcCopyWrite_TO_ZERO
}
VDL = NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart;
OffsetX = Offset+Length;
if((Off_l = ((ULONG)OffsetX & (PAGE_SIZE-1)))) {
if(OffsetX < VDL) {
#ifndef USE_CcCopyWrite_TO_ZERO
Off_l = ( (ULONG)(VDL-OffsetX) > PAGE_SIZE ) ?
(PAGE_SIZE - Off_l) :
((ULONG)(VDL-OffsetX));
*((PULONG)&OffsetX) &= ~(PAGE_SIZE-1);
MmPrint((" CcFlushCache(e) Offs %I64x, Len %x\n", OffsetX, Off_l));
CcFlushCache( SectionObject, (PLARGE_INTEGER)&OffsetX, Off_l, NULL );
#else //USE_CcCopyWrite_TO_ZERO
if(VDL - OffsetX > PAGE_SIZE) {
PgLen = (ULONG)OffsetX & ~(PAGE_SIZE-1);
} else {
PgLen = (ULONG)(VDL - OffsetX) & ~(PAGE_SIZE-1);
}
// ...|000000000000ddddd|....
// |<- PgLen ->|
MmPrint((" ZeroCache (CcWrite - 2) Offs %I64x, Len %x\n", OffsetX, PgLen));
#ifdef DBG
if(FileObject && Vcb) {
ASSERT(CanWait);
#endif //DBG
if (SectionObject->SharedCacheMap) {
CcCopyWrite(FileObject, (PLARGE_INTEGER)&OffsetX, PgLen, TRUE || CanWait, Vcb->ZBuffer);
}
Length -= PgLen;
#ifdef DBG
} else {
MmPrint((" Can't use CcWrite to zero cache (2)\n"));
}
#endif //DBG
#endif //USE_CcCopyWrite_TO_ZERO
}
}
#ifndef USE_CcCopyWrite_TO_ZERO
do
#else //USE_CcCopyWrite_TO_ZERO
while(Length)
#endif //USE_CcCopyWrite_TO_ZERO
{
MmPrint((" CcPurgeCacheSection()\n"));
if(PURGE_BLOCK_SZ > Length) {
CcPurgeCacheSection(SectionObject, (PLARGE_INTEGER)&Offset,
(ULONG)Length, FALSE);
/*
NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart += Length;
ASSERT(NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart <=
NtReqFcb->CommonFCBHeader.FileSize.QuadPart);
MmPrint((" CcFlushCache()\n"));
CcFlushCache( SectionObject, (PLARGE_INTEGER)&Offset, (ULONG)Length, NULL );
*/
#ifndef ALLOW_SPARSE
// UDFZeroFile__(
#endif //ALLOW_SPARSE
break;
} else {
CcPurgeCacheSection(SectionObject, (PLARGE_INTEGER)&Offset,
PURGE_BLOCK_SZ, FALSE);
/*
NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart += PURGE_BLOCK_SZ;
ASSERT(NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart <=
NtReqFcb->CommonFCBHeader.FileSize.QuadPart);
MmPrint((" CcFlushCache()\n"));
CcFlushCache( SectionObject, (PLARGE_INTEGER)&Offset, (ULONG)Length, NULL );
*/
#ifndef ALLOW_SPARSE
// UDFZeroFile__(
#endif //ALLOW_SPARSE
Length -= PURGE_BLOCK_SZ;
Offset += PURGE_BLOCK_SZ;
}
}
#ifndef USE_CcCopyWrite_TO_ZERO
while(Length);
#endif //USE_CcCopyWrite_TO_ZERO
if(VDL < Offset)
NtReqFcb->CommonFCBHeader.ValidDataLength.QuadPart = Offset;
}
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
BrutePoint();
} _SEH2_END;
} // end UDFPurgeCacheEx_()
#endif //UDF_READ_ONLY_BUILD