/*
 * COPYRIGHT:        See COPYRIGHT.TXT
 * PROJECT:          Ext2 File System Driver for WinNT/2K/XP
 * FILE:             cleanup.c
 * PROGRAMMER:       Matt Wu <mattwu@163.com>
 * HOMEPAGE:         http://www.ext2fsd.com
 * UPDATE HISTORY:
 */

/* INCLUDES *****************************************************************/

#include "ext2fs.h"

/* GLOBALS ***************************************************************/

extern PEXT2_GLOBAL Ext2Global;

/* DEFINITIONS *************************************************************/

NTSTATUS
Ext2Cleanup (IN PEXT2_IRP_CONTEXT IrpContext)
{
    PDEVICE_OBJECT  DeviceObject;
    NTSTATUS        Status = STATUS_SUCCESS;
    PEXT2_VCB       Vcb = NULL;
    PFILE_OBJECT    FileObject;
    PEXT2_FCB       Fcb = NULL;
    PEXT2_CCB       Ccb = NULL;
    PIRP            Irp = NULL;
    PEXT2_MCB       Mcb = NULL;


    BOOLEAN         VcbResourceAcquired = FALSE;
    BOOLEAN         FcbResourceAcquired = FALSE;
    BOOLEAN         FcbPagingIoResourceAcquired = FALSE;
    BOOLEAN         SymLinkDelete = FALSE;

    _SEH2_TRY {

        ASSERT(IrpContext != NULL);
        ASSERT((IrpContext->Identifier.Type == EXT2ICX) &&
               (IrpContext->Identifier.Size == sizeof(EXT2_IRP_CONTEXT)));

        DeviceObject = IrpContext->DeviceObject;
        if (IsExt2FsDevice(DeviceObject))  {
            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }

        Irp = IrpContext->Irp;
        Vcb = (PEXT2_VCB) DeviceObject->DeviceExtension;
        ASSERT(Vcb != NULL);
        ASSERT((Vcb->Identifier.Type == EXT2VCB) &&
               (Vcb->Identifier.Size == sizeof(EXT2_VCB)));

        if (!IsVcbInited(Vcb)) {
            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }

        FileObject = IrpContext->FileObject;
        Fcb = (PEXT2_FCB) FileObject->FsContext;
        if (!Fcb || (Fcb->Identifier.Type != EXT2VCB &&
                     Fcb->Identifier.Type != EXT2FCB)) {
            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }
        Mcb = Fcb->Mcb;
        Ccb = (PEXT2_CCB) FileObject->FsContext2;

        if (IsFlagOn(FileObject->Flags, FO_CLEANUP_COMPLETE)) {
            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }

        if (Fcb->Identifier.Type == EXT2VCB) {

            ExAcquireResourceExclusiveLite(
                    &Vcb->MainResource, TRUE);
            VcbResourceAcquired = TRUE;

            if (FlagOn(Vcb->Flags, VCB_VOLUME_LOCKED) &&
                Vcb->LockFile == FileObject ){

                ClearFlag(Vcb->Flags, VCB_VOLUME_LOCKED);
                Vcb->LockFile = NULL;
                Ext2ClearVpbFlag(Vcb->Vpb, VPB_LOCKED);
            }

            if (Ccb) {
                Ext2DerefXcb(&Vcb->OpenHandleCount);
                Ext2DerefXcb(&Vcb->OpenVolumeCount);
            }

            IoRemoveShareAccess(FileObject, &Vcb->ShareAccess);

            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }

        ASSERT((Fcb->Identifier.Type == EXT2FCB) &&
               (Fcb->Identifier.Size == sizeof(EXT2_FCB)));

        FcbResourceAcquired =
            ExAcquireResourceExclusiveLite(
                &Fcb->MainResource,
                TRUE
            );

        if (IsFlagOn(FileObject->Flags, FO_CLEANUP_COMPLETE)) {
            if (IsFlagOn(FileObject->Flags, FO_FILE_MODIFIED) &&
                    IsFlagOn(Vcb->Flags, VCB_FLOPPY_DISK) &&
                    !IsVcbReadOnly(Vcb) ) {
                Status = Ext2FlushFile(IrpContext, Fcb, Ccb);
            }
            _SEH2_LEAVE;
        }

        if (Ccb == NULL) {
            Status = STATUS_SUCCESS;
            _SEH2_LEAVE;
        }

        if (IsDirectory(Fcb)) {
            if (IsFlagOn(Ccb->Flags, CCB_DELETE_ON_CLOSE))  {
                SetLongFlag(Fcb->Flags, FCB_DELETE_PENDING);

                FsRtlNotifyFullChangeDirectory(
                    Vcb->NotifySync,
                    &Vcb->NotifyList,
                    Ccb,
                    NULL,
                    FALSE,
                    FALSE,
                    0,
                    NULL,
                    NULL,
                    NULL );
            }

            FsRtlNotifyCleanup(Vcb->NotifySync, &Vcb->NotifyList, Ccb);
        }

        ASSERT((Ccb->Identifier.Type == EXT2CCB) &&
               (Ccb->Identifier.Size == sizeof(EXT2_CCB)));

        Ext2DerefXcb(&Vcb->OpenHandleCount);
        Ext2DerefXcb(&Fcb->OpenHandleCount);

        if (IsFlagOn(FileObject->Flags, FO_FILE_MODIFIED)) {
            Fcb->Mcb->FileAttr |= FILE_ATTRIBUTE_ARCHIVE;
        }

        if (IsDirectory(Fcb)) {

            ext3_release_dir(Fcb->Inode, &Ccb->filp);

        } else {

            if ( IsFlagOn(FileObject->Flags, FO_FILE_MODIFIED) &&
                    !IsFlagOn(Ccb->Flags, CCB_LAST_WRITE_UPDATED)) {

                LARGE_INTEGER   SysTime;
                KeQuerySystemTime(&SysTime);

                Fcb->Inode->i_atime =
                    Fcb->Inode->i_mtime = Ext2LinuxTime(SysTime);
                Fcb->Mcb->LastAccessTime =
                    Fcb->Mcb->LastWriteTime = Ext2NtTime(Fcb->Inode->i_atime);

                Ext2SaveInode(IrpContext, Vcb, Fcb->Inode);

                Ext2NotifyReportChange(
                    IrpContext,
                    Vcb,
                    Fcb->Mcb,
                    FILE_NOTIFY_CHANGE_ATTRIBUTES |
                    FILE_NOTIFY_CHANGE_LAST_WRITE |
                    FILE_NOTIFY_CHANGE_LAST_ACCESS,
                    FILE_ACTION_MODIFIED );
            }

            FsRtlCheckOplock( &Fcb->Oplock,
                              Irp,
                              IrpContext,
                              NULL,
                              NULL );

            Fcb->Header.IsFastIoPossible = Ext2IsFastIoPossible(Fcb);

            if (!IsFlagOn(FileObject->Flags, FO_CACHE_SUPPORTED)) {
                Fcb->NonCachedOpenCount--;
            }

            if (IsFlagOn(Ccb->Flags, CCB_DELETE_ON_CLOSE))  {
                if (Ccb->SymLink || IsInodeSymLink(&Mcb->Inode)) {
                    SymLinkDelete = TRUE;
                } else {
                    SetLongFlag(Fcb->Flags, FCB_DELETE_PENDING);
                }
            }

            //
            // Drop any byte range locks this process may have on the file.
            //

            FsRtlFastUnlockAll(
                &Fcb->FileLockAnchor,
                FileObject,
                IoGetRequestorProcess(Irp),
                NULL  );

            //
            // If there are no byte range locks owned by other processes on the
            // file the fast I/O read/write functions doesn't have to check for
            // locks so we set IsFastIoPossible to FastIoIsPossible again.
            //
            if (!FsRtlGetNextFileLock(&Fcb->FileLockAnchor, TRUE)) {
                if (Fcb->Header.IsFastIoPossible != FastIoIsPossible) {
#if EXT2_DEBUG
                    DEBUG(DL_INF, (": %-16.16s %-31s %wZ\n",
                                   Ext2GetCurrentProcessName(),
                                   "FastIoIsPossible",
                                   &Fcb->Mcb->FullName
                                  ));
#endif

                    Fcb->Header.IsFastIoPossible = FastIoIsPossible;
                }
            }

            if (Fcb->OpenHandleCount == 0 && FlagOn(Fcb->Flags, FCB_ALLOC_IN_CREATE |
                                                                FCB_ALLOC_IN_SETINFO) ){

                if (FlagOn(Fcb->Flags, FCB_ALLOC_IN_SETINFO)) {
                    if (Fcb->Header.ValidDataLength.QuadPart < Fcb->Header.FileSize.QuadPart) {
                        if (!INODE_HAS_EXTENT(Fcb->Inode)) {
                        #if EXT2_PRE_ALLOCATION_SUPPORT
                            _SEH2_TRY {
                                CcZeroData( FileObject,
                                           &Fcb->Header.ValidDataLength,
                                           &Fcb->Header.AllocationSize,
                                           TRUE);
                            } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
                                DbgBreak();
                            } _SEH2_END;
                        #endif
                        }
                    }
                }

                if (FlagOn(Fcb->Flags, FCB_ALLOC_IN_CREATE)) {

                    LARGE_INTEGER Size;

                    ExAcquireResourceExclusiveLite(&Fcb->PagingIoResource, TRUE);
                    FcbPagingIoResourceAcquired = TRUE;

                    Size.QuadPart = CEILING_ALIGNED(ULONGLONG,
                                                    (ULONGLONG)Fcb->Mcb->Inode.i_size,
                                                    (ULONGLONG)BLOCK_SIZE);
                    if (!IsFlagOn(Fcb->Flags, FCB_DELETE_PENDING)) {

                        Ext2TruncateFile(IrpContext, Vcb, Fcb->Mcb, &Size);
                        Fcb->Header.AllocationSize = Size;
                        Fcb->Header.FileSize.QuadPart = Mcb->Inode.i_size;
                        if (Fcb->Header.ValidDataLength.QuadPart > Fcb->Header.FileSize.QuadPart)
                            Fcb->Header.ValidDataLength.QuadPart = Fcb->Header.FileSize.QuadPart;
                        if (CcIsFileCached(FileObject)) {
                            CcSetFileSizes(FileObject,
                                           (PCC_FILE_SIZES)(&(Fcb->Header.AllocationSize)));
                        }
                    }
                    ClearLongFlag(Fcb->Flags, FCB_ALLOC_IN_CREATE|FCB_ALLOC_IN_WRITE|FCB_ALLOC_IN_SETINFO);
                    ExReleaseResourceLite(&Fcb->PagingIoResource);
                    FcbPagingIoResourceAcquired = FALSE;
                }
            }
        }

        IoRemoveShareAccess(FileObject, &Fcb->ShareAccess);

        if (!IsDirectory(Fcb)) {

            if ( IsFlagOn(FileObject->Flags, FO_CACHE_SUPPORTED) &&
                    (Fcb->NonCachedOpenCount == Fcb->OpenHandleCount) &&
                    (Fcb->SectionObject.DataSectionObject != NULL)) {

                if (!IsVcbReadOnly(Vcb)) {
                    CcFlushCache(&Fcb->SectionObject, NULL, 0, NULL);
                    ClearLongFlag(Fcb->Flags, FCB_FILE_MODIFIED);
                }

                /* purge cache if all remaining openings are non-cached */
                if (Fcb->NonCachedOpenCount > 0 ||
                    IsFlagOn(Fcb->Flags, FCB_DELETE_PENDING)) {
                    if (ExAcquireResourceExclusiveLite(&(Fcb->PagingIoResource), TRUE)) {
                        ExReleaseResourceLite(&(Fcb->PagingIoResource));
                    }

                    /* CcPurge could generate recursive IRP_MJ_CLOSE request */
                    CcPurgeCacheSection( &Fcb->SectionObject,
                                         NULL,
                                         0,
                                         FALSE );
                }
            }

            CcUninitializeCacheMap(FileObject, NULL, NULL);
        }

        if (SymLinkDelete ||
            (IsFlagOn(Fcb->Flags, FCB_DELETE_PENDING) &&
             Fcb->OpenHandleCount == 0) ) {

            //
            // Ext2DeleteFile will acquire these lock inside
            //

            if (FcbResourceAcquired) {
                ExReleaseResourceLite(&Fcb->MainResource);
                FcbResourceAcquired = FALSE;
            }

            //
            //  this file is to be deleted ...
            //
            if (Ccb->SymLink) {
                Mcb = Ccb->SymLink;
                FileObject->DeletePending = FALSE;
            }

            Status = Ext2DeleteFile(IrpContext, Vcb, Fcb, Mcb);

            if (NT_SUCCESS(Status)) {
                if (IsMcbDirectory(Mcb)) {
                    Ext2NotifyReportChange( IrpContext, Vcb, Mcb,
                                            FILE_NOTIFY_CHANGE_DIR_NAME,
                                            FILE_ACTION_REMOVED );
                } else {
                    Ext2NotifyReportChange( IrpContext, Vcb, Mcb,
                                            FILE_NOTIFY_CHANGE_FILE_NAME,
                                            FILE_ACTION_REMOVED );
                }
            }

            //
            // re-acquire the main resource lock
            //

            FcbResourceAcquired =
                ExAcquireResourceExclusiveLite(
                    &Fcb->MainResource,
                    TRUE
                );
            if (!SymLinkDelete) {
                 SetFlag(FileObject->Flags, FO_FILE_MODIFIED);
                if (CcIsFileCached(FileObject)) {
                    CcSetFileSizes(FileObject,
                                   (PCC_FILE_SIZES)(&(Fcb->Header.AllocationSize)));
                }
            }
        }

        DEBUG(DL_INF, ( "Ext2Cleanup: OpenCount=%u ReferCount=%u NonCahcedCount=%xh %wZ\n",
                        Fcb->OpenHandleCount, Fcb->ReferenceCount, Fcb->NonCachedOpenCount, &Fcb->Mcb->FullName));

        Status = STATUS_SUCCESS;

        if (FileObject) {
            SetFlag(FileObject->Flags, FO_CLEANUP_COMPLETE);
        }

    } _SEH2_FINALLY {

        if (FcbPagingIoResourceAcquired) {
            ExReleaseResourceLite(&Fcb->PagingIoResource);
        }

        if (FcbResourceAcquired) {
            ExReleaseResourceLite(&Fcb->MainResource);
        }

        if (VcbResourceAcquired) {
            ExReleaseResourceLite(&Vcb->MainResource);
        }

        if (!IrpContext->ExceptionInProgress) {
            if (Status == STATUS_PENDING) {
                Ext2QueueRequest(IrpContext);
            } else {
                IrpContext->Irp->IoStatus.Status = Status;
                Ext2CompleteIrpContext(IrpContext, Status);
            }
        }
    } _SEH2_END;

    return Status;
}
