www.pudn.com > filesys.rar > write.c
/*************************************************************************
*
* File: write.c
*
* Module: Sample File System Driver (Kernel mode execution only)
*
* Description:
* Contains code to handle the "Write" dispatch entry point.
*
* Author: R. Nagar
*
* (c) 1996-97 Rajeev Nagar, All Rights Reserved
*
*************************************************************************/
#include "sfsd.h"
// define the file specific bug-check id
#define SFSD_BUG_CHECK_ID SFSD_FILE_WRITE
/*************************************************************************
*
* Function: SFsdWrite()
*
* 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 SFsdWrite(
PDEVICE_OBJECT DeviceObject, // the logical volume device object
PIRP Irp) // I/O Request Packet
{
NTSTATUS RC = STATUS_SUCCESS;
PtrSFsdIrpContext PtrIrpContext = NULL;
BOOLEAN AreWeTopLevel = FALSE;
FsRtlEnterFileSystem();
ASSERT(DeviceObject);
ASSERT(Irp);
// set the top level context
AreWeTopLevel = SFsdIsIrpTopLevel(Irp);
try {
// get an IRP context structure and issue the request
PtrIrpContext = SFsdAllocateIrpContext(Irp, DeviceObject);
ASSERT(PtrIrpContext);
RC = SFsdCommonWrite(PtrIrpContext, Irp);
} except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) {
RC = SFsdExceptionHandler(PtrIrpContext, Irp);
SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC);
}
if (AreWeTopLevel) {
IoSetTopLevelIrp(NULL);
}
FsRtlExitFileSystem();
return(RC);
}
/*************************************************************************
*
* Function: SFsdCommonWrite()
*
* 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 SFsdCommonWrite(
PtrSFsdIrpContext PtrIrpContext,
PIRP PtrIrp)
{
NTSTATUS RC = STATUS_SUCCESS;
PIO_STACK_LOCATION PtrIoStackLocation = NULL;
LARGE_INTEGER ByteOffset;
uint32 WriteLength = 0, TruncatedWriteLength = 0;
uint32 NumberBytesWritten = 0;
PFILE_OBJECT PtrFileObject = NULL;
PtrSFsdFCB PtrFCB = NULL;
PtrSFsdCCB PtrCCB = NULL;
PtrSFsdVCB PtrVCB = NULL;
PtrSFsdNTRequiredFCB PtrReqdFCB = NULL;
PERESOURCE PtrResourceAcquired = NULL;
IO_STATUS_BLOCK LocalIoStatus;
void *PtrSystemBuffer = NULL;
uint32 KeyValue = 0;
BOOLEAN CompleteIrp = TRUE;
BOOLEAN PostRequest = FALSE;
BOOLEAN CanWait = FALSE;
BOOLEAN PagingIo = FALSE;
BOOLEAN NonBufferedIo = FALSE;
BOOLEAN SynchronousIo = FALSE;
BOOLEAN IsThisADeferredWrite = FALSE;
BOOLEAN WritingAtEndOfFile = FALSE;
try {
// First, get a pointer to the current I/O stack location
PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp);
ASSERT(PtrIoStackLocation);
PtrFileObject = PtrIoStackLocation->FileObject;
ASSERT(PtrFileObject);
// 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 (PtrIoStackLocation->MinorFunction & IRP_MN_COMPLETE) {
// Caller wants to tell the Cache Manager that a previously
// allocated MDL can be freed.
SFsdMdlComplete(PtrIrpContext, PtrIrp, PtrIoStackLocation, FALSE);
// The IRP has been completed.
CompleteIrp = FALSE;
try_return(RC = STATUS_SUCCESS);
}
// If this is a request at IRQL DISPATCH_LEVEL, then post
// the request (your FSD may choose to process it synchronously
// if you implement the support correctly).
if (PtrIoStackLocation->MinorFunction & IRP_MN_DPC) {
CompleteIrp = FALSE;
PostRequest = TRUE;
try_return(RC = STATUS_PENDING);
}
// Get the FCB and CCB pointers
PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2);
ASSERT(PtrCCB);
PtrFCB = PtrCCB->PtrFCB;
ASSERT(PtrFCB);
// Get some of the parameters supplied to us
ByteOffset = PtrIoStackLocation->Parameters.Write.ByteOffset;
WriteLength = PtrIoStackLocation->Parameters.Write.Length;
CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE);
PagingIo = ((PtrIrp->Flags & IRP_PAGING_IO) ? TRUE : FALSE);
NonBufferedIo = ((PtrIrp->Flags & IRP_NOCACHE) ? TRUE : FALSE);
SynchronousIo = ((PtrFileObject->Flags & FO_SYNCHRONOUS_IO) ? TRUE : FALSE);
// You might wish to check at this point whether the file object being
// used for write really did have write permission requested when the
// create/open operation was performed. Of course, for paging-io write
// operations, the check is not valid since paging-io (via the VMM) could
// use any file object (likely the first one with which caching was
// initiated on the FCB) to perform the write operation
if (WriteLength == 0) {
// a 0 byte write can be immediately succeeded
try_return(RC);
}
// NOTE: if your FSD does not support file sizes > 2 GB, you
// could validate the start offset here and return end-of-file
// if the offset begins beyond the maximum supported length.
// Is this a write of the volume itself ?
if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) {
// Yup, we need to send this on to the disk driver after
// validation of the offset and length.
PtrVCB = (PtrSFsdVCB)(PtrFCB);
// Acquire the volume resource exclusively
if (!ExAcquireResourceSharedLite(&(PtrVCB->VCBResource), CanWait)) {
// post the request to be processed in the context of a worker thread
CompleteIrp = FALSE;
PostRequest = TRUE;
try_return(RC = STATUS_PENDING);
}
PtrResourceAcquired = &(PtrVCB->VCBResource);
// Insert code to validate the caller supplied offset here
// Lock the callers buffer
if (!NT_SUCCESS(RC = SFsdLockCallersBuffer(PtrIrp, TRUE, WriteLength))) {
try_return(RC);
}
// Forward the request to the lower level driver
// For synchronous I/O wait here, else return STATUS_PENDING
// For asynchronous I/O support, read the discussion in Chapter 10
try_return(RC);
}
// Your 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, you 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 the IrpContext structure we have created (See the
// appendix to this book for a definition of the structure)
IsThisADeferredWrite = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_DEFERRED_WRITE) ? TRUE : FALSE);
if (!NonBufferedIo) {
if (!CcCanIWrite(PtrFileObject, WriteLength, CanWait, IsThisADeferredWrite)) {
// Cache Manager and/or the VMM does not want us to perform
// the write at this time. Post the request.
SFsdSetFlag(PtrIrpContext->IrpContextFlags, SFSD_IRP_CONTEXT_DEFERRED_WRITE);
CcDeferWrite(PtrFileObject, SFsdDeferredWriteCallBack, PtrIrpContext, PtrIrp, WriteLength, IsThisADeferredWrite);
CompleteIrp = FALSE;
try_return(RC = STATUS_PENDING);
}
}
// If the write request is directed to a page file (if your FSD
// supports paging files), send the request directly to the disk
// driver. For requests directed to a page file, you have to trust
// that the offsets will be set correctly by the VMM. You should not
// attempt to acquire any FSD resources either.
if (PtrFCB->FCBFlags & SFSD_FCB_PAGE_FILE) {
IoMarkIrpPending(PtrIrp);
// You will need to set a completion routine before invoking a lower level driver.
// Forward request directly to disk driver.
// SFsdPageFileIo(PtrIrpContext, PtrIrp);
CompleteIrp = FALSE;
try_return(RC = STATUS_PENDING);
}
// We can continue. Check whether this write operation is targeted
// to a directory object in which case the sample FSD will disallow
// the write request. Once again though, if you create a stream file
// object to represent a directory in memory, you could come to this
// point as a result of modifying the directory contents internally
// by the FSD itself. In that case, you should be able to differentiate
// the directory write as being an internal, non-cached write operation
// and allow it to proceed.
if (PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY) {
RC = STATUS_INVALID_DEVICE_REQUEST;
try_return(RC);
}
PtrReqdFCB = &(PtrFCB->NTRequiredFCB);
// Acquire the appropriate FCB resource exclusively
if (PagingIo) {
// Try to acquire the FCB PagingIoResource exclusively
if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->PagingIoResource), CanWait)) {
CompleteIrp = FALSE;
PostRequest = TRUE;
try_return(RC = STATUS_PENDING);
}
// Remember the resource that was acquired
PtrResourceAcquired = &(PtrReqdFCB->PagingIoResource);
} else {
// Try to acquire the FCB MainResource exclusively
if (!ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), CanWait)) {
CompleteIrp = FALSE;
PostRequest = TRUE;
try_return(RC = STATUS_PENDING);
}
// Remember the resource that was acquired
PtrResourceAcquired = &(PtrReqdFCB->MainResource);
}
// 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.LowPart == FILE_WRITE_TO_END_OF_FILE) && (ByteOffset.HighPart == 0xFFFFFFFF)) {
WritingAtEndOfFile = TRUE;
}
// 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, you should try to flush the
// cached data (if some other file object has already initiated caching
// on the file stream). You 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
// Read Chapter 10 for more information on the issues involved in
// maintaining data consistency.
if (NonBufferedIo && !PagingIo && (PtrReqdFCB->SectionObject.DataSectionObject != NULL)) {
// Flush and then attempt to purge the cache
CcFlushCache(&(PtrReqdFCB->SectionObject), &ByteOffset, WriteLength, &(PtrIrp->IoStatus));
// If the flush failed, return error to the caller
if (!NT_SUCCESS(RC = PtrIrp->IoStatus.Status)) {
try_return(RC);
}
// Attempt the purge and ignore the return code
CcPurgeCacheSection(&(PtrReqdFCB->SectionObject), (WritingAtEndOfFile ? &(PtrReqdFCB->CommonFCBHeader.FileSize) : &(ByteOffset)),
WriteLength, FALSE);
// We are finished with our flushing and purging
}
// Paging I/O write operations are special. If paging i/o write
// requests begin beyond end-of-file, the request should be no-oped
// (see the next two chapters for more information). If paging i/o
// requests extend beyond current end of file, they should be truncated
// to current end-of-file.
// Insert code to do this here.
// This is also a good place to set whether fast-io can be performed
// on this particular file or not. Your FSD must make it's own
// determination on whether or not to allow fast-io operations.
// Commonly, fast-io is not allowed if any byte range locks exist
// on the file or if oplocks prevent fast-io. Practically any reason
// choosen by your FSD could result in your setting FastIoIsNotPossible
// OR FastIoIsQuestionable instead of FastIoIsPossible.
//
// PtrReqdFCB->CommonFCBHeader.IsFastIoPossible = FastIoIsPossible;
// This is a good place for oplock related processing.
// Chapter 11 expands upon this topic in greater detail.
// Check whether the desired write can be allowed depending
// on any byte range locks that might exist. Note that for
// paging-io, no such checks should be performed.
if (!PagingIo) {
// Insert code to perform the check here ...
// if (!SFsdCheckForByteLock(PtrFCB, PtrCCB, PtrIrp,
// PtrCurrentIoStackLocation)) {
// try_return(RC = STATUS_FILE_LOCK_CONFLICT);
// }
}
// Check whether the current request will extend the file size,
// or the valid data length (if your 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.
// if ((WritingAtEndOfFile) || ((ByteOffset + TruncatedWriteLength) > PtrReqdFCB->CommonFCBHeader.FileSize)) {
// we are extending the file;
// allocate space and inform the Cache Manager;
// } else if (same test as above for valid data length) {
// we are extending valid data length, inform Cache Manager;
// }
// 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 (PtrFileObject->PrivateCacheMap == NULL) {
// This is the first cached I/O operation. You must ensure
// that the FCB Common FCB Header contains valid sizes at this time
CcInitializeCacheMap(PtrFileObject, (PCC_FILE_SIZES)(&(PtrReqdFCB->CommonFCBHeader.AllocationSize)),
FALSE, // We will not utilize pin access for this file
&(SFsdGlobalData.CacheMgrCallBacks), // callbacks
PtrCCB); // The context used in callbacks
}
// Check and see if this request requires a MDL returned to the caller
if (PtrIoStackLocation->MinorFunction & IRP_MN_MDL) {
// Caller does want a MDL returned. Note that this mode
// implies that the caller is prepared to block
CcPrepareMdlWrite(PtrFileObject, &ByteOffset, TruncatedWriteLength, &(PtrIrp->MdlAddress), &(PtrIrp->IoStatus));
NumberBytesWritten = PtrIrp->IoStatus.Information;
RC = PtrIrp->IoStatus.Status;
try_return(RC);
}
// 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
// More on this in Chapter 10
// Also, if the request extends the ValidDataLength, use CcZeroData()
// first to zero out the gap (if any) between current valid data
// length and the start of the request
PtrSystemBuffer = SFsdGetCallersBuffer(PtrIrp);
ASSERT(PtrSystemBuffer);
if (!CcCopyWrite(PtrFileObject, &(ByteOffset), TruncatedWriteLength, CanWait, PtrSystemBuffer)) {
// The caller was not prepared to block and data is not immediately
// available in the system cache
CompleteIrp = FALSE;
PostRequest = TRUE;
// Mark Irp Pending ...
try_return(RC = STATUS_PENDING);
} else {
// We have the data
PtrIrp->IoStatus.Status = RC;
PtrIrp->IoStatus.Information = NumberBytesWritten = WriteLength;
}
} else {
// If the request extends beyond valid data length, and if the caller
// is not the lazy writer, then utilize CcZeroData() to zero out any
// blocks between current ValidDataLength and the start of the write
// operation. This method of zeroing data is convenient since it avoids
// any unneccessary writes to disk. Of course, if your FSD makes no
// guarantees about reading uninitialized data (native NT FSD
// implementations guarantee that read operations will receive zeroes
// if the sectors were not written to, thereby ensuring that old data
// cannot be re-read unintentionally or maliciously), you can avoid
// performing the zeroing operation altogether.
// You must however be careful about correctly determining the
// top-level component for the IRP so as to be able to extend valid
// data length only when appropriate and also avoid any
// infinite, recursive loops.
// See Chapter 10 for a discussion on this topic.
// Here is a common method used by Windows NT file system drivers
// that are in the process of sending a request to the disk driver.
// First, mark the IRP as pending, then invoke the lower level driver
// after setting a completion routine.
// Meanwhile, this particular thread can immediately return a
// STATUS_PENDING return code.
// The completion routine is then responsible for completing the IRP
// and unlocking appropriate resources
IoMarkIrpPending(PtrIrp);
// Invoke a routine to write information to disk at this time
// You will need to set a completion routine before invoking
// a lower level driver
CompleteIrp = FALSE;
try_return(RC = STATUS_PENDING);
}
try_exit: NOTHING;
} finally {
// Post IRP if required
if (PostRequest) {
// Implement a routine that will queue up the request to be executed
// later (asynchronously) in the context of a system worker thread.
// See Chapter 10 for details.
if (PtrResourceAcquired) {
SFsdReleaseResource(PtrResourceAcquired);
}
} else if (CompleteIrp && !(RC == STATUS_PENDING)) {
// 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)) {
PtrFileObject->CurrentByteOffset = RtlLargeIntegerAdd(ByteOffset,
RtlConvertUlongToLargeInteger((unsigned long)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) {
SFsdSetFlag(PtrCCB->CCBFlags, SFSD_CCB_MODIFIED);
}
// If the file size was changed, set a flag in the FCB indicating that
// this occurred.
// 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.
// Release resources ...
if (PtrResourceAcquired) {
SFsdReleaseResource(PtrResourceAcquired);
}
// Can complete the IRP here if no exception was encountered
if (!(PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_EXCEPTION)) {
PtrIrp->IoStatus.Status = RC;
PtrIrp->IoStatus.Information = NumberBytesWritten;
// Free up the Irp Context
SFsdReleaseIrpContext(PtrIrpContext);
// complete the IRP
IoCompleteRequest(PtrIrp, IO_DISK_INCREMENT);
}
} // can we complete the IRP ?
} // end of "finally" processing
return(RC);
}
/*************************************************************************
*
* Function: SFsdDeferredWriteCallBack()
*
* 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 SFsdDeferredWriteCallBack (
void *Context1, // Should be PtrIrpContext
void *Context2) // Should be PtrIrp
{
// You should typically simply post the request to your internal
// queue of posted requests (just as you would if the original write
// could not be completed because the caller could not block).
// Once you post the request, return from this routine. The write
// will then be retried in the context of a system worker thread
}