//////////////////////////////////////////////////////////////////// // 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