/*
 * Copyright (C) 2002-2005 ReactOS Team (and the authors from the programmers section)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *
 * PROJECT:         ReactOS kernel
 * FILE:            ntoskrnl/mm/anonmem.c
 * PURPOSE:         Implementing anonymous memory.
 *
 * PROGRAMMERS:     David Welch
 *                  Casper Hornstrup
 *                  KJK::Hyperion
 *                  Ge van Geldorp
 *                  Eric Kohl
 *                  Royce Mitchell III
 *                  Aleksey Bragin
 *                  Jason Filby
 *                  Art Yerkes
 *                  Gunnar Andre' Dalsnes
 *                  Filip Navara
 *                  Thomas Weidenmueller
 *                  Alex Ionescu
 *                  Trevor McCort
 *                  Steven Edwards
 */

/* INCLUDE *****************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

#define MODULE_INVOLVED_IN_ARM3
#include "ARM3/miarm.h"

/* FUNCTIONS *****************************************************************/

NTSTATUS
NTAPI
MmPageOutVirtualMemory(PMMSUPPORT AddressSpace,
    PMEMORY_AREA MemoryArea,
    PVOID Address,
    PMM_PAGEOP PageOp)
{
    PFN_NUMBER Page;
    BOOLEAN WasDirty;
    SWAPENTRY SwapEntry;
    NTSTATUS Status;
    PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

    DPRINT("MmPageOutVirtualMemory(Address 0x%.8X) PID %d\n",
        Address, Process->UniqueProcessId);

    /*
    * Check for paging out from a deleted virtual memory area.
    */
    if (MemoryArea->DeleteInProgress)
    {
        PageOp->Status = STATUS_UNSUCCESSFUL;
        KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
        MmReleasePageOp(PageOp);
        return(STATUS_UNSUCCESSFUL);
    }

    /*
     * Check the reference count to ensure this page can be paged out
     */
    Page = MmGetPfnForProcess(Process, Address);
    if (MmGetReferenceCountPage(Page) != 1)
    {
        DPRINT("Cannot page out locked virtual memory page: 0x%p (RefCount: %d)\n",
                Page, MmGetReferenceCountPage(Page));
        PageOp->Status = STATUS_UNSUCCESSFUL;
        KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
        MmReleasePageOp(PageOp);
        return(STATUS_UNSUCCESSFUL);
    }

    /*
    * Disable the virtual mapping.
    */
    MmDisableVirtualMapping(Process, Address,
        &WasDirty, &Page);

    if (Page == 0)
    {
        KeBugCheck(MEMORY_MANAGEMENT);
    }

    /*
    * Paging out non-dirty data is easy.
    */
    if (!WasDirty)
    {
        MmLockAddressSpace(AddressSpace);
        MmDeleteVirtualMapping(Process, Address, FALSE, NULL, NULL);
        MmDeleteAllRmaps(Page, NULL, NULL);
        if ((SwapEntry = MmGetSavedSwapEntryPage(Page)) != 0)
        {
            MmCreatePageFileMapping(Process, Address, SwapEntry);
            MmSetSavedSwapEntryPage(Page, 0);
        }
        MmUnlockAddressSpace(AddressSpace);
        MmReleasePageMemoryConsumer(MC_USER, Page);
        PageOp->Status = STATUS_SUCCESS;
        KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
        MmReleasePageOp(PageOp);
        return(STATUS_SUCCESS);
    }

    /*
    * If necessary, allocate an entry in the paging file for this page
    */
    SwapEntry = MmGetSavedSwapEntryPage(Page);
    if (SwapEntry == 0)
    {
        SwapEntry = MmAllocSwapPage();
        if (SwapEntry == 0)
        {
            MmShowOutOfSpaceMessagePagingFile();
            MmEnableVirtualMapping(Process, Address);
            PageOp->Status = STATUS_UNSUCCESSFUL;
            KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
            MmReleasePageOp(PageOp);
            return(STATUS_PAGEFILE_QUOTA);
        }
    }

    /*
    * Write the page to the pagefile
    */
    Status = MmWriteToSwapPage(SwapEntry, Page);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("MM: Failed to write to swap page (Status was 0x%.8X)\n",
            Status);
        MmEnableVirtualMapping(Process, Address);
        PageOp->Status = STATUS_UNSUCCESSFUL;
        KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
        MmReleasePageOp(PageOp);
        return(STATUS_UNSUCCESSFUL);
    }

    /*
    * Otherwise we have succeeded, free the page
    */
    DPRINT("MM: Swapped out virtual memory page 0x%.8X!\n", Page << PAGE_SHIFT);
    MmLockAddressSpace(AddressSpace);
    MmDeleteVirtualMapping(Process, Address, FALSE, NULL, NULL);
    MmCreatePageFileMapping(Process, Address, SwapEntry);
    MmUnlockAddressSpace(AddressSpace);
    MmDeleteAllRmaps(Page, NULL, NULL);
    MmSetSavedSwapEntryPage(Page, 0);
    MmReleasePageMemoryConsumer(MC_USER, Page);
    PageOp->Status = STATUS_SUCCESS;
    KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
    MmReleasePageOp(PageOp);
    return(STATUS_SUCCESS);
}

NTSTATUS
NTAPI
MmNotPresentFaultVirtualMemory(PMMSUPPORT AddressSpace,
    MEMORY_AREA* MemoryArea,
    PVOID Address)
