////////////////////////////////////////////////////////////////////
// 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: Flush.cpp
*
* Module: UDF File System Driver (Kernel mode execution only)
*
* Description:
*   Contains code to handle the "Flush Buffers" dispatch entry point.
*
*************************************************************************/

#include            "udffs.h"

// define the file specific bug-check id
#define         UDF_BUG_CHECK_ID                UDF_FILE_FLUSH



/*************************************************************************
*
* Function: UDFFlush()
*
* 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
NTAPI
UDFFlush(
    PDEVICE_OBJECT      DeviceObject,       // the logical volume device object
    PIRP                Irp)                // I/O Request Packet
{
    NTSTATUS            RC = STATUS_SUCCESS;
    PtrUDFIrpContext    PtrIrpContext = NULL;
    BOOLEAN             AreWeTopLevel = FALSE;

    UDFPrint(("UDFFlush: \n"));

    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 = UDFCommonFlush(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 UDFFlush()



/*************************************************************************
*
* Function: UDFCommonFlush()
*
* 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
UDFCommonFlush(
    PtrUDFIrpContext PtrIrpContext,
    PIRP             Irp
    )
{
    NTSTATUS            RC = STATUS_SUCCESS;
    PIO_STACK_LOCATION  IrpSp = NULL;
    PFILE_OBJECT        FileObject = NULL;
    PtrUDFFCB           Fcb = NULL;
    PtrUDFCCB           Ccb = NULL;
    PVCB                Vcb = NULL;
    PtrUDFNTRequiredFCB NtReqFcb = NULL;
    BOOLEAN             AcquiredVCB = FALSE;
    BOOLEAN             AcquiredFCB = FALSE;
    BOOLEAN             PostRequest = FALSE;
    BOOLEAN             CanWait = TRUE;

    UDFPrint(("UDFCommonFlush: \n"));

    _SEH2_TRY {

        // Get some of the parameters supplied to us
        CanWait = ((PtrIrpContext->IrpContextFlags & UDF_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);
        }

        // First, get a pointer to the current I/O stack location
        IrpSp = IoGetCurrentIrpStackLocation(Irp);
        ASSERT(IrpSp);

        FileObject = IrpSp->FileObject;
        ASSERT(FileObject);

        // Get the FCB and CCB pointers
        Ccb = (PtrUDFCCB)(FileObject->FsContext2);
        ASSERT(Ccb);
        Fcb = Ccb->Fcb;
        ASSERT(Fcb);
        NtReqFcb = Fcb->NTRequiredFCB;

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

            if (Fcb->NodeIdentifier.NodeType == UDF_NODE_TYPE_VCB) {
                Vcb = (PVCB)(Fcb);
            } else {
                Vcb = Fcb->Vcb;
            }
            Vcb->VCBFlags |= UDF_VCB_SKIP_EJECT_CHECK;

#ifdef UDF_DELAYED_CLOSE
            UDFCloseAllDelayed(Vcb);
#endif //UDF_DELAYED_CLOSE

            UDFAcquireResourceExclusive(&(Vcb->VCBResource), TRUE);
            AcquiredVCB = TRUE;
            // 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
            // VCB 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.

            UDFFlushLogicalVolume(PtrIrpContext, Irp, Vcb, 0);

            UDFReleaseResource(&(Vcb->VCBResource));
            AcquiredVCB = FALSE;

            try_return(RC);
        } else
        if (!(Fcb->FCBFlags & UDF_FCB_DIRECTORY)) {
            // This is a regular file.
            Vcb = Fcb->Vcb;
            ASSERT(Vcb);
            if(!ExIsResourceAcquiredExclusiveLite(&(Vcb->VCBResource)) &&
               !ExIsResourceAcquiredSharedLite(&(Vcb->VCBResource))) {
                UDFAcquireResourceShared(&(Vcb->VCBResource), TRUE);
                AcquiredVCB = TRUE;
            }
            UDF_CHECK_PAGING_IO_RESOURCE(NtReqFcb);
            UDFAcquireResourceExclusive(&(NtReqFcb->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.
            UDFFlushAFile(Fcb, Ccb, &(Irp->IoStatus), 0);
            RC = Irp->IoStatus.Status;

            // Some log-based FSD implementations may wish to flush their
            // log files at this time. Finally, we 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.
        } else {
            Vcb = Fcb->Vcb;
        }

try_exit:   NOTHING;

    } _SEH2_FINALLY {

        if (AcquiredFCB) {
            UDF_CHECK_PAGING_IO_RESOURCE(NtReqFcb);
            UDFReleaseResource(&(NtReqFcb->MainResource));
            AcquiredFCB = FALSE;
        }
        if (AcquiredVCB) {
            UDFReleaseResource(&(Vcb->VCBResource));
            AcquiredVCB = FALSE;
        }

        if(!_SEH2_AbnormalTermination()) {
            if (PostRequest) {
                // Nothing to lock now.
                BrutePoint();
                RC = UDFPostRequest(PtrIrpContext, Irp);
            } else {
                // Some applications like this request very much
                // (ex. WinWord). But it's not a good idea for CD-R/RW media
                if(Vcb->FlushMedia) {
                    PIO_STACK_LOCATION      PtrNextIoStackLocation = NULL;
                    NTSTATUS                RC1 = STATUS_SUCCESS;

                    // Send the request down at this point.
                    // To do this, we 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 we do have a completion routine!
                    PtrNextIoStackLocation = IoGetNextIrpStackLocation(Irp);
                    *PtrNextIoStackLocation = *IrpSp;

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

                    RC1 = IoCallDriver(Vcb->TargetDeviceObject, Irp);

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

                    // Release the IRP context at this time.
                    UDFReleaseIrpContext(PtrIrpContext);
                } else {
                    Irp->IoStatus.Status = RC;
                    Irp->IoStatus.Information = 0;
                    // Free up the Irp Context
                    UDFReleaseIrpContext(PtrIrpContext);
                    // complete the IRP
                    IoCompleteRequest(Irp, IO_DISK_INCREMENT);
                }
            }
        }
    } _SEH2_END;

    return(RC);
} // end UDFCommonFlush()


/*************************************************************************
*
* Function: UDFFlushAFile()
*
* Description:
*   Tell the Cache Manager to perform a flush.
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
ULONG
UDFFlushAFile(
    IN PtrUDFFCB           Fcb,
    IN PtrUDFCCB           Ccb,
    OUT PIO_STATUS_BLOCK   PtrIoStatus,
    IN ULONG               FlushFlags
    )
{
    BOOLEAN SetArchive = FALSE;
//    BOOLEAN PurgeCache = FALSE;
    ULONG ret_val = 0;

    UDFPrint(("UDFFlushAFile: \n"));
    if(!Fcb)
        return 0;

    _SEH2_TRY {
        if(Fcb->Vcb->VCBFlags & UDF_VCB_FLAGS_RAW_DISK)
            return 0;
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;
#ifndef UDF_READ_ONLY_BUILD
    // Flush Security if required
    _SEH2_TRY {
        UDFWriteSecurity(Fcb->Vcb, Fcb, &(Fcb->NTRequiredFCB->SecurityDesc));
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;
#endif //UDF_READ_ONLY_BUILD
    // Flush SDir if any
    _SEH2_TRY {
        if(UDFHasAStreamDir(Fcb->FileInfo) &&
           Fcb->FileInfo->Dloc->SDirInfo &&
           !UDFIsSDirDeleted(Fcb->FileInfo->Dloc->SDirInfo) ) {
            ret_val |=
                UDFFlushADirectory(Fcb->Vcb, Fcb->FileInfo->Dloc->SDirInfo, PtrIoStatus, FlushFlags);
        }
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;
    // Flush File
    _SEH2_TRY {
        if((Fcb->CachedOpenHandleCount || !Fcb->OpenHandleCount) &&
            Fcb->NTRequiredFCB->SectionObject.DataSectionObject) {
            if(!(Fcb->NTRequiredFCB->NtReqFCBFlags & UDF_NTREQ_FCB_DELETED)
                                         &&
                ((Fcb->NTRequiredFCB->NtReqFCBFlags & UDF_NTREQ_FCB_MODIFIED) ||
                 (Ccb && !(Ccb->CCBFlags & UDF_CCB_FLUSHED)) )) {
                MmPrint(("    CcFlushCache()\n"));
                CcFlushCache(&(Fcb->NTRequiredFCB->SectionObject), NULL, 0, PtrIoStatus);
            }
            // notice, that we should purge cache
            // we can't do it now, because it may cause last Close
            // request & thus, structure deallocation
//            PurgeCache = TRUE;

#ifndef UDF_READ_ONLY_BUILD
            if(Ccb) {
                if( (Ccb->FileObject->Flags & FO_FILE_MODIFIED) &&
                   !(Ccb->CCBFlags & UDF_CCB_WRITE_TIME_SET)) {
                    if(Fcb->Vcb->CompatFlags & UDF_VCB_IC_UPDATE_MODIFY_TIME) {
                        LONGLONG NtTime;
                        KeQuerySystemTime((PLARGE_INTEGER)&NtTime);
                        UDFSetFileXTime(Fcb->FileInfo, NULL, NULL, NULL, &NtTime);
                        Fcb->NTRequiredFCB->LastWriteTime.QuadPart = NtTime;
                    }
                    SetArchive = TRUE;
                    Ccb->FileObject->Flags &= ~FO_FILE_MODIFIED;
                }
                if(Ccb->FileObject->Flags & FO_FILE_SIZE_CHANGED) {
                    LONGLONG ASize = UDFGetFileAllocationSize(Fcb->Vcb, Fcb->FileInfo);
                    UDFSetFileSizeInDirNdx(Fcb->Vcb, Fcb->FileInfo, &ASize);
                    Ccb->FileObject->Flags &= ~FO_FILE_SIZE_CHANGED;
                }
            }
#endif //UDF_READ_ONLY_BUILD
        }
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;

    _SEH2_TRY {
#ifndef UDF_READ_ONLY_BUILD
        if(SetArchive &&
           (Fcb->Vcb->CompatFlags & UDF_VCB_IC_UPDATE_ARCH_BIT)) {
            ULONG Attr;
            PDIR_INDEX_ITEM DirNdx;
            DirNdx = UDFDirIndex(UDFGetDirIndexByFileInfo(Fcb->FileInfo), Fcb->FileInfo->Index);
            // Archive bit
            Attr = UDFAttributesToNT(DirNdx, Fcb->FileInfo->Dloc->FileEntry);
            if(!(Attr & FILE_ATTRIBUTE_ARCHIVE))
                UDFAttributesToUDF(DirNdx, Fcb->FileInfo->Dloc->FileEntry, Attr | FILE_ATTRIBUTE_ARCHIVE);
        }
#endif //UDF_READ_ONLY_BUILD
        UDFFlushFile__( Fcb->Vcb, Fcb->FileInfo, FlushFlags);
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;

/*    if(PurgeCache) {
        _SEH2_TRY {
            MmPrint(("    CcPurgeCacheSection()\n"));
            CcPurgeCacheSection( &(Fcb->NTRequiredFCB->SectionObject), NULL, 0, FALSE );
        } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
            BrutePoint();
        } _SEH2_END;
    }*/

    return ret_val;
} // end UDFFlushAFile()

