[NTOS:KE/x64] Handle NMI vs swapgs race condition
[reactos.git] / ntoskrnl / cc / fs.c
index 5fb3ca4..14e3a41 100644 (file)
 /* INCLUDES ******************************************************************/
 
 #include <ntoskrnl.h>
+
 #define NDEBUG
 #include <debug.h>
 
-#ifndef VACB_MAPPING_GRANULARITY
-#define VACB_MAPPING_GRANULARITY (256 * 1024)
-#endif
-
-/* GLOBALS   *****************************************************************/
-
-extern KGUARDED_MUTEX ViewLock;
-extern ULONG DirtyPageCount;
-
-NTSTATUS CcRosInternalFreeVacb(PROS_VACB Vacb);
-
 /* FUNCTIONS *****************************************************************/
 
 /*
@@ -55,7 +45,7 @@ NTAPI
 CcGetFileObjectFromBcb (
     IN PVOID Bcb)
 {
-    PINTERNAL_BCB iBcb = (PINTERNAL_BCB)Bcb;
+    PINTERNAL_BCB iBcb = CONTAINING_RECORD(Bcb, INTERNAL_BCB, PFCB);
 
     CCTRACE(CC_API_DEBUG, "Bcb=%p\n", Bcb);
 
@@ -103,6 +93,7 @@ CcInitializeCacheMap (
     /* Call old ROS cache init function */
     Status = CcRosInitializeFileCache(FileObject,
                                       FileSizes,
+                                      PinAccess,
                                       CallBacks,
                                       LazyWriterContext);
     if (!NT_SUCCESS(Status))