/*
* FUNCTION: Move data into memory to satisfy a page not present fault
* ARGUMENTS:
*      AddressSpace = Address space within which the fault occurred
*      MemoryArea = The memory area within which the fault occurred
*      Address = The absolute address of fault
* RETURNS: Status
* NOTES: This function is called with the address space lock held.
*/
{
    PFN_NUMBER Page;
    NTSTATUS Status;
    PMM_REGION Region;
    PMM_PAGEOP PageOp;
    PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

    /*
    * There is a window between taking the page fault and locking the
    * address space when another thread could load the page so we check
    * that.
    */
    if (MmIsPagePresent(Process, Address))
    {
        return(STATUS_SUCCESS);
    }

    /*
    * Check for the virtual memory area being deleted.
    */
    if (MemoryArea->DeleteInProgress)
    {
        return(STATUS_UNSUCCESSFUL);
    }

    /*
    * Get the segment corresponding to the virtual address
    */
    Region = MmFindRegion(MemoryArea->StartingAddress,
        &MemoryArea->Data.VirtualMemoryData.RegionListHead,
        Address, NULL);

    if (Region->Type == MEM_RESERVE || Region->Protect == PAGE_NOACCESS)
    {
        return(STATUS_ACCESS_VIOLATION);
    }

    /*
    * FIXME
    */
    if (Region->Protect & PAGE_GUARD)
    {
        return(STATUS_GUARD_PAGE_VIOLATION);
    }

    /*
    * Get or create a page operation
    */
    PageOp = MmGetPageOp(MemoryArea, Process->UniqueProcessId,
        (PVOID)PAGE_ROUND_DOWN(Address), NULL, 0,
        MM_PAGEOP_PAGEIN, FALSE);
    if (PageOp == NULL)
    {
        DPRINT1("MmGetPageOp failed");
        KeBugCheck(MEMORY_MANAGEMENT);
    }

    /*
    * Check if someone else is already handling this fault, if so wait
    * for them
    */
    if (PageOp->Thread != PsGetCurrentThread())
    {
        MmUnlockAddressSpace(AddressSpace);
        Status = KeWaitForSingleObject(&PageOp->CompletionEvent,
            0,
            KernelMode,
            FALSE,
            NULL);
        /*
    * Check for various strange conditions
    */
        if (Status != STATUS_SUCCESS)
        {
            DPRINT1("Failed to wait for page op\n");
            KeBugCheck(MEMORY_MANAGEMENT);
        }
        if (PageOp->Status == STATUS_PENDING)
        {
            DPRINT1("Woke for page op before completion\n");
            KeBugCheck(MEMORY_MANAGEMENT);
        }
        /*
    * If this wasn't a pagein then we need to restart the handling
    */
        if (PageOp->OpType != MM_PAGEOP_PAGEIN)
        {
            MmLockAddressSpace(AddressSpace);
            KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
            MmReleasePageOp(PageOp);
            return(STATUS_MM_RESTART_OPERATION);
        }
        /*
    * If the thread handling this fault has failed then we don't retry
    */
        if (!NT_SUCCESS(PageOp->Status))
        {
            MmLockAddressSpace(AddressSpace);
            KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
            Status = PageOp->Status;
            MmReleasePageOp(PageOp);
            return(Status);
        }
        MmLockAddressSpace(AddressSpace);
        KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
        MmReleasePageOp(PageOp);
        return(STATUS_SUCCESS);
    }

    /*
    * Try to allocate a page
    */
    MI_SET_USAGE(MI_USAGE_VAD);
    MI_SET_PROCESS2(Process->ImageFileName);
    Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Page);
    if (Status == STATUS_NO_MEMORY)
    {
        MmUnlockAddressSpace(AddressSpace);
        Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
        MmLockAddressSpace(AddressSpace);
    }
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("MmRequestPageMemoryConsumer failed, status = %x\n", Status);
        KeBugCheck(MEMORY_MANAGEMENT);
    }

    /*
    * Handle swapped out pages.
    */
    if (MmIsPageSwapEntry(Process, Address))
    {
        SWAPENTRY SwapEntry;

        MmDeletePageFileMapping(Process, Address, &SwapEntry);
        Status = MmReadFromSwapPage(SwapEntry, Page);
        if (!NT_SUCCESS(Status))
        {
            KeBugCheck(MEMORY_MANAGEMENT);
        }
        MmSetSavedSwapEntryPage(Page, SwapEntry);
    }

    /*
    * Set the page. If we fail because we are out of memory then
    * try again
    */
    Status = MmCreateVirtualMapping(Process,
        (PVOID)PAGE_ROUND_DOWN(Address),
        Region->Protect,
        &Page,
        1);
    while (Status == STATUS_NO_MEMORY)
    {
        MmUnlockAddressSpace(AddressSpace);
        Status = MmCreateVirtualMapping(Process,
            (PVOID)PAGE_ROUND_DOWN(Address),
            Region->Protect,
            &Page,
            1);
        MmLockAddressSpace(AddressSpace);
    }
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
        KeBugCheck(MEMORY_MANAGEMENT);
        return(Status);
    }

    /*
    * Add the page to the process's working set
    */
    MmInsertRmap(Page, Process, (PVOID)PAGE_ROUND_DOWN(Address));

    /*
    * Finish the operation
    */
    PageOp->Status = STATUS_SUCCESS;
    KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
    MmReleasePageOp(PageOp);
    return(STATUS_SUCCESS);
}