/*************************************************************************
*
* Function: UDFFlushADirectory()
*
* Description:
*   Tell the Cache Manager to perform a flush for all files
*   in current directory & all subdirectories and flush all metadata
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
ULONG
UDFFlushADirectory(
    IN PVCB                Vcb,
    IN PUDF_FILE_INFO      FI,
    OUT PIO_STATUS_BLOCK   PtrIoStatus,
    IN ULONG               FlushFlags
    )
{
    UDFPrint(("UDFFlushADirectory: \n"));
//    PDIR_INDEX_HDR hDI;
    PDIR_INDEX_ITEM DI;
//    BOOLEAN Referenced = FALSE;
    ULONG ret_val = 0;

    if(Vcb->VCBFlags & UDF_VCB_FLAGS_RAW_DISK)
        return 0;

    if(!FI || !FI->Dloc || !FI->Dloc->DirIndex) goto SkipFlushDir;
//    hDI = FI->Dloc->DirIndex;

    // Flush Security if required
    _SEH2_TRY {
        UDFWriteSecurity(Vcb, FI->Fcb, &(FI->Fcb->NTRequiredFCB->SecurityDesc));
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;
    // Flush SDir if any
    _SEH2_TRY {
        if(UDFHasAStreamDir(FI) &&
           FI->Dloc->SDirInfo &&
           !UDFIsSDirDeleted(FI->Dloc->SDirInfo) ) {
            ret_val |=
                UDFFlushADirectory(Vcb, FI->Dloc->SDirInfo, PtrIoStatus, FlushFlags);
        }
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;

    // Flush Dir Tree
    _SEH2_TRY {
        UDF_DIR_SCAN_CONTEXT ScanContext;
        PUDF_FILE_INFO      tempFI;

        if(UDFDirIndexInitScan(FI, &ScanContext, 2)) {
            while((DI = UDFDirIndexScan(&ScanContext, &tempFI))) {
                // Flush Dir entry
                _SEH2_TRY {
                    if(!tempFI) continue;
                    if(UDFIsADirectory(tempFI)) {
                        UDFFlushADirectory(Vcb, tempFI, PtrIoStatus, FlushFlags);
                    } else {
                        UDFFlushAFile(tempFI->Fcb, NULL, PtrIoStatus, FlushFlags);
                    }
                } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
                    BrutePoint();
                } _SEH2_END;
                if(UDFFlushIsBreaking(Vcb, FlushFlags)) {
                    ret_val |= UDF_FLUSH_FLAGS_INTERRUPTED;
                    break;
                }
            }
        }
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;
SkipFlushDir:
    // Flush Dir
    _SEH2_TRY {
        UDFFlushFile__( Vcb, FI, FlushFlags );
    } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
        BrutePoint();
    } _SEH2_END;

    return ret_val;
} // end UDFFlushADirectory()

/*************************************************************************
*
* Function: UDFFlushLogicalVolume()
*
* Description:
*   Flush everything beginning from root directory.
*   Vcb must be previously acquired exclusively.
*
* Expected Interrupt Level (for execution) :
*
*  IRQL_PASSIVE_LEVEL
*
* Return Value: None
*
*************************************************************************/
ULONG
UDFFlushLogicalVolume(
    IN PtrUDFIrpContext PtrIrpContext,
    IN PIRP             Irp,
    IN PVCB             Vcb,
    IN ULONG            FlushFlags
    )
{
    ULONG ret_val = 0;
#ifndef UDF_READ_ONLY_BUILD
    IO_STATUS_BLOCK IoStatus;

    UDFPrint(("UDFFlushLogicalVolume: \n"));

    _SEH2_TRY {
        if(Vcb->VCBFlags & (UDF_VCB_FLAGS_RAW_DISK/* |
                            UDF_VCB_FLAGS_MEDIA_READ_ONLY*/))
            return 0;
        if(Vcb->VCBFlags & UDF_VCB_FLAGS_VOLUME_READ_ONLY)
            return 0;
        if(!(Vcb->VCBFlags & UDF_VCB_FLAGS_VOLUME_MOUNTED))
            return 0;

        // NOTE: This function may also be invoked internally as part of
        // processing a shutdown request.
        ASSERT(Vcb->RootDirFCB);
        ret_val |= UDFFlushADirectory(Vcb, Vcb->RootDirFCB->FileInfo, &IoStatus, FlushFlags);

//        if(UDFFlushIsBreaking(Vcb, FlushFlags))
//            return;
        // flush internal cache
        if(FlushFlags & UDF_FLUSH_FLAGS_LITE) {
            UDFPrint(("  Lite flush, keep Modified=%d.\n", Vcb->Modified));
        } else {
            if(Vcb->VerifyOnWrite) {
                UDFPrint(("UDF: Flushing cache for verify\n"));
                //WCacheFlushAll__(&(Vcb->FastCache), Vcb);
                WCacheFlushBlocks__(&(Vcb->FastCache), Vcb, 0, Vcb->LastLBA);
                UDFVFlush(Vcb);
            }
            // umount (this is internal operation, NT will "dismount" volume later)
            UDFUmount__(Vcb);

            UDFPreClrModified(Vcb);
            WCacheFlushAll__(&(Vcb->FastCache), Vcb);
            UDFClrModified(Vcb);
        }

    } _SEH2_FINALLY {
        ;
    } _SEH2_END;
#endif //UDF_READ_ONLY_BUILD

    return ret_val;
} // end UDFFlushLogicalVolume()


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

    UDFPrint(("UDFFlushCompletion: \n"));

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

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

    return(STATUS_SUCCESS);
} // end UDFFlushCompletion()