@@ -110,17 +101,52 @@ CcInitializeCacheMap (
 }
 
 /*
- * @unimplemented
+ * @implemented
  */
 BOOLEAN
 NTAPI
 CcIsThereDirtyData (
     IN PVPB Vpb)
 {
+    PROS_VACB Vacb;
+    PLIST_ENTRY Entry;
+    KIRQL oldIrql;
+    /* Assume no dirty data */
+    BOOLEAN Dirty = FALSE;
+
     CCTRACE(CC_API_DEBUG, "Vpb=%p\n", Vpb);
 
-    UNIMPLEMENTED;
-    return FALSE;
+    oldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
+
+    /* Browse dirty VACBs */
+    for (Entry = DirtyVacbListHead.Flink; Entry != &DirtyVacbListHead; Entry = Entry->Flink)
+    {
+        Vacb = CONTAINING_RECORD(Entry, ROS_VACB, DirtyVacbListEntry);
+        /* Look for these associated with our volume */
+        if (Vacb->SharedCacheMap->FileObject->Vpb != Vpb)
+        {
+            continue;
+        }
+
+        /* From now on, we are associated with our VPB */
+
+        /* Temporary files are not counted as dirty */
+        if (BooleanFlagOn(Vacb->SharedCacheMap->FileObject->Flags, FO_TEMPORARY_FILE))
+        {
+            continue;
+        }
+
+        /* A single dirty VACB is enough to have dirty data */
+        if (Vacb->Dirty)
+        {
+            Dirty = TRUE;
+            break;
+        }
+    }
+
+    KeReleaseQueuedSpinLock(LockQueueMasterLock, oldIrql);
+
+    return Dirty;
 }
 
 /*
@@ -134,11 +160,131 @@ CcPurgeCacheSection (
     IN ULONG Length,
     IN BOOLEAN UninitializeCacheMaps)
 {
+    PROS_SHARED_CACHE_MAP SharedCacheMap;
+    PPRIVATE_CACHE_MAP PrivateCacheMap;
+    LONGLONG StartOffset;
+    LONGLONG EndOffset;
+    LIST_ENTRY FreeList;
+    KIRQL OldIrql;
+    PLIST_ENTRY ListEntry;
+    PROS_VACB Vacb;
+    LONGLONG ViewEnd;
+    BOOLEAN Success;
+
     CCTRACE(CC_API_DEBUG, "SectionObjectPointer=%p\n FileOffset=%p Length=%lu UninitializeCacheMaps=%d",
         SectionObjectPointer, FileOffset, Length, UninitializeCacheMaps);
 
-    //UNIMPLEMENTED;
-    return FALSE;
+    /* Obtain the shared cache from the section */
+    SharedCacheMap = SectionObjectPointer->SharedCacheMap;
+    if (!SharedCacheMap)
+    {
+        Success = TRUE;
+        goto purgeMm;
+    }
+
+    if (UninitializeCacheMaps)
+    {
+        /*
+         * We have gotten the acknowledgement that
+         * the caller wants to unintialize the private
+         * cache maps so let's do this. Since we already
+         * have the shared cache map from above, iterate
+         * over that cache's private lists.
+         */
+        while (!IsListEmpty(&SharedCacheMap->PrivateList))
+        {
+            /*
+             * This list is not empty, grab the
+             * private cache map.
+             */
+            PrivateCacheMap = CONTAINING_RECORD(SharedCacheMap->PrivateList.Flink, PRIVATE_CACHE_MAP, PrivateLinks);
+
+            /* Unintialize the private cache now */
+            CcUninitializeCacheMap(PrivateCacheMap->FileObject, NULL, NULL);
+        }
+    }
+
+    StartOffset = FileOffset != NULL ? FileOffset->QuadPart : 0;
+    if (Length == 0 || FileOffset == NULL)
+    {
+        EndOffset = MAXLONGLONG;
+    }
+    else
+    {
+        EndOffset = StartOffset + Length;
+        ASSERT(EndOffset > StartOffset);
+    }
+
+    InitializeListHead(&FreeList);
+
+    /* Assume success */
+    Success = TRUE;
+
+    OldIrql = KeAcquireQueuedSpinLock(LockQueueMasterLock);
+    KeAcquireSpinLockAtDpcLevel(&SharedCacheMap->CacheMapLock);
+    ListEntry = SharedCacheMap->CacheMapVacbListHead.Flink;
+    while (ListEntry != &SharedCacheMap->CacheMapVacbListHead)
+    {
+        ULONG Refs;
+
+        Vacb = CONTAINING_RECORD(ListEntry, ROS_VACB, CacheMapVacbListEntry);
+        ListEntry = ListEntry->Flink;
+
+        /* Skip VACBs outside the range, or only partially in range */
+        if (Vacb->FileOffset.QuadPart < StartOffset)
+        {
+            continue;
+        }
+        ViewEnd = min(Vacb->FileOffset.QuadPart + VACB_MAPPING_GRANULARITY,
+                      SharedCacheMap->SectionSize.QuadPart);
+        if (ViewEnd >= EndOffset)
+        {
+            break;
+        }
+
+        /* Still in use, it cannot be purged, fail
+         * Allow one ref: VACB is supposed to be always 1-referenced
+         */
+        Refs = CcRosVacbGetRefCount(Vacb);
+        if ((Refs > 1 && !Vacb->Dirty) ||
+            (Refs > 2 && Vacb->Dirty))
+        {
+            Success = FALSE;
+            break;
+        }
+
+        /* This VACB is in range, so unlink it and mark for free */
+        ASSERT(Refs == 1 || Vacb->Dirty);
+        RemoveEntryList(&Vacb->VacbLruListEntry);
+        InitializeListHead(&Vacb->VacbLruListEntry);
+        if (Vacb->Dirty)
+        {
+            CcRosUnmarkDirtyVacb(Vacb, FALSE);
+        }
+        RemoveEntryList(&Vacb->CacheMapVacbListEntry);
+        InsertHeadList(&FreeList, &Vacb->CacheMapVacbListEntry);
+    }
+    KeReleaseSpinLockFromDpcLevel(&SharedCacheMap->CacheMapLock);
+    KeReleaseQueuedSpinLock(LockQueueMasterLock, OldIrql);
+
+    while (!IsListEmpty(&FreeList))
+    {
+        ULONG Refs;
+
+        Vacb = CONTAINING_RECORD(RemoveHeadList(&FreeList),
+                                 ROS_VACB,
+                                 CacheMapVacbListEntry);
+        InitializeListHead(&Vacb->CacheMapVacbListEntry);
+        Refs = CcRosVacbDecRefCount(Vacb);
+        ASSERT(Refs == 0);
+    }
+
+    /* Now make sure that Mm doesn't hold some pages here. */
+purgeMm:
+    if (Success)
+        Success = MmPurgeSegment(SectionObjectPointer, FileOffset, Length);
+
+    return Success;
 }
 
 