static VOID
MmModifyAttributes(PMMSUPPORT AddressSpace,
    PVOID BaseAddress,
    SIZE_T RegionSize,
    ULONG OldType,
    ULONG OldProtect,
    ULONG NewType,
    ULONG NewProtect)
/*
* FUNCTION: Modify the attributes of a memory region
*/
{
    PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

    /*
    * If we are switching a previously committed region to reserved then
    * free any allocated pages within the region
    */
    if (NewType == MEM_RESERVE && OldType == MEM_COMMIT)
    {
        ULONG i;

        for (i=0; i < PAGE_ROUND_UP(RegionSize)/PAGE_SIZE; i++)
        {
            PFN_NUMBER Page;

            if (MmIsPageSwapEntry(Process,
                        (char*)BaseAddress + (i * PAGE_SIZE)))
            {
                SWAPENTRY SwapEntry;

                MmDeletePageFileMapping(Process,
                    (char*)BaseAddress + (i * PAGE_SIZE),
                    &SwapEntry);
                MmFreeSwapPage(SwapEntry);
            }
            else
            {
                MmDeleteVirtualMapping(Process,
                    (char*)BaseAddress + (i*PAGE_SIZE),
                    FALSE, NULL, &Page);
                if (Page != 0)
                {
                    SWAPENTRY SavedSwapEntry;
                    SavedSwapEntry = MmGetSavedSwapEntryPage(Page);
                    if (SavedSwapEntry != 0)
                    {
                        MmFreeSwapPage(SavedSwapEntry);
                        MmSetSavedSwapEntryPage(Page, 0);
                    }
                    MmDeleteRmap(Page, Process,
                        (char*)BaseAddress + (i * PAGE_SIZE));
                    MmReleasePageMemoryConsumer(MC_USER, Page);
                }
            }
        }
    }

    /*
    * If we are changing the protection attributes of a committed region then
    * alter the attributes for any allocated pages within the region
    */
    if (NewType == MEM_COMMIT && OldType == MEM_COMMIT &&
            OldProtect != NewProtect)
    {
        ULONG i;
        PMM_PAGEOP PageOp;
        PMEMORY_AREA MArea;
        char* addr = (char*)BaseAddress;

        for (i=0; i < PAGE_ROUND_UP(RegionSize)/PAGE_SIZE; i++)
        {
            MArea = MmLocateMemoryAreaByAddress(AddressSpace, addr);
            do
            {
                PageOp = MmGetPageOp(MArea, Process->UniqueProcessId, addr,
                    NULL, 0, MM_PAGEOP_CHANGEPROTECT, TRUE);
            } while(PageOp == NULL);

            /* Should we enable/disable virtual mapping? */
            if((NewProtect & PAGE_NOACCESS) &&
                !(OldProtect & PAGE_NOACCESS) &&
                (MmIsPagePresent(Process, addr)))
            {
                /* Set other flags if any */
                if(NewProtect != PAGE_NOACCESS)
                    MmSetPageProtect(Process, addr, NewProtect & ~PAGE_NOACCESS);
                MmDisableVirtualMapping(Process, addr, NULL, NULL);
            }
            else if((OldProtect & PAGE_NOACCESS) && !(NewProtect & PAGE_NOACCESS))
            {
                MmEnableVirtualMapping(Process, addr);
            }

            /* Set new protection flags */
            if(MmIsPagePresent(Process, addr))
            {
                MmSetPageProtect(Process, addr, NewProtect);
            }
            KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
            MmReleasePageOp(PageOp);
            addr += PAGE_SIZE;
        }
    }
}

NTSTATUS NTAPI
MiProtectVirtualMemory(IN PEPROCESS Process,
    IN OUT PVOID *BaseAddress,
    IN OUT PSIZE_T NumberOfBytesToProtect,
    IN ULONG NewAccessProtection,
    OUT PULONG OldAccessProtection  OPTIONAL)
{
    PMEMORY_AREA MemoryArea;
    PMMSUPPORT AddressSpace;
    ULONG OldAccessProtection_;
    NTSTATUS Status;

    *NumberOfBytesToProtect =
    PAGE_ROUND_UP((ULONG_PTR)(*BaseAddress) + (*NumberOfBytesToProtect)) -
    PAGE_ROUND_DOWN(*BaseAddress);
    *BaseAddress = (PVOID)PAGE_ROUND_DOWN(*BaseAddress);

    AddressSpace = &Process->Vm;

    MmLockAddressSpace(AddressSpace);
    MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, *BaseAddress);
    if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
    {
        MmUnlockAddressSpace(AddressSpace);
        return STATUS_UNSUCCESSFUL;
    }

    if (OldAccessProtection == NULL)
    OldAccessProtection = &OldAccessProtection_;

    if (MemoryArea->Type == MEMORY_AREA_VIRTUAL_MEMORY)
    {
        Status = MmProtectAnonMem(AddressSpace, MemoryArea, *BaseAddress,
            *NumberOfBytesToProtect, NewAccessProtection,
            OldAccessProtection);
    }
    else if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
    {
        Status = MmProtectSectionView(AddressSpace, MemoryArea, *BaseAddress,
            *NumberOfBytesToProtect,
            NewAccessProtection,
            OldAccessProtection);
    }
    else
    {
        /* FIXME: Should we return failure or success in this case? */
        Status = STATUS_CONFLICTING_ADDRESSES;
    }

    MmUnlockAddressSpace(AddressSpace);

    return Status;
}