/*
  Check if we should break FlushTree process
 */
BOOLEAN
UDFFlushIsBreaking(
    IN PVCB         Vcb,
    IN ULONG        FlushFlags
    )
{
    BOOLEAN ret_val = FALSE;
//    if(!(FlushFlags & UDF_FLUSH_FLAGS_BREAKABLE))
        return FALSE;
    UDFAcquireResourceExclusive(&(Vcb->FlushResource),TRUE);
    ret_val = (Vcb->VCBFlags & UDF_VCB_FLAGS_FLUSH_BREAK_REQ) ? TRUE : FALSE;
    Vcb->VCBFlags &= ~UDF_VCB_FLAGS_FLUSH_BREAK_REQ;
    UDFReleaseResource(&(Vcb->FlushResource));
    return ret_val;
} // end UDFFlushIsBreaking()

/*
  Signal FlushTree break request. Note, this is
  treated as recommendation only
 */
VOID
UDFFlushTryBreak(
    IN PVCB         Vcb
    )
{
    UDFAcquireResourceExclusive(&(Vcb->FlushResource),TRUE);
    Vcb->VCBFlags |= UDF_VCB_FLAGS_FLUSH_BREAK_REQ;
    UDFReleaseResource(&(Vcb->FlushResource));
} // end UDFFlushTryBreak()