/*************************************************************************
*
* File: flush.c
*
* Module: Sample File System Driver (Kernel mode execution only)
*
* Description:
*	Contains code to handle the "Flush Buffers" 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_FLUSH



/*************************************************************************
*
* Function: SFsdFlush()
*
* Description:
*	The I/O Manager will invoke this routine to handle a flush buffers
*	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 SFsdFlush(
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 = SFsdCommonFlush(PtrIrpContext, Irp);

	} except (SFsdExceptionFilter(PtrIrpContext, GetExceptionInformation())) {

		RC = SFsdExceptionHandler(PtrIrpContext, Irp);

		SFsdLogEvent(SFSD_ERROR_INTERNAL_ERROR, RC);
	}

	if (AreWeTopLevel) {
		IoSetTopLevelIrp(NULL);
	}

	FsRtlExitFileSystem();

	return(RC);
}



/*************************************************************************
*
* Function: SFsdCommonFlush()
*
* 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	SFsdCommonFlush(
PtrSFsdIrpContext			PtrIrpContext,
PIRP							PtrIrp)
{
	NTSTATUS					RC = STATUS_SUCCESS;
	PIO_STACK_LOCATION	PtrIoStackLocation = NULL;
	PFILE_OBJECT			PtrFileObject = NULL;
	PtrSFsdFCB				PtrFCB = NULL;
	PtrSFsdCCB				PtrCCB = NULL;
	PtrSFsdVCB				PtrVCB = NULL;
	PtrSFsdNTRequiredFCB	PtrReqdFCB = NULL;
	BOOLEAN					AcquiredFCB = FALSE;
	BOOLEAN					PostRequest = FALSE;
	BOOLEAN					CanWait = TRUE;

	try {
		// First, get a pointer to the current I/O stack location
		PtrIoStackLocation = IoGetCurrentIrpStackLocation(PtrIrp);
		ASSERT(PtrIoStackLocation);

		PtrFileObject = PtrIoStackLocation->FileObject;
		ASSERT(PtrFileObject);

		// Get the FCB and CCB pointers
		PtrCCB = (PtrSFsdCCB)(PtrFileObject->FsContext2);
		ASSERT(PtrCCB);
		PtrFCB = PtrCCB->PtrFCB;
		ASSERT(PtrFCB);
		PtrReqdFCB = &(PtrFCB->NTRequiredFCB);

		// Get some of the parameters supplied to us
		CanWait = ((PtrIrpContext->IrpContextFlags & SFSD_IRP_CONTEXT_CAN_BLOCK) ? TRUE : FALSE);

		// If we cannot wait, post the request immediately since a flush is inherently blocking/synchronous.
		if (!CanWait) {
			PostRequest = TRUE;
			try_return(RC);
		}

		// Check the type of object passed-in. That will determine the course of
		// action we take.
		if ((PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) || (PtrFCB->FCBFlags & SFSD_FCB_ROOT_DIRECTORY)) {

			if (PtrFCB->NodeIdentifier.NodeType == SFSD_NODE_TYPE_VCB) {
				PtrVCB = (PtrSFsdVCB)(PtrFCB);
			} else {
				PtrVCB = PtrFCB->PtrVCB;
			}

			// The caller wishes to flush all files for the mounted
			// logical volume. The flush volume routine below should simply
			// walk through all of the open file streams, acquire the
			// FCB resource, and request the flush operation from the Cache
			// Manager. Basically, the sequence of operations listed below
			// for a single file should be executed on all open files.

			SFsdFlushLogicalVolume(PtrIrpContext, PtrIrp, PtrVCB);

			try_return(RC);
		}

		if (!(PtrFCB->FCBFlags & SFSD_FCB_DIRECTORY)) {
			// This is a regular file.
			ExAcquireResourceExclusiveLite(&(PtrReqdFCB->MainResource), TRUE);
			AcquiredFCB = TRUE;

			// Request the Cache Manager to perform a flush operation.
			// Further, instruct the Cache Manager that we wish to flush the
			// entire file stream.
			SFsdFlushAFile(PtrReqdFCB, &(PtrIrp->IoStatus));
			RC = PtrIrp->IoStatus.Status;
			// All done. You may want to also flush the directory entry for the
			// file stream at this time.

			// Some log-based FSD implementations may wish to flush their
			// log files at this time. Finally, you should update the time-stamp
			// values for the file stream appropriately. This would involve
			// obtaining the current time and modifying the appropriate directory
			// entry fields.
		}

		try_exit:

		if (AcquiredFCB) {
			SFsdReleaseResource(&(PtrReqdFCB->MainResource));
			AcquiredFCB = FALSE;
		}

		if (!PostRequest) {
			PIO_STACK_LOCATION		PtrNextIoStackLocation = NULL;
			NTSTATUS						RC1 = STATUS_SUCCESS;

			// Send the request down at this point.
			// To do this, you must set the next IRP stack location, and
			// maybe set a completion routine.
			// Be careful about marking the IRP pending if the lower level
			// driver returned pending and you do have a completion routine!
			PtrNextIoStackLocation = IoGetNextIrpStackLocation(PtrIrp);
			*PtrNextIoStackLocation = *PtrIoStackLocation;

			// Set the completion routine to "eat-up" any
			// STATUS_INVALID_DEVICE_REQUEST error code returned by the lower
			// level driver.
			IoSetCompletionRoutine(PtrIrp, SFsdFlushCompletion, NULL, TRUE, TRUE, TRUE);

			RC1 = IoCallDriver(PtrVCB->TargetDeviceObject, PtrIrp);

			RC = ((RC1 == STATUS_INVALID_DEVICE_REQUEST) ? RC : RC1);
		}

	} finally {
		if (PostRequest) {
			// Nothing to lock now.
			RC = SFsdPostRequest(PtrIrpContext, PtrIrp);
		} else {
			// Release the IRP context at this time.
  			SFsdReleaseIrpContext(PtrIrpContext);
		}
	}

	return(RC);
}


/*************************************************************************
*
* Function: SFsdFlushAFile()
*
* Description:
*	Tell the Cache Manager to perform a flush.
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
void SFsdFlushAFile(
PtrSFsdNTRequiredFCB	PtrReqdFCB,
PIO_STATUS_BLOCK		PtrIoStatus)
{
	CcFlushCache(&(PtrReqdFCB->SectionObject), NULL, 0, PtrIoStatus);
	return;
}


/*************************************************************************
*
* Function: SFsdFlushLogicalVolume()
*
* Description:
*	Flush everything beginning at root directory.
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
void SFsdFlushLogicalVolume(
PtrSFsdIrpContext			PtrIrpContext,
PIRP							PtrIrp,
PtrSFsdVCB					PtrVCB)
{
	BOOLEAN			AcquiredVCB = FALSE;
	PtrSFsdFCB		PtrFCB = NULL;
	PLIST_ENTRY		PtrNextFCB = NULL;

	try {
		ExAcquireResourceExclusiveLite(&(PtrVCB->VCBResource), TRUE);
		AcquiredVCB = TRUE;

		// Go through the list of FCB's. You would probably
		// flush all of the files. Then, you could flush the
		// directories that you may have have pinned into memory.

		// NOTE: This function may also be invoked internally as part of
		// processing a shutdown request.

	} finally {

		if (AcquiredVCB) {
			SFsdReleaseResource(&(PtrVCB->VCBResource));
		}
	}

	return;
}


/*************************************************************************
*
* Function: SFsdFlushCompletion()
*
* Description:
*	Eat up any bad errors.
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
NTSTATUS SFsdFlushCompletion(
PDEVICE_OBJECT	PtrDeviceObject,
PIRP				PtrIrp,
PVOID				Context)
{
	NTSTATUS		RC = STATUS_SUCCESS;

	if (PtrIrp->PendingReturned) {
		IoMarkIrpPending(PtrIrp);
	}

	if (PtrIrp->IoStatus.Status == STATUS_INVALID_DEVICE_REQUEST) {
		// cannot do much here, can we?
		PtrIrp->IoStatus.Status = STATUS_SUCCESS;
	}

	return(STATUS_SUCCESS);
}