/*
* @implemented
*/
NTSTATUS
NTAPI
NtAllocateVirtualMemory(IN HANDLE ProcessHandle,
    IN OUT PVOID* UBaseAddress,
    IN ULONG_PTR ZeroBits,
    IN OUT PSIZE_T URegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect)
{
    PEPROCESS Process;
    MEMORY_AREA* MemoryArea;
    ULONG_PTR MemoryAreaLength;
    ULONG Type;
    NTSTATUS Status;
    PMMSUPPORT AddressSpace;
    PVOID BaseAddress;
    ULONG RegionSize;
    PVOID PBaseAddress;
    ULONG_PTR PRegionSize;
    PHYSICAL_ADDRESS BoundaryAddressMultiple;
    PEPROCESS CurrentProcess = PsGetCurrentProcess();
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    KAPC_STATE ApcState;
    ULONG ProtectionMask;
    BOOLEAN Attached = FALSE;
    BoundaryAddressMultiple.QuadPart = 0;
    PAGED_CODE();

    /* Check for valid Zero bits */
    if (ZeroBits > 21)
    {
        DPRINT1("Too many zero bits\n");
        return STATUS_INVALID_PARAMETER_3;
    }

    /* Check for valid Allocation Types */
    if ((AllocationType & ~(MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_PHYSICAL |
                    MEM_TOP_DOWN | MEM_WRITE_WATCH)))
    {
        DPRINT1("Invalid Allocation Type\n");
        return STATUS_INVALID_PARAMETER_5;
    }

    /* Check for at least one of these Allocation Types to be set */
    if (!(AllocationType & (MEM_COMMIT | MEM_RESERVE | MEM_RESET)))
    {
        DPRINT1("No memory allocation base type\n");
        return STATUS_INVALID_PARAMETER_5;
    }

    /* MEM_RESET is an exclusive flag, make sure that is valid too */
    if ((AllocationType & MEM_RESET) && (AllocationType != MEM_RESET))
    {
        DPRINT1("Invalid use of MEM_RESET\n");
        return STATUS_INVALID_PARAMETER_5;
    }

    /* Check if large pages are being used */
    if (AllocationType & MEM_LARGE_PAGES)
    {
        /* Large page allocations MUST be committed */
        if (!(AllocationType & MEM_COMMIT))
        {
            DPRINT1("Must supply MEM_COMMIT with MEM_LARGE_PAGES\n");
            return STATUS_INVALID_PARAMETER_5;
        }

        /* These flags are not allowed with large page allocations */
        if (AllocationType & (MEM_PHYSICAL | MEM_RESET | MEM_WRITE_WATCH))
        {
            DPRINT1("Using illegal flags with MEM_LARGE_PAGES\n");
            return STATUS_INVALID_PARAMETER_5;
        }
    }

    /* MEM_WRITE_WATCH can only be used if MEM_RESERVE is also used */
    if ((AllocationType & MEM_WRITE_WATCH) && !(AllocationType & MEM_RESERVE))
    {
        DPRINT1("MEM_WRITE_WATCH used without MEM_RESERVE\n");
        return STATUS_INVALID_PARAMETER_5;
    }

    /* MEM_PHYSICAL can only be used if MEM_RESERVE is also used */
    if ((AllocationType & MEM_PHYSICAL) && !(AllocationType & MEM_RESERVE))
    {
        DPRINT1("MEM_WRITE_WATCH used without MEM_RESERVE\n");
        return STATUS_INVALID_PARAMETER_5;
    }

    /* Check for valid MEM_PHYSICAL usage */
    if (AllocationType & MEM_PHYSICAL)
    {
        /* Only these flags are allowed with MEM_PHYSIAL */
        if (AllocationType & ~(MEM_RESERVE | MEM_TOP_DOWN | MEM_PHYSICAL))
        {
            DPRINT1("Using illegal flags with MEM_PHYSICAL\n");
            return STATUS_INVALID_PARAMETER_5;
        }

        /* Then make sure PAGE_READWRITE is used */
        if (Protect != PAGE_READWRITE)
        {
            DPRINT1("MEM_PHYSICAL used without PAGE_READWRITE\n");
            return STATUS_INVALID_PARAMETER_6;
        }
    }

    /* Calculate the protection mask and make sure it's valid */
    ProtectionMask = MiMakeProtectionMask(Protect);
    if (ProtectionMask == MM_INVALID_PROTECTION)
    {
        DPRINT1("Invalid protection mask\n");
        return STATUS_INVALID_PAGE_PROTECTION;
    }

    /* Enter SEH */
    _SEH2_TRY
    {
        /* Check for user-mode parameters */
        if (PreviousMode != KernelMode)
        {
            /* Make sure they are writable */
            ProbeForWritePointer(UBaseAddress);
            ProbeForWriteUlong(URegionSize);
        }

        /* Capture their values */
        PBaseAddress = *UBaseAddress;
        PRegionSize = *URegionSize;
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        /* Return the exception code */
        _SEH2_YIELD(return _SEH2_GetExceptionCode());
    }
    _SEH2_END;

    /* Make sure the allocation isn't past the VAD area */
    if (PBaseAddress >= MM_HIGHEST_VAD_ADDRESS)
    {
        DPRINT1("Virtual allocation base above User Space\n");
        return STATUS_INVALID_PARAMETER_2;
    }

    /* Make sure the allocation wouldn't overflow past the VAD area */
    if ((((ULONG_PTR)MM_HIGHEST_VAD_ADDRESS + 1) - (ULONG_PTR)PBaseAddress) < PRegionSize)
    {
        DPRINT1("Region size would overflow into kernel-memory\n");
        return STATUS_INVALID_PARAMETER_4;
    }

    /* Make sure there's a size specified */
    if (!PRegionSize)
    {
        DPRINT1("Region size is invalid (zero)\n");
        return STATUS_INVALID_PARAMETER_4;
    }

    /* Check if this is for the current process */
    if (ProcessHandle == NtCurrentProcess())
    {
        /* We already have the current process, no need to go through Ob */
        Process = CurrentProcess;
    }
    else
    {
        /* Reference the handle for correct permissions */
        Status = ObReferenceObjectByHandle(ProcessHandle,
            PROCESS_VM_OPERATION,
            PsProcessType,
            PreviousMode,
            (PVOID*)&Process,
            NULL);
        if (!NT_SUCCESS(Status)) return Status;

        /* Check if not running in the current process */
        if (CurrentProcess != Process)
        {
            /* Attach to it */
            KeStackAttachProcess(&Process->Pcb, &ApcState);
            Attached = TRUE;
        }
    }

    /* Check for large page allocations */
    if (AllocationType & MEM_LARGE_PAGES)
    {
        /* The lock memory privilege is required */
        if (!SeSinglePrivilegeCheck(SeLockMemoryPrivilege, PreviousMode))
        {
            /* Fail without it */
            DPRINT1("Privilege not held for MEM_LARGE_PAGES\n");
            if (Attached) KeUnstackDetachProcess(&ApcState);
            if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
            return STATUS_PRIVILEGE_NOT_HELD;
        }
    }

    BaseAddress = (PVOID)PAGE_ROUND_DOWN(PBaseAddress);
    RegionSize = PAGE_ROUND_UP((ULONG_PTR)PBaseAddress + PRegionSize) -
    PAGE_ROUND_DOWN(PBaseAddress);


    /*
    * Copy on Write is reserved for system use. This case is a certain failure
    * but there may be other cases...needs more testing
    */
    if ((!BaseAddress || (AllocationType & MEM_RESERVE)) &&
            (Protect & (PAGE_WRITECOPY | PAGE_EXECUTE_WRITECOPY)))
    {
        DPRINT1("Copy on write is not supported by VirtualAlloc\n");
        if (Attached) KeUnstackDetachProcess(&ApcState);
        if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
        return STATUS_INVALID_PAGE_PROTECTION;
    }

    Type = (AllocationType & MEM_COMMIT) ? MEM_COMMIT : MEM_RESERVE;
    DPRINT("Type %x\n", Type);

    AddressSpace = &Process->Vm;
    MmLockAddressSpace(AddressSpace);

    if (PBaseAddress != 0)
    {
        MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, BaseAddress);

        if (MemoryArea != NULL)
        {
            MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress -
            (ULONG_PTR)MemoryArea->StartingAddress;

            if (((ULONG_PTR)BaseAddress + RegionSize) > (ULONG_PTR)MemoryArea->EndingAddress)
            {
                DPRINT("BaseAddress + RegionSize %x is larger than MemoryArea's EndingAddress %x\n",
                    (ULONG_PTR)BaseAddress + RegionSize, MemoryArea->EndingAddress);

                MmUnlockAddressSpace(AddressSpace);
                if (Attached) KeUnstackDetachProcess(&ApcState);
                if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
                return STATUS_MEMORY_NOT_ALLOCATED;
            }

            if (AllocationType == MEM_RESET)
            {
                if (MmIsPagePresent(Process, BaseAddress))
                {
                    /* FIXME: mark pages as not modified */
                }
                else
                {
                    /* FIXME: if pages are in paging file discard them and bring in pages of zeros */
                }

                MmUnlockAddressSpace(AddressSpace);
                if (Attached) KeUnstackDetachProcess(&ApcState);
                if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);

                /* MEM_RESET does not modify any attributes of region */
                return STATUS_SUCCESS;
            }

            if (MemoryArea->Type == MEMORY_AREA_VIRTUAL_MEMORY &&
                    MemoryAreaLength >= RegionSize)
            {
                Status = MmAlterRegion(AddressSpace,
                    MemoryArea->StartingAddress,
                    &MemoryArea->Data.VirtualMemoryData.RegionListHead,
                    BaseAddress, RegionSize,
                    Type, Protect, MmModifyAttributes);
                MmUnlockAddressSpace(AddressSpace);
                if (Attached) KeUnstackDetachProcess(&ApcState);
                if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
                DPRINT("NtAllocateVirtualMemory() = %x\n",Status);

                /* Give the caller rounded BaseAddress and area length */
                if (NT_SUCCESS(Status))
                {
                    *UBaseAddress = BaseAddress;
                    *URegionSize = RegionSize;
                    DPRINT("*UBaseAddress %x  *URegionSize %x\n", BaseAddress, RegionSize);
                }

                return(Status);
            }
            else if (MemoryAreaLength >= RegionSize)
            {
                /* Region list initialized? */
                if (MemoryArea->Data.SectionData.RegionListHead.Flink)
                {
                    Status = MmAlterRegion(AddressSpace,
                        MemoryArea->StartingAddress,
                        &MemoryArea->Data.SectionData.RegionListHead,
                        BaseAddress, RegionSize,
                        Type, Protect, MmModifyAttributes);
                }
                else
                {
                    Status = STATUS_ACCESS_VIOLATION;
                }

                MmUnlockAddressSpace(AddressSpace);
                if (Attached) KeUnstackDetachProcess(&ApcState);
                if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
                DPRINT("NtAllocateVirtualMemory() = %x\n",Status);

                /* Give the caller rounded BaseAddress and area length */
                if (NT_SUCCESS(Status))
                {
                    *UBaseAddress = BaseAddress;
                    *URegionSize = RegionSize;
                    DPRINT("*UBaseAddress %x  *URegionSize %x\n", BaseAddress, RegionSize);
                }

                return(Status);
            }
            else
            {
                MmUnlockAddressSpace(AddressSpace);
                if (Attached) KeUnstackDetachProcess(&ApcState);
                if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
                return(STATUS_UNSUCCESSFUL);
            }
        }
    }

    Status = MmCreateMemoryArea(AddressSpace,
        MEMORY_AREA_VIRTUAL_MEMORY,
        &BaseAddress,
        RegionSize,
        Protect,
        &MemoryArea,
        PBaseAddress != 0,
        AllocationType & MEM_TOP_DOWN,
        BoundaryAddressMultiple);
    if (!NT_SUCCESS(Status))
    {
        MmUnlockAddressSpace(AddressSpace);
        if (Attached) KeUnstackDetachProcess(&ApcState);
        if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);
        DPRINT("NtAllocateVirtualMemory() = %x\n",Status);
        return(Status);
    }

    MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress -
        (ULONG_PTR)MemoryArea->StartingAddress;

    MmInitializeRegion(&MemoryArea->Data.VirtualMemoryData.RegionListHead,
        MemoryAreaLength, Type, Protect);

    if ((AllocationType & MEM_COMMIT) &&
            (Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)))
    {
        const ULONG nPages = PAGE_ROUND_UP(MemoryAreaLength) >> PAGE_SHIFT;
        MmReserveSwapPages(nPages);
    }

    MmUnlockAddressSpace(AddressSpace);
    if (Attached) KeUnstackDetachProcess(&ApcState);
    if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);

    *UBaseAddress = BaseAddress;
    *URegionSize = MemoryAreaLength;
    DPRINT("*UBaseAddress %x  *URegionSize %x\n", BaseAddress, RegionSize);

    return(STATUS_SUCCESS);
}

