Sync with trunk (r48545)
[reactos.git] / drivers / filesystems / fastfat_new / close.c
index 6934698..69ccf3c 100644 (file)
 #define NDEBUG
 #include "fastfat.h"
 
+VOID NTAPI
+FatQueueClose(IN PCLOSE_CONTEXT CloseContext,
+              IN BOOLEAN DelayClose);
+
+PCLOSE_CONTEXT NTAPI
+FatRemoveClose(PVCB Vcb OPTIONAL,
+               PVCB LastVcbHint OPTIONAL);
+
+const ULONG FatMaxDelayedCloseCount = 16;
+
 /* FUNCTIONS ****************************************************************/
 
+NTSTATUS
+NTAPI
+FatiCommonClose(IN PVCB Vcb,
+                IN PFCB Fcb,
+                IN PCCB Ccb,
+                IN TYPE_OF_OPEN TypeOfOpen,
+                IN BOOLEAN Wait,
+                OUT PBOOLEAN VcbDeleted)
+{
+    NTSTATUS Status;
+    PFCB ParentDcb;
+    BOOLEAN RecursiveClose, VcbDeletedLv = FALSE;
+    FAT_IRP_CONTEXT IrpContext;
+
+    if (VcbDeleted) *VcbDeleted = FALSE;
+
+    if (TypeOfOpen == UnopenedFileObject)
+    {
+        DPRINT1("Closing unopened file object\n");
+        Status = STATUS_SUCCESS;
+        return Status;
+    }
+
+    RtlZeroMemory(&IrpContext, sizeof(FAT_IRP_CONTEXT));
+
+    IrpContext.NodeTypeCode = FAT_NTC_IRP_CONTEXT;
+    IrpContext.NodeByteSize = sizeof(IrpContext);
+    IrpContext.MajorFunction = IRP_MJ_CLOSE;
+
+    if (Wait) SetFlag(IrpContext.Flags, IRPCONTEXT_CANWAIT);
+
+    if (!ExAcquireResourceExclusiveLite(&Vcb->Resource, Wait)) return STATUS_PENDING;
+
+    if (Vcb->State & VCB_STATE_FLAG_CLOSE_IN_PROGRESS)
+    {
+        RecursiveClose = TRUE;
+    }
+    else
+    {
+        SetFlag(Vcb->State, VCB_STATE_FLAG_CLOSE_IN_PROGRESS);
+        RecursiveClose = FALSE;
+
+        Vcb->OpenFileCount++;
+    }
+
+    /* Update on-disk structures */
+    switch (TypeOfOpen)
+    {
+    case VirtualVolumeFile:
+        DPRINT1("Close VirtualVolumeFile\n");
+
+        InterlockedDecrement((PLONG)&(Vcb->InternalOpenCount));
+        InterlockedDecrement((PLONG)&(Vcb->ResidualOpenCount));
+
+        Status = STATUS_SUCCESS;
+        goto close_done;
+        break;
+
+    case UserVolumeOpen:
+        DPRINT1("Close UserVolumeOpen\n");
+
+        Vcb->DirectAccessOpenCount--;
+        Vcb->OpenFileCount--;
+        if (FlagOn(Ccb->Flags, CCB_READ_ONLY)) Vcb->ReadOnlyCount--;
+
+        FatDeleteCcb(&IrpContext, Ccb);
+
+        Status = STATUS_SUCCESS;
+        goto close_done;
+        break;
+
+    case EaFile:
+        UNIMPLEMENTED;
+        break;
+
+    case DirectoryFile:
+        DPRINT1("Close DirectoryFile\n");
+
+        InterlockedDecrement((PLONG)&(Fcb->Dcb.DirectoryFileOpenCount));
+        InterlockedDecrement((PLONG)&(Vcb->InternalOpenCount));
+
+        if (FatNodeType(Fcb) == FAT_NTC_ROOT_DCB)
+        {
+            InterlockedDecrement((PLONG)&(Vcb->ResidualOpenCount));
+        }
+
+        if (RecursiveClose)
+        {
+            Status = STATUS_SUCCESS;
+            goto close_done;
+        }
+        else
+        {
+            break;
+        }
+
+    case UserDirectoryOpen:
+    case UserFileOpen:
+        DPRINT("Close UserFileOpen/UserDirectoryOpen\n");
+
+        if ((FatNodeType(Fcb) == FAT_NTC_DCB) &&
+            IsListEmpty(&Fcb->Dcb.ParentDcbList) &&
+            (Fcb->OpenCount == 1) &&
+            (Fcb->Dcb.DirectoryFile != NULL))
+        {
+                PFILE_OBJECT DirectoryFileObject = Fcb->Dcb.DirectoryFile;
+
+                DPRINT1("Uninitialize the stream file object\n");
+
+                CcUninitializeCacheMap(DirectoryFileObject, NULL, NULL);
+
+                Fcb->Dcb.DirectoryFile = NULL;
+                ObDereferenceObject(DirectoryFileObject);
+        }
+
+        Fcb->OpenCount--;
+        Vcb->OpenFileCount--;
+        if (FlagOn(Ccb->Flags, CCB_READ_ONLY)) Vcb->ReadOnlyCount --;
+
+        FatDeleteCcb(&IrpContext, Ccb);
+        break;
+
+    default:
+        KeBugCheckEx(FAT_FILE_SYSTEM, __LINE__, (ULONG_PTR)TypeOfOpen, 0, 0);
+    }
+
+    /* Update in-memory structures */
+    if (((FatNodeType(Fcb) == FAT_NTC_FCB) &&
+        (Fcb->OpenCount == 0))
+        ||
+        ((FatNodeType(Fcb) == FAT_NTC_DCB) &&
+        (IsListEmpty(&Fcb->Dcb.ParentDcbList)) &&
+        (Fcb->OpenCount == 0) &&
+        (Fcb->Dcb.DirectoryFileOpenCount == 0)))
+    {
+        ParentDcb = Fcb->ParentFcb;
+
+        SetFlag(Vcb->State, VCB_STATE_FLAG_DELETED_FCB);
+
+        FatDeleteFcb(&IrpContext, Fcb);
+
+        while ((FatNodeType(ParentDcb) == FAT_NTC_DCB) &&
+            IsListEmpty(&ParentDcb->Dcb.ParentDcbList) &&
+            (ParentDcb->OpenCount == 0) &&
+            (ParentDcb->Dcb.DirectoryFile != NULL))
+        {
+                PFILE_OBJECT DirectoryFileObject;
+
+                DirectoryFileObject = ParentDcb->Dcb.DirectoryFile;
+
+                DPRINT1("Uninitialize parent Stream Cache Map\n");
+
+                CcUninitializeCacheMap(DirectoryFileObject, NULL, NULL);
+
+                ParentDcb->Dcb.DirectoryFile = NULL;
+
+                ObDereferenceObject(DirectoryFileObject);
+
+                if (ParentDcb->Dcb.DirectoryFileOpenCount == 0)
+                {
+                    PFCB CurrentDcb;
+
+                    CurrentDcb = ParentDcb;
+                    ParentDcb = CurrentDcb->ParentFcb;
+
+                    SetFlag(Vcb->State, VCB_STATE_FLAG_DELETED_FCB);
+
+                    FatDeleteFcb(&IrpContext, CurrentDcb);
+                }
+                else
+                {
+                    break;
+                }
+        }
+    }
+
+    Status = STATUS_SUCCESS;
+
+close_done:
+    /* Closing is done, check if VCB could be closed too */
+    if (!RecursiveClose)
+    {
+        /* One open left - yes, VCB can go away */
+        if (Vcb->OpenFileCount == 1 &&
+            !FlagOn(Vcb->State, VCB_STATE_FLAG_DISMOUNT_IN_PROGRESS)
+            && VcbDeleted)
+        {
+            FatReleaseVcb(&IrpContext, Vcb );
+
+            SetFlag(IrpContext.Flags, IRPCONTEXT_CANWAIT);
+
+            FatAcquireExclusiveGlobal(&IrpContext);
+
+            FatAcquireExclusiveVcb(&IrpContext, Vcb);
+
+            Vcb->OpenFileCount--;
+
+            VcbDeletedLv = FatCheckForDismount(&IrpContext, Vcb, FALSE);
+
+            FatReleaseGlobal(&IrpContext);
+
+            if (VcbDeleted) *VcbDeleted = VcbDeletedLv;
+        }
+        else
+        {
+            /* Remove extra referenec */
+            Vcb->OpenFileCount --;
+        }
+
+        /* Clear recursion flag if necessary */
+        if (!VcbDeletedLv)
+        {
+            ClearFlag(Vcb->State, VCB_STATE_FLAG_CLOSE_IN_PROGRESS);
+        }
+    }
+
+    /* Release VCB if it wasn't deleted */
+    if (!VcbDeletedLv)
+        FatReleaseVcb(&IrpContext, Vcb);
+
+    return Status;
+}
+
+NTSTATUS
+NTAPI
+FatiClose(IN PFAT_IRP_CONTEXT IrpContext,
+          IN PIRP Irp)
+{
+    PIO_STACK_LOCATION IrpSp;
+    TYPE_OF_OPEN TypeOfOpen;
+    PVCB Vcb;
+    PFCB Fcb;
+    PCCB Ccb;
+    BOOLEAN TopLevel, Wait, VcbDeleted = FALSE, DelayedClose = FALSE;
+    NTSTATUS Status = STATUS_SUCCESS;
+    PCLOSE_CONTEXT CloseContext = NULL;
+
+    TopLevel = FatIsTopLevelIrp(Irp);
+
+    /* Get current IRP stack location */
+    IrpSp = IoGetCurrentIrpStackLocation(Irp);
+
+    /* Decode incoming file object */
+    TypeOfOpen = FatDecodeFileObject(IrpSp->FileObject, &Vcb, &Fcb, &Ccb);
+
+    /* Set CCB read only flag */
+    if (Ccb && IsFileObjectReadOnly(IrpSp->FileObject))
+        SetFlag(Ccb->Flags, CCB_READ_ONLY);
+
+    /* It's possible to wait only if we are top level or not a system process */
+    Wait = TopLevel && (PsGetCurrentProcess() != FatGlobalData.SystemProcess);
+
+    /* Determine if it's a delayed close, by flags first */
+    if ((TypeOfOpen == UserFileOpen || TypeOfOpen == UserDirectoryOpen) &&
+        (Fcb->State & FCB_STATE_DELAY_CLOSE) &&
+        !FatGlobalData.ShutdownStarted)
+    {
+        DelayedClose = TRUE;
+    }
+
+    /* If close is not delayed, try to perform the close operation */
+    if (!DelayedClose)
+        Status = FatiCommonClose(Vcb, Fcb, Ccb, TypeOfOpen, Wait, &VcbDeleted);
+
+    /* We have to delay close if either it's defined by a flag or it was not possible
+       to perform it synchronously */
+    if (DelayedClose || Status == STATUS_PENDING)
+    {
+        DPRINT1("Queuing a pending close, Vcb %p, Fcb %p, Ccb %p\n", Vcb, Fcb, Ccb);
+
+        /* Check if a close context should be allocated */
+        if (TypeOfOpen == VirtualVolumeFile)
+        {
+            ASSERT(Vcb->CloseContext != NULL);
+            CloseContext = Vcb->CloseContext;
+            Vcb->CloseContext = NULL;
+            CloseContext->Free = TRUE;
+        }
+        else if (TypeOfOpen == DirectoryFile ||
+                 TypeOfOpen == EaFile)
+        {
+            UNIMPLEMENTED;
+            //CloseContext = FatAllocateCloseContext(Vcb);
+            //ASSERT(CloseContext != NULL);
+            CloseContext->Free = TRUE;
+        }
+        else
+        {
+            //TODO: FatDeallocateCcbStrings( Ccb );
+
+            /* Set CloseContext to a buffer inside Ccb */
+            CloseContext = &Ccb->CloseContext;
+            CloseContext->Free = FALSE;
+            SetFlag(Ccb->Flags, CCB_CLOSE_CONTEXT);
+        }
+
+        /* Save all info in the close context */
+        CloseContext->Vcb = Vcb;
+        CloseContext->Fcb = Fcb;
+        CloseContext->TypeOfOpen = TypeOfOpen;
+
+        /* Queue the close */
+        FatQueueClose(CloseContext, (BOOLEAN)(Fcb && FlagOn(Fcb->State, FCB_STATE_DELAY_CLOSE)));
+    }
+    else
+    {
+        /* Close finished right away */
+        if (TypeOfOpen == VirtualVolumeFile ||
+            TypeOfOpen == DirectoryFile ||
+            TypeOfOpen == EaFile)
+        {
+                if (TypeOfOpen == VirtualVolumeFile)
+                {
+                    /* Free close context for the not deleted VCB */
+                    if (!VcbDeleted)
+                    {
+                        CloseContext = Vcb->CloseContext;
+                        Vcb->CloseContext = NULL;
+
+                        ASSERT(CloseContext != NULL);
+                    }
+                }
+                else
+                {
+                    //CloseContext = FatAllocateCloseContext(Vcb);
+                    DPRINT1("TODO: Allocate close context!\n");
+                    ASSERT(CloseContext != NULL);
+                }
+
+                /* Free close context */
+                if (CloseContext) ExFreePool(CloseContext);
+        }
+    }
+
+    /* Complete the request */
+    FatCompleteRequest(NULL, Irp, Status);
+
+    /* Reset the top level IRP if necessary */
+    if (TopLevel) IoSetTopLevelIrp(NULL);
+
+    return Status;
+}
+
 NTSTATUS
 NTAPI
 FatClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
 {
-    DPRINT1("FatClose(DeviceObject %p, Irp %p)\n", DeviceObject, Irp);
+    PFAT_IRP_CONTEXT IrpContext;
+    NTSTATUS Status;
+
+    DPRINT("FatClose(DeviceObject %p, Irp %p)\n", DeviceObject, Irp);
+
+    /* FatClose works only with a volume device object */
+    if (DeviceObject == FatGlobalData.DiskDeviceObject)
+    {
+        /* Complete the request and return success */
+        Irp->IoStatus.Status = STATUS_SUCCESS;
+        Irp->IoStatus.Information = FILE_OPENED;
+
+        IoCompleteRequest(Irp, IO_DISK_INCREMENT);
+
+        return STATUS_SUCCESS;
+    }
+
+    /* Enter FsRtl critical region */
+    FsRtlEnterFileSystem();
+
+    /* Build an irp context */
+    IrpContext = FatBuildIrpContext(Irp, TRUE);
+
+    /* Call internal function */
+    Status = FatiClose(IrpContext, Irp);
+
+    /* Leave FsRtl critical region */
+    FsRtlExitFileSystem();
+
+    return Status;
+}
+
+VOID
+NTAPI
+FatPendingClose(IN PVCB Vcb OPTIONAL)
+{
+    PCLOSE_CONTEXT CloseContext;
+    PVCB CurrentVcb = NULL;
+    PVCB LastVcb = NULL;
+    BOOLEAN FreeContext;
+    ULONG Loops = 0;
+
+    /* Do the top-level IRP trick */
+    if (!Vcb) IoSetTopLevelIrp((PIRP)FSRTL_FSP_TOP_LEVEL_IRP);
+
+    while ((CloseContext = FatRemoveClose(Vcb, LastVcb)))
+    {
+        if (!Vcb)
+        {
+            if (!FatGlobalData.ShutdownStarted)
+            {
+                if (CloseContext->Vcb != CurrentVcb)
+                {
+                    Loops = 0;
+
+                    /* Release previous VCB */
+                    if (CurrentVcb)
+                        ExReleaseResourceLite(&CurrentVcb->Resource);
+
+                    /* Lock the new VCB */
+                    CurrentVcb = CloseContext->Vcb;
+                    (VOID)ExAcquireResourceExclusiveLite(&CurrentVcb->Resource, TRUE);
+                }
+                else
+                {
+                    /* Try to lock */
+                    if (++Loops >= 20)
+                    {
+                        if (ExGetSharedWaiterCount(&CurrentVcb->Resource) +
+                            ExGetExclusiveWaiterCount(&CurrentVcb->Resource))
+                        {
+                            ExReleaseResourceLite(&CurrentVcb->Resource);
+                            (VOID)ExAcquireResourceExclusiveLite(&CurrentVcb->Resource, TRUE);
+                        }
+
+                        Loops = 0;
+                    }
+                }
+
+                /* Check open count */
+                if (CurrentVcb->OpenFileCount <= 1)
+                {
+                    ExReleaseResourceLite(&CurrentVcb->Resource);
+                    CurrentVcb = NULL;
+                }
+            }
+            else if (CurrentVcb)
+            {
+                ExReleaseResourceLite(&CurrentVcb->Resource);
+                CurrentVcb = NULL;
+            }
+        }
+
+        LastVcb = CurrentVcb;
+
+        /* Remember if we should free the context */
+        FreeContext = CloseContext->Free;
+
+        FatiCommonClose(CloseContext->Vcb,
+                        CloseContext->Fcb,
+                        (FreeContext ? NULL : CONTAINING_RECORD(CloseContext, CCB, CloseContext)),
+                        CloseContext->TypeOfOpen,
+                        TRUE,
+                        NULL);
+
+        /* Free context if necessary */
+        if (FreeContext) ExFreePool(CloseContext);
+    }
+
+    /* Release VCB if necessary */
+    if (CurrentVcb) ExReleaseResourceLite(&CurrentVcb->Resource);
+
+    /* Reset top level IRP */
+    if (!Vcb) IoSetTopLevelIrp( NULL );
+}
+
+VOID
+NTAPI
+FatCloseWorker(IN PDEVICE_OBJECT DeviceObject,
+               IN PVOID Context)
+{
+    FsRtlEnterFileSystem();
+
+    FatPendingClose((PVCB)Context);
+
+    FsRtlExitFileSystem();
+}
+
+VOID
+NTAPI
+FatQueueClose(IN PCLOSE_CONTEXT CloseContext,
+              IN BOOLEAN DelayClose)
+{
+    BOOLEAN RunWorker = FALSE;
+
+    /* Acquire the close lists mutex */
+    ExAcquireFastMutexUnsafe(&FatCloseQueueMutex);
+
+    /* Add it to the desired list */
+    if (DelayClose)
+    {
+        InsertTailList(&FatGlobalData.DelayedCloseList,
+                       &CloseContext->GlobalLinks);
+        InsertTailList(&CloseContext->Vcb->DelayedCloseList,
+                       &CloseContext->VcbLinks);
+
+        FatGlobalData.DelayedCloseCount++;
+
+        if (FatGlobalData.DelayedCloseCount > FatMaxDelayedCloseCount &&
+            !FatGlobalData.AsyncCloseActive)
+        {
+            FatGlobalData.AsyncCloseActive = TRUE;
+            RunWorker = TRUE;
+        }
+    }
+    else
+    {
+        InsertTailList(&FatGlobalData.AsyncCloseList,
+                       &CloseContext->GlobalLinks);
+        InsertTailList(&CloseContext->Vcb->AsyncCloseList,
+                       &CloseContext->VcbLinks);
+
+        FatGlobalData.AsyncCloseCount++;
+
+        if (!FatGlobalData.AsyncCloseActive)
+        {
+            FatGlobalData.AsyncCloseActive = TRUE;
+            RunWorker = TRUE;
+        }
+    }
+
+    /* Release the close lists mutex */
+    ExReleaseFastMutexUnsafe(&FatCloseQueueMutex);
+
+    if (RunWorker)
+        IoQueueWorkItem(FatGlobalData.FatCloseItem, FatCloseWorker, CriticalWorkQueue, NULL);
+}
+
+PCLOSE_CONTEXT
+NTAPI
+FatRemoveClose(PVCB Vcb OPTIONAL,
+               PVCB LastVcbHint OPTIONAL)
+{
+    PLIST_ENTRY Entry;
+    PCLOSE_CONTEXT CloseContext;
+    BOOLEAN IsWorker = FALSE;
+
+    /* Acquire the close lists mutex */
+    ExAcquireFastMutexUnsafe(&FatCloseQueueMutex);
+
+    if (!Vcb) IsWorker = TRUE;
+
+    if (Vcb == NULL && LastVcbHint != NULL)
+    {
+        // TODO: A very special case of overflowing the queue
+        UNIMPLEMENTED;
+    }
+
+    /* Usual processing from a worker thread */
+    if (!Vcb)
+    {
+TryToCloseAgain:
+
+        /* Is there anything in the async close list */
+        if (!IsListEmpty(&FatGlobalData.AsyncCloseList))
+        {
+            Entry = RemoveHeadList(&FatGlobalData.AsyncCloseList);
+            FatGlobalData.AsyncCloseCount--;
+
+            CloseContext = CONTAINING_RECORD(Entry,
+                                             CLOSE_CONTEXT,
+                                             GlobalLinks);
+
+            RemoveEntryList(&CloseContext->VcbLinks);
+        } else if (!IsListEmpty(&FatGlobalData.DelayedCloseList) &&
+                   (FatGlobalData.DelayedCloseCount > FatMaxDelayedCloseCount/2 ||
+                   FatGlobalData.ShutdownStarted))
+        {
+            /* In case of a shutdown or when delayed queue is filled at half - perform closing */
+            Entry = RemoveHeadList(&FatGlobalData.DelayedCloseList);
+            FatGlobalData.DelayedCloseCount--;
+
+            CloseContext = CONTAINING_RECORD(Entry,
+                                             CLOSE_CONTEXT,
+                                             GlobalLinks);
+            RemoveEntryList(&CloseContext->VcbLinks);
+        }
+        else
+        {
+            /* Nothing to close */
+            CloseContext = NULL;
+            if (IsWorker) FatGlobalData.AsyncCloseActive = FALSE;
+        }
+    }
+    else
+    {
+        if (!IsListEmpty(&Vcb->AsyncCloseList))
+        {
+            /* Is there anything in the async close list */
+            Entry = RemoveHeadList(&Vcb->AsyncCloseList);
+            FatGlobalData.AsyncCloseCount--;
+
+            CloseContext = CONTAINING_RECORD(Entry,
+                                             CLOSE_CONTEXT,
+                                             VcbLinks);
+
+            RemoveEntryList(&CloseContext->GlobalLinks);
+        }
+        else if (!IsListEmpty(&Vcb->DelayedCloseList))
+        {
+            /* Process delayed close list */
+            Entry = RemoveHeadList(&Vcb->DelayedCloseList);
+            FatGlobalData.DelayedCloseCount--;
+
+            CloseContext = CONTAINING_RECORD(Entry,
+                                             CLOSE_CONTEXT,
+                                             VcbLinks);
+
+            RemoveEntryList(&CloseContext->GlobalLinks);
+        }
+        else if (LastVcbHint)
+        {
+            /* Try again */
+            goto TryToCloseAgain;
+        }
+        else
+        {
+            /* Nothing to close */
+            CloseContext = NULL;
+        }
+    }
+
+    /* Release the close lists mutex */
+    ExReleaseFastMutexUnsafe(&FatCloseQueueMutex);
 
-    return STATUS_NOT_IMPLEMENTED;
+    return CloseContext;
 }
 
 /* EOF */