@@ -150,12 +296,9 @@ CcSetFileSizes (
     IN PFILE_OBJECT FileObject,
     IN PCC_FILE_SIZES FileSizes)
 {
-    KIRQL oldirql;
+    KIRQL OldIrql;
     PROS_SHARED_CACHE_MAP SharedCacheMap;
-    PLIST_ENTRY current_entry;
-    PROS_VACB current;
-    LIST_ENTRY FreeListHead;
-    NTSTATUS Status;
+    LARGE_INTEGER OldSectionSize;
 
     CCTRACE(CC_API_DEBUG, "FileObject=%p FileSizes=%p\n",
         FileObject, FileSizes);
@@ -176,64 +319,25 @@ CcSetFileSizes (
     if (SharedCacheMap == NULL)
         return;
 
-    if (FileSizes->AllocationSize.QuadPart < SharedCacheMap->SectionSize.QuadPart)
-    {
-        InitializeListHead(&FreeListHead);
-        KeAcquireGuardedMutex(&ViewLock);
-        KeAcquireSpinLock(&SharedCacheMap->CacheMapLock, &oldirql);
-
-        current_entry = SharedCacheMap->CacheMapVacbListHead.Flink;
-        while (current_entry != &SharedCacheMap->CacheMapVacbListHead)
-        {
-            current = CONTAINING_RECORD(current_entry,
-                                        ROS_VACB,
-                                        CacheMapVacbListEntry);
-            current_entry = current_entry->Flink;
-            if (current->FileOffset.QuadPart >= FileSizes->AllocationSize.QuadPart)
-            {
-                if ((current->ReferenceCount == 0) || ((current->ReferenceCount == 1) && current->Dirty))
-                {
-                    RemoveEntryList(&current->CacheMapVacbListEntry);
-                    RemoveEntryList(&current->VacbLruListEntry);
-                    if (current->Dirty)
-                    {
-                        RemoveEntryList(&current->DirtyVacbListEntry);
-                        DirtyPageCount -= VACB_MAPPING_GRANULARITY / PAGE_SIZE;
-                    }
-                    InsertHeadList(&FreeListHead, &current->CacheMapVacbListEntry);
-                }
-                else
-                {
-                    DPRINT1("Someone has referenced a VACB behind the new size.\n");
-                    KeBugCheck(CACHE_MANAGER);
-                }
-            }
-        }
-
-        SharedCacheMap->SectionSize = FileSizes->AllocationSize;
-        SharedCacheMap->FileSize = FileSizes->FileSize;
-        KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, oldirql);
-        KeReleaseGuardedMutex(&ViewLock);
+    /* Update the relevant fields */
+    KeAcquireSpinLock(&SharedCacheMap->CacheMapLock, &OldIrql);
+    OldSectionSize = SharedCacheMap->SectionSize;
+    SharedCacheMap->SectionSize = FileSizes->AllocationSize;
+    SharedCacheMap->FileSize = FileSizes->FileSize;
+    SharedCacheMap->ValidDataLength = FileSizes->ValidDataLength;
+    KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, OldIrql);
 
-        current_entry = FreeListHead.Flink;
-        while(current_entry != &FreeListHead)
-        {
-            current = CONTAINING_RECORD(current_entry, ROS_VACB, CacheMapVacbListEntry);
-            current_entry = current_entry->Flink;
-            Status = CcRosInternalFreeVacb(current);
-            if (!NT_SUCCESS(Status))
-            {
-                DPRINT1("CcRosInternalFreeVacb failed, status = %x\n", Status);
-                KeBugCheck(CACHE_MANAGER);
-            }
-        }
+    if (FileSizes->AllocationSize.QuadPart < OldSectionSize.QuadPart)
+    {
+        CcPurgeCacheSection(FileObject->SectionObjectPointer,
+                            &FileSizes->AllocationSize,
+                            0,
+                            FALSE);
     }
     else
     {
-        KeAcquireSpinLock(&SharedCacheMap->CacheMapLock, &oldirql);
-        SharedCacheMap->SectionSize = FileSizes->AllocationSize;
-        SharedCacheMap->FileSize = FileSizes->FileSize;
-        KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, oldirql);
+        /* Extend our section object */
+        MmExtendSection(SharedCacheMap->Section, &SharedCacheMap->SectionSize);
     }
 }
 
@@ -264,13 +368,33 @@ CcUninitializeCacheMap (
     IN PCACHE_UNINITIALIZE_EVENT UninitializeCompleteEvent OPTIONAL)
 {
     NTSTATUS Status;
+    PROS_SHARED_CACHE_MAP SharedCacheMap;
+    KIRQL OldIrql;
 
     CCTRACE(CC_API_DEBUG, "FileObject=%p TruncateSize=%p UninitializeCompleteEvent=%p\n",
         FileObject, TruncateSize, UninitializeCompleteEvent);
 
+    if (TruncateSize != NULL &&
+        FileObject->SectionObjectPointer->SharedCacheMap != NULL)
+    {
+        SharedCacheMap = FileObject->SectionObjectPointer->SharedCacheMap;
+        KeAcquireSpinLock(&SharedCacheMap->CacheMapLock, &OldIrql);
+        if (SharedCacheMap->FileSize.QuadPart > TruncateSize->QuadPart)
+        {
+            SharedCacheMap->FileSize = *TruncateSize;
+        }
+        KeReleaseSpinLock(&SharedCacheMap->CacheMapLock, OldIrql);
+        CcPurgeCacheSection(FileObject->SectionObjectPointer,
+                            TruncateSize,
+                            0,
+                            FALSE);
+    }
+
     Status = CcRosReleaseFileCache(FileObject);
     if (UninitializeCompleteEvent)
+    {
         KeSetEvent(&UninitializeCompleteEvent->Event, IO_NO_INCREMENT, FALSE);
+    }
     return NT_SUCCESS(Status);
 }