static VOID
MmFreeVirtualMemoryPage(PVOID Context,
    MEMORY_AREA* MemoryArea,
    PVOID Address,
    PFN_NUMBER Page,
    SWAPENTRY SwapEntry,
    BOOLEAN Dirty)
{
    PEPROCESS Process = (PEPROCESS)Context;

    if (Page != 0)
    {
        SWAPENTRY SavedSwapEntry;
        SavedSwapEntry = MmGetSavedSwapEntryPage(Page);
        if (SavedSwapEntry != 0)
        {
            MmFreeSwapPage(SavedSwapEntry);
            MmSetSavedSwapEntryPage(Page, 0);
        }
        MmDeleteRmap(Page, Process, Address);
        MmReleasePageMemoryConsumer(MC_USER, Page);
    }
    else if (SwapEntry != 0)
    {
        MmFreeSwapPage(SwapEntry);
    }
}

VOID
NTAPI
MmFreeVirtualMemory(PEPROCESS Process,
    PMEMORY_AREA MemoryArea)
{
    PLIST_ENTRY current_entry;
    PMM_REGION current;
    ULONG i;

    DPRINT("MmFreeVirtualMemory(Process %p  MemoryArea %p)\n", Process,
        MemoryArea);

    /* Mark this memory area as about to be deleted. */
    MemoryArea->DeleteInProgress = TRUE;

    /*
    * Wait for any ongoing paging operations. Notice that since we have
    * flagged this memory area as deleted no more page ops will be added.
    */
    if (MemoryArea->PageOpCount > 0)
    {
        ULONG_PTR MemoryAreaLength = (ULONG_PTR)MemoryArea->EndingAddress -
        (ULONG_PTR)MemoryArea->StartingAddress;
        const ULONG nPages = PAGE_ROUND_UP(MemoryAreaLength) >> PAGE_SHIFT;

        for (i = 0; i < nPages && MemoryArea->PageOpCount != 0; ++i)
        {
            PMM_PAGEOP PageOp;
            PageOp = MmCheckForPageOp(MemoryArea, Process->UniqueProcessId,
                (PVOID)((ULONG_PTR)MemoryArea->StartingAddress + (i * PAGE_SIZE)),
                NULL, 0);
            if (PageOp != NULL)
            {
                NTSTATUS Status;
                MmUnlockAddressSpace(&Process->Vm);
                Status = KeWaitForSingleObject(&PageOp->CompletionEvent,
                    0,
                    KernelMode,
                    FALSE,
                    NULL);
                if (Status != STATUS_SUCCESS)
                {
                    DPRINT1("Failed to wait for page op\n");
                    KeBugCheck(MEMORY_MANAGEMENT);
                }
                MmLockAddressSpace(&Process->Vm);
                MmReleasePageOp(PageOp);
            }
        }
    }

    /* Free all the individual segments. */
    current_entry = MemoryArea->Data.VirtualMemoryData.RegionListHead.Flink;
    while (current_entry != &MemoryArea->Data.VirtualMemoryData.RegionListHead)
    {
        current = CONTAINING_RECORD(current_entry, MM_REGION, RegionListEntry);
        current_entry = current_entry->Flink;
        ExFreePool(current);
    }

    /* Actually free the memory area. */
    MmFreeMemoryArea(&Process->Vm,
    MemoryArea,
    MmFreeVirtualMemoryPage,
    (PVOID)Process);
}

/*
* @implemented
*/
NTSTATUS NTAPI
NtFreeVirtualMemory(IN HANDLE ProcessHandle,
    IN PVOID*  UBaseAddress,
    IN PSIZE_T URegionSize,
    IN ULONG FreeType)
/*
* FUNCTION: Frees a range of virtual memory
* ARGUMENTS:
*        ProcessHandle = Points to the process that allocated the virtual
*                        memory
*        BaseAddress = Points to the memory address, rounded down to a
*                      multiple of the pagesize
*        RegionSize = Limits the range to free, rounded up to a multiple of
*                     the paging size
*        FreeType = Can be one of the values:  MEM_DECOMMIT, or MEM_RELEASE
* RETURNS: Status
*/
{
    MEMORY_AREA* MemoryArea;
    NTSTATUS Status;
    PEPROCESS Process;
    PMMSUPPORT AddressSpace;
    PVOID BaseAddress = NULL, PBaseAddress;
    SIZE_T RegionSize = 0, PRegionSize;
    PEPROCESS CurrentProcess = PsGetCurrentProcess();
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    KAPC_STATE ApcState;
    BOOLEAN Attached = FALSE;
    PAGED_CODE();

    /* Only two flags are supported */
    if (!(FreeType & (MEM_RELEASE | MEM_DECOMMIT)))
    {
        DPRINT1("Invalid FreeType\n");
        return STATUS_INVALID_PARAMETER_4;
    }

    /* Check if no flag was used, or if both flags were used */
    if (!((FreeType & (MEM_DECOMMIT | MEM_RELEASE))) ||
            ((FreeType & (MEM_DECOMMIT | MEM_RELEASE)) == (MEM_DECOMMIT | MEM_RELEASE)))
    {
        DPRINT1("Invalid FreeType combination\n");
        return STATUS_INVALID_PARAMETER_4;
    }

    /* Enter SEH */
    _SEH2_TRY
    {
        /* Check for user-mode parameters */
        if (PreviousMode != KernelMode)
        {
            /* Make sure they are writeable */
            ProbeForWritePointer(UBaseAddress);
            ProbeForWriteUlong(URegionSize);
        }

        /* Capture their values */
        PBaseAddress = *UBaseAddress;
        PRegionSize = *URegionSize;
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        /* Return the exception code */
        _SEH2_YIELD(return _SEH2_GetExceptionCode());
    }
    _SEH2_END;

    /* Make sure the allocation isn't past the user area */
    if (PBaseAddress >= MM_HIGHEST_USER_ADDRESS)
    {
        DPRINT1("Virtual free base above User Space\n");
        return STATUS_INVALID_PARAMETER_2;
    }

    /* Make sure the allocation wouldn't overflow past the user area */
    if (((ULONG_PTR)MM_HIGHEST_USER_ADDRESS - (ULONG_PTR)PBaseAddress) < PRegionSize)
    {
        DPRINT1("Region size would overflow into kernel-memory\n");
        return STATUS_INVALID_PARAMETER_3;
    }

    /* Check if this is for the current process */
    if (ProcessHandle == NtCurrentProcess())
    {
        /* We already have the current process, no need to go through Ob */
        Process = CurrentProcess;
    }
    else
    {
        /* Reference the handle for correct permissions */
        Status = ObReferenceObjectByHandle(ProcessHandle,
            PROCESS_VM_OPERATION,
            PsProcessType,
            PreviousMode,
            (PVOID*)&Process,
            NULL);
        if (!NT_SUCCESS(Status)) return Status;

        /* Check if not running in the current process */
        if (CurrentProcess != Process)
        {
            /* Attach to it */
            KeStackAttachProcess(&Process->Pcb, &ApcState);
            Attached = TRUE;
        }
    }

    BaseAddress = (PVOID)PAGE_ROUND_DOWN((PBaseAddress));

    AddressSpace = &Process->Vm;

    MmLockAddressSpace(AddressSpace);
    MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, BaseAddress);
    if (MemoryArea == NULL)
    {
        DPRINT1("Unable to find memory area from address 0x%p\n", BaseAddress);
        Status = STATUS_UNABLE_TO_FREE_VM;
        goto unlock_deref_and_return;
    }

    if (PRegionSize != 0)
    {
        /* Use the region the caller wanted, rounded to whole pages */
        RegionSize = PAGE_ROUND_UP((ULONG_PTR)(PBaseAddress) + (PRegionSize)) -
        PAGE_ROUND_DOWN((PBaseAddress));
    }
    else
    {
        /* The caller wanted the whole memory area */
        RegionSize = (ULONG_PTR)MemoryArea->EndingAddress -
                     (ULONG_PTR)MemoryArea->StartingAddress;
    }

    switch (FreeType)
    {
    case MEM_RELEASE:
        /* MEM_RELEASE must be used with the exact base and length
         * that was returned by NtAllocateVirtualMemory */

        /* Verify the base address is correct */
        if (MemoryArea->StartingAddress != BaseAddress)
        {
            DPRINT1("Base address is illegal for MEM_RELEASE (0x%p != 0x%p)\n",
                    MemoryArea->StartingAddress,
                    BaseAddress);
            Status = STATUS_UNABLE_TO_FREE_VM;
            goto unlock_deref_and_return;
        }

        /* Verify the region size is correct */
        if ((ULONG_PTR)MemoryArea->StartingAddress + RegionSize !=
            (ULONG_PTR)MemoryArea->EndingAddress)
        {
            DPRINT1("Region size is illegal for MEM_RELEASE (0x%x)\n", RegionSize);
            Status = STATUS_UNABLE_TO_FREE_VM;
            //goto unlock_deref_and_return;
        }

        if (MemoryArea->Type != MEMORY_AREA_VIRTUAL_MEMORY)
        {
            DPRINT1("Memory area is not VM\n");
            Status = STATUS_UNABLE_TO_FREE_VM;
            goto unlock_deref_and_return;
        }

        MmFreeVirtualMemory(Process, MemoryArea);
        Status = STATUS_SUCCESS;
        break;

    case MEM_DECOMMIT:
        if ((ULONG_PTR)BaseAddress + RegionSize >
            (ULONG_PTR)MemoryArea->EndingAddress)
        {
            DPRINT1("Invald base address (0x%p) and region size (0x%x) for MEM_DECOMMIT\n",
                    BaseAddress, RegionSize);
            Status = STATUS_UNABLE_TO_FREE_VM;
            goto unlock_deref_and_return;
        }
        Status = MmAlterRegion(AddressSpace,
            MemoryArea->StartingAddress,
            (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW) ?
            &MemoryArea->Data.SectionData.RegionListHead :
            &MemoryArea->Data.VirtualMemoryData.RegionListHead,
            BaseAddress,
            RegionSize,
            MEM_RESERVE,
            PAGE_NOACCESS,
            MmModifyAttributes);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("MmAlterRegion failed with status 0x%x\n", Status);
            Status = STATUS_UNABLE_TO_FREE_VM;
            goto unlock_deref_and_return;
        }
        break;

    default:
        Status = STATUS_NOT_IMPLEMENTED;
        goto unlock_deref_and_return;
    }

unlock_deref_and_return:
    MmUnlockAddressSpace(AddressSpace);

    /* Copy rounded values back in success case */
    if (NT_SUCCESS(Status))
    {
        /* Enter SEH */
        _SEH2_TRY
        {
            *UBaseAddress = BaseAddress;
            *URegionSize = RegionSize;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
            DPRINT1("Failed to copy values back! (Status: 0x%x)\n", Status);
        }
        _SEH2_END;
    }

    if (Attached) KeUnstackDetachProcess(&ApcState);
    if (ProcessHandle != NtCurrentProcess()) ObDereferenceObject(Process);

    return(Status);
}

NTSTATUS
NTAPI
MmProtectAnonMem(PMMSUPPORT AddressSpace,
    PMEMORY_AREA MemoryArea,
    PVOID BaseAddress,
    SIZE_T Length,
    ULONG Protect,
    PULONG OldProtect)
{
    PMM_REGION Region;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG_PTR LengthCount = 0;

    /* Search all Regions in MemoryArea up to Length */
    /* Every Region up to Length must be committed for success */
    for (;;)
    {
        Region = MmFindRegion(MemoryArea->StartingAddress,
            &MemoryArea->Data.VirtualMemoryData.RegionListHead,
            (PVOID)((ULONG_PTR)BaseAddress + LengthCount), NULL);

        /* If a Region was found and it is committed */
        if ((Region) && (Region->Type == MEM_COMMIT))
        {
            LengthCount += Region->Length;
            if (Length <= LengthCount) break;
            continue;
        }
        /* If Region was found and it is not commited */
        else if (Region)
        {
            Status = STATUS_NOT_COMMITTED;
            break;
        }
        /* If no Region was found at all */
        else if (LengthCount == 0)
        {
            Status = STATUS_INVALID_ADDRESS;
            break;
        }
    }

    if (NT_SUCCESS(Status))
    {
        *OldProtect = Region->Protect;
        Status = MmAlterRegion(AddressSpace, MemoryArea->StartingAddress,
            &MemoryArea->Data.VirtualMemoryData.RegionListHead,
            BaseAddress, Length, Region->Type, Protect,
            MmModifyAttributes);
    }

    return (Status);
}

NTSTATUS NTAPI
MmQueryAnonMem(PMEMORY_AREA MemoryArea,
    PVOID Address,
    PMEMORY_BASIC_INFORMATION Info,
    PSIZE_T ResultLength)
{
    PMM_REGION Region;
    PVOID RegionBase = NULL;

    Info->BaseAddress = (PVOID)PAGE_ROUND_DOWN(Address);

    Region = MmFindRegion(MemoryArea->StartingAddress,
        &MemoryArea->Data.VirtualMemoryData.RegionListHead,
        Address, &RegionBase);
    Info->BaseAddress = RegionBase;
    Info->AllocationBase = MemoryArea->StartingAddress;
    Info->AllocationProtect = MemoryArea->Protect;
    Info->RegionSize = Region->Length;
    Info->State = Region->Type;
    Info->Protect = Region->Protect;
    Info->Type = MEM_PRIVATE;

    *ResultLength = sizeof(MEMORY_BASIC_INFORMATION);
    return(STATUS_SUCCESS);
}

/* EOF */
