/*
 * COPYRIGHT:   See COPYING in the top level directory
 * LICENSE:     See LGPL.txt in the top level directory
 * PROJECT:     ReactOS system libraries
 * FILE:        reactos/lib/psapi/misc/win32.c
 * PURPOSE:     Win32 interfaces for PSAPI
 * PROGRAMMER:  KJK::Hyperion <noog@libero.it>
 *              Thomas Weidenmueller <w3seek@reactos.com>
 *              Pierre Schweitzer <pierre@reactos.org>
 * UPDATE HISTORY:
 *              10/06/2002: Created
 */

#include <stdarg.h>

#define WIN32_NO_STATUS
#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#define NTOS_MODE_USER
#include <ndk/exfuncs.h>
#include <ndk/mmfuncs.h>
#include <ndk/psfuncs.h>
#include <ndk/rtlfuncs.h>

#include <psapi.h>

#include <pseh/pseh2.h>

#define NDEBUG
#include <debug.h>

#define MAX_MODULES 0x2710      // Matches 10.000 modules
#define INIT_MEMORY_SIZE 0x1000 // Matches 4kB

/* INTERNAL *******************************************************************/

/*
 * @implemented
 */
static BOOL NTAPI
FindDeviceDriver(IN PVOID ImageBase,
                 OUT PRTL_PROCESS_MODULE_INFORMATION MatchingModule)
{
    NTSTATUS Status;
    DWORD NewSize, Count;
    PRTL_PROCESS_MODULES Information;
    RTL_PROCESS_MODULE_INFORMATION Module;
    /* By default, to prevent too many reallocations, we already make room for 4 modules */
    DWORD Size = sizeof(RTL_PROCESS_MODULES) + 3 * sizeof(RTL_PROCESS_MODULE_INFORMATION);

    do
    {
        /* Allocate a buffer to hold modules information */
        Information = LocalAlloc(LMEM_FIXED, Size);
        if (!Information)
        {
            SetLastError(ERROR_NO_SYSTEM_RESOURCES);
            return FALSE;
        }

        /* Query information */
        Status = NtQuerySystemInformation(SystemModuleInformation, Information, Size, &Count);
        /* In case of an error */
        if (!NT_SUCCESS(Status))
        {
            /* Save the amount of output modules */
            NewSize = Information->NumberOfModules;
            /* And free buffer */
            LocalFree(Information);

            /* If it was not a length mismatch (ie, buffer too small), just leave */
            if (Status != STATUS_INFO_LENGTH_MISMATCH)
            {
                SetLastError(RtlNtStatusToDosError(Status));
                return FALSE;
            }

            /* Compute new size length */
            ASSERT(Size >= sizeof(RTL_PROCESS_MODULES));
            NewSize *= sizeof(RTL_PROCESS_MODULE_INFORMATION);
            NewSize += sizeof(ULONG);
            ASSERT(NewSize >= sizeof(RTL_PROCESS_MODULES));
            /* Check whether it is really bigger - otherwise, leave */
            if (NewSize < Size)
            {
                ASSERT(NewSize > Size);
                SetLastError(RtlNtStatusToDosError(STATUS_INFO_LENGTH_MISMATCH));
                return FALSE;
            }

            /* Loop again with that new buffer */
            Size = NewSize;
            continue;
        }

        /* No modules returned? Leave */
        if (Information->NumberOfModules == 0)
        {
            break;
        }

        /* Try to find which module matches the base address given */
        for (Count = 0; Count < Information->NumberOfModules; ++Count)
        {
            Module = Information->Modules[Count];
            if (Module.ImageBase == ImageBase)
            {
                /* Copy the matching module and leave */
                memcpy(MatchingModule, &Module, sizeof(Module));
                LocalFree(Information);
                return TRUE;
            }
        }

        /* If we arrive here, it means we were not able to find matching base address */
        break;
    } while (TRUE);

    /* Release and leave */
    LocalFree(Information);
    SetLastError(ERROR_INVALID_HANDLE);

    return FALSE;
}

/*
 * @implemented
 */
static BOOL NTAPI
FindModule(IN HANDLE hProcess,
           IN HMODULE hModule OPTIONAL,
           OUT PLDR_DATA_TABLE_ENTRY Module)
{
    DWORD Count;
    NTSTATUS Status;
    PPEB_LDR_DATA LoaderData;
    PLIST_ENTRY ListHead, ListEntry;
    PROCESS_BASIC_INFORMATION ProcInfo;

    /* Query the process information to get its PEB address */
    Status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &ProcInfo, sizeof(ProcInfo), NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    /* If no module was provided, get base as module */
    if (hModule == NULL)
    {
        if (!ReadProcessMemory(hProcess, &ProcInfo.PebBaseAddress->ImageBaseAddress, &hModule, sizeof(hModule), NULL))
        {
            return FALSE;
        }
    }

    /* Read loader data address from PEB */
    if (!ReadProcessMemory(hProcess, &ProcInfo.PebBaseAddress->Ldr, &LoaderData, sizeof(LoaderData), NULL))
    {
        return FALSE;
    }

    if (LoaderData == NULL)
    {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    /* Store list head address */
    ListHead = &(LoaderData->InMemoryOrderModuleList);

    /* Read first element in the modules list */
    if (!ReadProcessMemory(hProcess,
                           &(LoaderData->InMemoryOrderModuleList.Flink),
                           &ListEntry,
                           sizeof(ListEntry),
                           NULL))
    {
        return FALSE;
    }

    Count = 0;

    /* Loop on the modules */
    while (ListEntry != ListHead)
    {
        /* Load module data */
        if (!ReadProcessMemory(hProcess,
                               CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks),
                               Module,
                               sizeof(*Module),
                               NULL))
        {
            return FALSE;
        }

        /* Does that match the module we're looking for? */
        if (Module->DllBase == hModule)
        {
            return TRUE;
        }

        ++Count;
        if (Count > MAX_MODULES)
        {
            break;
        }

        /* Get to next listed module */
        ListEntry = Module->InMemoryOrderLinks.Flink;
    }

    SetLastError(ERROR_INVALID_HANDLE);
    return FALSE;
}

typedef struct _INTERNAL_ENUM_PAGE_FILES_CONTEXT
{
    LPVOID lpContext;
    PENUM_PAGE_FILE_CALLBACKA pCallbackRoutine;
    DWORD dwErrCode;
} INTERNAL_ENUM_PAGE_FILES_CONTEXT, *PINTERNAL_ENUM_PAGE_FILES_CONTEXT;

/*
 * @implemented
 */
static BOOL CALLBACK
CallBackConvertToAscii(LPVOID pContext,
                       PENUM_PAGE_FILE_INFORMATION pPageFileInfo,
                       LPCWSTR lpFilename)
{
    BOOL Ret;
    DWORD Len;
    LPSTR AnsiFileName;
    PINTERNAL_ENUM_PAGE_FILES_CONTEXT Context = (PINTERNAL_ENUM_PAGE_FILES_CONTEXT)pContext;

    Len = wcslen(lpFilename);

    /* Alloc space for the ANSI string */
    AnsiFileName = LocalAlloc(LMEM_FIXED, (Len * sizeof(CHAR)) + sizeof(ANSI_NULL));
    if (AnsiFileName == NULL)
    {
        Context->dwErrCode = RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES);
        return FALSE;
    }

    /* Convert string to ANSI */
    if (WideCharToMultiByte(CP_ACP, 0, lpFilename, -1, AnsiFileName, (Len * sizeof(CHAR)) + sizeof(ANSI_NULL), NULL, NULL) == 0)
    {
        Context->dwErrCode = GetLastError();
        LocalFree(AnsiFileName);
        return FALSE;
    }

    /* And finally call "real" callback */
    Ret = Context->pCallbackRoutine(Context->lpContext, pPageFileInfo, AnsiFileName);
    LocalFree(AnsiFileName);

    return Ret;
}

/*
 * @unimplemented
 */
static VOID NTAPI
PsParseCommandLine(VOID)
{
    UNIMPLEMENTED;
}

/*
 * @unimplemented
 */
static VOID NTAPI
PsInitializeAndStartProfile(VOID)
{
    UNIMPLEMENTED;
}

/*
 * @unimplemented
 */
static VOID NTAPI
PsStopAndAnalyzeProfile(VOID)
{
    UNIMPLEMENTED;
}

/* PUBLIC *********************************************************************/

/*
 * @implemented
 */
BOOLEAN
WINAPI
DllMain(HINSTANCE hDllHandle,
        DWORD nReason,
        LPVOID Reserved)
{
    switch(nReason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hDllHandle);
            if (NtCurrentPeb()->ProcessParameters->Flags & RTL_USER_PROCESS_PARAMETERS_PROFILE_USER)
            {
                PsParseCommandLine();
                PsInitializeAndStartProfile();
            }
            break;

        case DLL_PROCESS_DETACH:
            if (NtCurrentPeb()->ProcessParameters->Flags & RTL_USER_PROCESS_PARAMETERS_PROFILE_USER)
            {
                PsStopAndAnalyzeProfile();
            }
            break;  
  }

  return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
EmptyWorkingSet(HANDLE hProcess)
{
    SYSTEM_INFO SystemInfo;
    QUOTA_LIMITS QuotaLimits;
    NTSTATUS Status;

    GetSystemInfo(&SystemInfo);

    /* Query the working set */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessQuotaLimits,
                                       &QuotaLimits,
                                       sizeof(QuotaLimits),
                                       NULL);

    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    /* Empty the working set */
    QuotaLimits.MinimumWorkingSetSize = -1;
    QuotaLimits.MaximumWorkingSetSize = -1;

    /* Set the working set */
    Status = NtSetInformationProcess(hProcess,
                                     ProcessQuotaLimits,
                                     &QuotaLimits,
                                     sizeof(QuotaLimits));
    if (!NT_SUCCESS(Status) && Status != STATUS_PRIVILEGE_NOT_HELD)
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
EnumDeviceDrivers(LPVOID *lpImageBase,
                  DWORD cb,
                  LPDWORD lpcbNeeded)
{
    NTSTATUS Status;
    DWORD NewSize, Count;
    PRTL_PROCESS_MODULES Information;
    /* By default, to prevent too many reallocations, we already make room for 4 modules */
    DWORD Size = sizeof(RTL_PROCESS_MODULES) + 3 * sizeof(RTL_PROCESS_MODULE_INFORMATION);

    do
    {
        /* Allocate a buffer to hold modules information */
        Information = LocalAlloc(LMEM_FIXED, Size);
        if (!Information)
        {
            SetLastError(ERROR_NO_SYSTEM_RESOURCES);
            return FALSE;
        }

        /* Query information */
        Status = NtQuerySystemInformation(SystemModuleInformation, Information, Size, &Count);
        /* In case of an error */
        if (!NT_SUCCESS(Status))
        {
            /* Save the amount of output modules */
            NewSize = Information->NumberOfModules;
            /* And free buffer */
            LocalFree(Information);

            /* If it was not a length mismatch (ie, buffer too small), just leave */
            if (Status != STATUS_INFO_LENGTH_MISMATCH)
            {
                SetLastError(RtlNtStatusToDosError(Status));
                return FALSE;
            }

            /* Compute new size length */
            ASSERT(Size >= sizeof(RTL_PROCESS_MODULES));
            NewSize *= sizeof(RTL_PROCESS_MODULE_INFORMATION);
            NewSize += sizeof(ULONG);
            ASSERT(NewSize >= sizeof(RTL_PROCESS_MODULES));
            /* Check whether it is really bigger - otherwise, leave */
            if (NewSize < Size)
            {
                ASSERT(NewSize > Size);
                SetLastError(RtlNtStatusToDosError(STATUS_INFO_LENGTH_MISMATCH));
                return FALSE;
            }

            /* Loop again with that new buffer */
            Size = NewSize;
            continue;
        }

        /* End of allocation loop */
        break;
    } while (TRUE);

    _SEH2_TRY
    {
        for (Count = 0; Count < Information->NumberOfModules && Count < cb / sizeof(LPVOID); ++Count)
        {
            lpImageBase[Count] = Information->Modules[Count].ImageBase;
        }

        *lpcbNeeded = Information->NumberOfModules * sizeof(LPVOID);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
        _SEH2_YIELD(return FALSE);
    }
    _SEH2_END;

    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
EnumProcesses(DWORD *lpidProcess,
              DWORD cb,
              LPDWORD lpcbNeeded)
{
    NTSTATUS Status;
    DWORD Size = MAXSHORT, Count;
    PSYSTEM_PROCESS_INFORMATION ProcInfo;
    PSYSTEM_PROCESS_INFORMATION ProcInfoArray;

    /* First of all, query all the processes */
    do
    {
        ProcInfoArray = LocalAlloc(LMEM_FIXED, Size);
        if (ProcInfoArray == NULL)
        {
            return FALSE;
        }

        Status = NtQuerySystemInformation(SystemProcessInformation, ProcInfoArray, Size, NULL);
        if (Status == STATUS_INFO_LENGTH_MISMATCH)
        {
            LocalFree(ProcInfoArray);
            Size += MAXSHORT;
            continue;
        }

        break;
    }
    while (TRUE);

    if (!NT_SUCCESS(Status))
    {
        LocalFree(ProcInfoArray);
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    /* Then, loop to output data */
    Count = 0;
    ProcInfo = ProcInfoArray;

    _SEH2_TRY
    {
        do
        {
            /* It may sound weird, but actually MS only updated Count on
             * successful write. So, it cannot measure the amount of space needed!
             * This is really tricky.
             */
            if (Count < cb / sizeof(DWORD))
            {
                lpidProcess[Count] = (DWORD)ProcInfo->UniqueProcessId;
                Count++;
            }

            if (ProcInfo->NextEntryOffset == 0)
            {
                break;
            }

            ProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcInfo + ProcInfo->NextEntryOffset);
        }
        while (TRUE);

        *lpcbNeeded = Count * sizeof(DWORD);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
        LocalFree(ProcInfoArray);
        _SEH2_YIELD(return FALSE);
    }
    _SEH2_END;

    LocalFree(ProcInfoArray);
    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
EnumProcessModules(HANDLE hProcess,
                   HMODULE *lphModule,
                   DWORD cb,
                   LPDWORD lpcbNeeded)
{
    NTSTATUS Status;
    DWORD NbOfModules, Count;
    PPEB_LDR_DATA LoaderData;
    PLIST_ENTRY ListHead, ListEntry;
    PROCESS_BASIC_INFORMATION ProcInfo;
    LDR_DATA_TABLE_ENTRY CurrentModule;

    /* Query the process information to get its PEB address */
    Status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &ProcInfo, sizeof(ProcInfo), NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    if (ProcInfo.PebBaseAddress == NULL)
    {
        SetLastError(RtlNtStatusToDosError(STATUS_PARTIAL_COPY));
        return FALSE;
    }

    /* Read loader data address from PEB */
    if (!ReadProcessMemory(hProcess, &ProcInfo.PebBaseAddress->Ldr, &LoaderData, sizeof(LoaderData), NULL))
    {
        return FALSE;
    }

    /* Store list head address */
    ListHead = &LoaderData->InLoadOrderModuleList;

    /* Read first element in the modules list */
    if (!ReadProcessMemory(hProcess, &LoaderData->InLoadOrderModuleList.Flink, &ListEntry, sizeof(ListEntry), NULL))
    {
        return FALSE;
    }

    NbOfModules = cb / sizeof(HMODULE);
    Count = 0;

    /* Loop on the modules */
    while (ListEntry != ListHead)
    {
        /* Load module data */
        if (!ReadProcessMemory(hProcess,
                               CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks),
                               &CurrentModule,
                               sizeof(CurrentModule),
                               NULL))
        {
            return FALSE;
        }

        /* Check if we can output module, do it if so */
        if (Count < NbOfModules)
        {
            _SEH2_TRY
            {
                lphModule[Count] = CurrentModule.DllBase;
            }
            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
            {
                SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
                _SEH2_YIELD(return FALSE);
            }
            _SEH2_END;
        }

        ++Count;
        if (Count > MAX_MODULES)
        {
            SetLastError(ERROR_INVALID_HANDLE);
            return FALSE;
        }

        /* Get to next listed module */
        ListEntry = CurrentModule.InLoadOrderLinks.Flink;
    }

    _SEH2_TRY
    {
        *lpcbNeeded = Count * sizeof(HMODULE);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
        _SEH2_YIELD(return FALSE);
    }
    _SEH2_END;

    return TRUE;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetDeviceDriverBaseNameA(LPVOID ImageBase,
                         LPSTR lpBaseName,
                         DWORD nSize)
{
    DWORD Len, LenWithNull;
    RTL_PROCESS_MODULE_INFORMATION Module;

    /* Get the associated device driver to the base address */
    if (!FindDeviceDriver(ImageBase, &Module))
    {
        return 0;
    }

    /* And copy as much as possible to output buffer.
     * Try to add 1 to the len, to copy the null char as well.
     */
    Len =
    LenWithNull = strlen(&Module.FullPathName[Module.OffsetToFileName]) + 1;
    if (Len > nSize)
    {
        Len = nSize;
    }

    memcpy(lpBaseName, &Module.FullPathName[Module.OffsetToFileName], Len);
    /* In case we copied null char, remove it from final len */
    if (Len == LenWithNull)
    {
        --Len;
    }

    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetDeviceDriverFileNameA(LPVOID ImageBase,
                         LPSTR lpFilename,
                         DWORD nSize)
{
    DWORD Len, LenWithNull;
    RTL_PROCESS_MODULE_INFORMATION Module;

    /* Get the associated device driver to the base address */
    if (!FindDeviceDriver(ImageBase, &Module))
    {
        return 0;
    }

    /* And copy as much as possible to output buffer.
     * Try to add 1 to the len, to copy the null char as well.
     */
    Len =
    LenWithNull = strlen(Module.FullPathName) + 1;
    if (Len > nSize)
    {
        Len = nSize;
    }

    memcpy(lpFilename, Module.FullPathName, Len);
    /* In case we copied null char, remove it from final len */
    if (Len == LenWithNull)
    {
        --Len;
    }

    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetDeviceDriverBaseNameW(LPVOID ImageBase,
                         LPWSTR lpBaseName,
                         DWORD nSize)
{
    DWORD Len;
    LPSTR BaseName;

    /* Allocate internal buffer for conversion */
    BaseName = LocalAlloc(LMEM_FIXED, nSize);
    if (BaseName == 0)
    {
        return 0;
    }

    /* Call A API */
    Len = GetDeviceDriverBaseNameA(ImageBase, BaseName, nSize);
    if (Len == 0)
    {
        LocalFree(BaseName);
        return 0;
    }

    /* And convert output */
    if (MultiByteToWideChar(CP_ACP, 0, BaseName, (Len < nSize) ? Len + 1 : Len, lpBaseName, nSize) == 0)
    {
        LocalFree(BaseName);
        return 0;
    }

    LocalFree(BaseName);
    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetDeviceDriverFileNameW(LPVOID ImageBase,
                         LPWSTR lpFilename,
                         DWORD nSize)
{
    DWORD Len;
    LPSTR FileName;

    /* Allocate internal buffer for conversion */
    FileName = LocalAlloc(LMEM_FIXED, nSize);
    if (FileName == 0)
    {
        return 0;
    }

    /* Call A API */
    Len = GetDeviceDriverFileNameA(ImageBase, FileName, nSize);
    if (Len == 0)
    {
        LocalFree(FileName);
        return 0;
    }

    /* And convert output */
    if (MultiByteToWideChar(CP_ACP, 0, FileName, (Len < nSize) ? Len + 1 : Len, lpFilename, nSize) == 0)
    {
        LocalFree(FileName);
        return 0;
    }

    LocalFree(FileName);
    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetMappedFileNameA(HANDLE hProcess,
                   LPVOID lpv,
                   LPSTR lpFilename,
                   DWORD nSize)
{
    DWORD Len;
    LPWSTR FileName;

    DPRINT("GetMappedFileNameA(%p, %p, %p, %lu)\n", hProcess, lpv, lpFilename, nSize);

    /* Allocate internal buffer for conversion */
    FileName = LocalAlloc(LMEM_FIXED, nSize * sizeof(WCHAR));
    if (FileName == NULL)
    {
        return 0;
    }

    /* Call W API */
    Len = GetMappedFileNameW(hProcess, lpv, FileName, nSize);

    /* And convert output */
    if (WideCharToMultiByte(CP_ACP, 0, FileName, (Len < nSize) ? Len + 1 : Len, lpFilename, nSize, NULL, NULL) == 0)
    {
        Len = 0;
    }

    LocalFree(FileName);
    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetMappedFileNameW(HANDLE hProcess,
                   LPVOID lpv,
                   LPWSTR lpFilename,
                   DWORD nSize)
{
    DWORD Len;
    DWORD OutSize;
    NTSTATUS Status;
    struct
    {
        MEMORY_SECTION_NAME;
        WCHAR CharBuffer[MAX_PATH];
    } SectionName;

    DPRINT("GetMappedFileNameW(%p, %p, %p, %lu)\n", hProcess, lpv, lpFilename, nSize);

    /* If no buffer, no need to keep going on */
    if (nSize == 0)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return 0;
    }

    /* Query section name */
    Status = NtQueryVirtualMemory(hProcess, lpv, MemorySectionName,
                                  &SectionName, sizeof(SectionName), &OutSize);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return 0;
    }

    /* Prepare to copy file name */
    Len =
    OutSize = SectionName.SectionFileName.Length / sizeof(WCHAR);
    if (OutSize + 1 > nSize)
    {
        Len = nSize - 1;
        OutSize = nSize;
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
    }
    else
    {
        SetLastError(ERROR_SUCCESS);
    }

    /* Copy, zero and return */
    memcpy(lpFilename, SectionName.SectionFileName.Buffer, Len * sizeof(WCHAR));
    lpFilename[Len] = 0;

    return OutSize;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetModuleBaseNameA(HANDLE hProcess,
                   HMODULE hModule,
                   LPSTR lpBaseName,
                   DWORD nSize)
{
    DWORD Len;
    PWSTR BaseName;

    /* Allocate internal buffer for conversion */
    BaseName = LocalAlloc(LMEM_FIXED, nSize * sizeof(WCHAR));
    if (BaseName == NULL)
    {
        return 0;
    }

    /* Call W API */
    Len = GetModuleBaseNameW(hProcess, hModule, BaseName, nSize);
    /* And convert output */
    if (WideCharToMultiByte(CP_ACP, 0, BaseName, (Len < nSize) ? Len + 1 : Len, lpBaseName, nSize, NULL, NULL) == 0)
    {
        Len = 0;
    }

    LocalFree(BaseName);

    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetModuleBaseNameW(HANDLE hProcess,
                   HMODULE hModule,
                   LPWSTR lpBaseName,
                   DWORD nSize)
{
    DWORD Len;
    LDR_DATA_TABLE_ENTRY Module;

    /* Get the matching module */
    if (!FindModule(hProcess, hModule, &Module))
    {
        return 0;
    }

    /* Get the maximum len we have/can write in given size */
    Len = Module.BaseDllName.Length + sizeof(UNICODE_NULL);
    if (nSize * sizeof(WCHAR) < Len)
    {
        Len = nSize * sizeof(WCHAR);
    }

    /* Read string */
    if (!ReadProcessMemory(hProcess, (&Module.BaseDllName)->Buffer, lpBaseName, Len, NULL))
    {
        return 0;
    }

    /* If we are at the end of the string, prepare to override to nullify string */
    if (Len == Module.BaseDllName.Length + sizeof(UNICODE_NULL))
    {
        Len -= sizeof(UNICODE_NULL);
    }

    /* Nullify at the end if needed */
    if (Len >= nSize * sizeof(WCHAR))
    {
        if (nSize)
        {
            ASSERT(nSize >= sizeof(UNICODE_NULL));
            lpBaseName[nSize - 1] = UNICODE_NULL;
        }
    }
    /* Otherwise, nullify at last writen char */
    else
    {
        ASSERT(Len + sizeof(UNICODE_NULL) <= nSize * sizeof(WCHAR));
        lpBaseName[Len / sizeof(WCHAR)] = UNICODE_NULL;
    }

    return Len / sizeof(WCHAR);
}


/*
 * @implemented
 */
DWORD
WINAPI
GetModuleFileNameExA(HANDLE hProcess,
                     HMODULE hModule,
                     LPSTR lpFilename,
                     DWORD nSize)
{
    DWORD Len;
    PWSTR Filename;

    /* Allocate internal buffer for conversion */
    Filename = LocalAlloc(LMEM_FIXED, nSize * sizeof(WCHAR));
    if (Filename == NULL)
    {
        return 0;
    }

    /* Call W API */
    Len = GetModuleFileNameExW(hProcess, hModule, Filename, nSize);
    /* And convert output */
    if (WideCharToMultiByte(CP_ACP, 0, Filename, (Len < nSize) ? Len + 1 : Len, lpFilename, nSize, NULL, NULL) == 0)
    {
        Len = 0;
    }

    LocalFree(Filename);

    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetModuleFileNameExW(HANDLE hProcess,
                     HMODULE hModule,
                     LPWSTR lpFilename,
                     DWORD nSize)
{
    DWORD Len;
    LDR_DATA_TABLE_ENTRY Module;

    /* Get the matching module */
    if (!FindModule(hProcess, hModule, &Module))
    {
        return 0;
    }

    /* Get the maximum len we have/can write in given size */
    Len = Module.FullDllName.Length + sizeof(UNICODE_NULL);
    if (nSize * sizeof(WCHAR) < Len)
    {
        Len = nSize * sizeof(WCHAR);
    }

    /* Read string */
    if (!ReadProcessMemory(hProcess, (&Module.FullDllName)->Buffer, lpFilename, Len, NULL))
    {
        return 0;
    }

    /* If we are at the end of the string, prepare to override to nullify string */
    if (Len == Module.FullDllName.Length + sizeof(UNICODE_NULL))
    {
        Len -= sizeof(UNICODE_NULL);
    }

    /* Nullify at the end if needed */
    if (Len >= nSize * sizeof(WCHAR))
    {
        if (nSize)
        {
            ASSERT(nSize >= sizeof(UNICODE_NULL));
            lpFilename[nSize - 1] = UNICODE_NULL;
        }
    }
    /* Otherwise, nullify at last writen char */
    else
    {
        ASSERT(Len + sizeof(UNICODE_NULL) <= nSize * sizeof(WCHAR));
        lpFilename[Len / sizeof(WCHAR)] = UNICODE_NULL;
    }

    return Len / sizeof(WCHAR);
}


/*
 * @implemented
 */
BOOL
WINAPI
GetModuleInformation(HANDLE hProcess,
                     HMODULE hModule,
                     LPMODULEINFO lpmodinfo,
                     DWORD cb)
{
    MODULEINFO LocalInfo;
    LDR_DATA_TABLE_ENTRY Module;

    /* Check output size */
    if (cb < sizeof(MODULEINFO))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    /* Get the matching module */
    if (!FindModule(hProcess, hModule, &Module))
    {
        return FALSE;
    }

    /* Get a local copy first, to check for valid pointer once */
    LocalInfo.lpBaseOfDll = hModule;
    LocalInfo.SizeOfImage = Module.SizeOfImage;
    LocalInfo.EntryPoint = Module.EntryPoint;

    /* Attempt to copy to output */
    _SEH2_TRY
    {
        memcpy(lpmodinfo, &LocalInfo, sizeof(LocalInfo));
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
        _SEH2_YIELD(return FALSE);
    }
    _SEH2_END;

    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
InitializeProcessForWsWatch(HANDLE hProcess)
{
    NTSTATUS Status;

    /* Simply forward the call */
    Status = NtSetInformationProcess(hProcess,
                                     ProcessWorkingSetWatch,
                                     NULL,
                                     0);
    /* In case the function returns this, MS considers the call as a success */
    if (NT_SUCCESS(Status) || Status == STATUS_PORT_ALREADY_SET || Status == STATUS_ACCESS_DENIED)
    {
        return TRUE;
    }

    SetLastError(RtlNtStatusToDosError(Status));
    return FALSE;
}


/*
 * @implemented
 */
BOOL
WINAPI
GetWsChanges(HANDLE hProcess,
             PPSAPI_WS_WATCH_INFORMATION lpWatchInfo,
             DWORD cb)
{
    NTSTATUS Status;

    /* Simply forward the call */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessWorkingSetWatch,
                                       lpWatchInfo,
                                       cb,
                                       NULL);
    if(!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    return TRUE;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetProcessImageFileNameW(HANDLE hProcess,
                         LPWSTR lpImageFileName,
                         DWORD nSize)
{
    PUNICODE_STRING ImageFileName;
    SIZE_T BufferSize;
    NTSTATUS Status;
    DWORD Len;

    /* Allocate string big enough to hold name */
    BufferSize = sizeof(UNICODE_STRING) + (nSize * sizeof(WCHAR));
    ImageFileName = LocalAlloc(LMEM_FIXED, BufferSize);
    if (ImageFileName == NULL)
    {
        return 0;
    }

    /* Query name */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessImageFileName,
                                       ImageFileName,
                                       BufferSize,
                                       NULL);
    /* Len mismatch => buffer too small */
    if (Status == STATUS_INFO_LENGTH_MISMATCH)
    {
        Status = STATUS_BUFFER_TOO_SMALL;
    }
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        LocalFree(ImageFileName);
        return 0;
    }

    /* Copy name and null-terminate if possible */
    memcpy(lpImageFileName, ImageFileName->Buffer, ImageFileName->Length);
    Len = ImageFileName->Length / sizeof(WCHAR);
    if (Len < nSize)
    {
        lpImageFileName[Len] = UNICODE_NULL;
    }

    LocalFree(ImageFileName);
    return Len;
}


/*
 * @implemented
 */
DWORD
WINAPI
GetProcessImageFileNameA(HANDLE hProcess,
                         LPSTR lpImageFileName,
                         DWORD nSize)
{
    PUNICODE_STRING ImageFileName;
    SIZE_T BufferSize;
    NTSTATUS Status;
    DWORD Len;

    /* Allocate string big enough to hold name */
    BufferSize = sizeof(UNICODE_STRING) + (nSize * sizeof(WCHAR));
    ImageFileName = LocalAlloc(LMEM_FIXED, BufferSize);
    if (ImageFileName == NULL)
    {
        return 0;
    }

    /* Query name */
    Status = NtQueryInformationProcess(hProcess,
                                       ProcessImageFileName,
                                       ImageFileName,
                                       BufferSize,
                                       NULL);
    /* Len mismatch => buffer too small */
    if (Status == STATUS_INFO_LENGTH_MISMATCH)
    {
        Status = STATUS_BUFFER_TOO_SMALL;
    }
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        LocalFree(ImageFileName);
        return 0;
    }

    /* Copy name */
    Len = WideCharToMultiByte(CP_ACP, 0, ImageFileName->Buffer,
                              ImageFileName->Length, lpImageFileName, nSize, NULL, NULL);
    /* If conversion was successfull, don't return len with added \0 */
    if (Len != 0)
    {
        Len -= sizeof(ANSI_NULL);
    }

    LocalFree(ImageFileName);
    return Len;
}


/*
 * @implemented
 */
BOOL
WINAPI
EnumPageFilesA(PENUM_PAGE_FILE_CALLBACKA pCallbackRoutine,
               LPVOID lpContext)
{
    BOOL Ret;
    INTERNAL_ENUM_PAGE_FILES_CONTEXT Context;

    Context.dwErrCode = ERROR_SUCCESS;
    Context.lpContext = lpContext;
    Context.pCallbackRoutine = pCallbackRoutine;

    /* Call W with our own callback for W -> A conversions */
    Ret = EnumPageFilesW(CallBackConvertToAscii, &Context);
    /* If we succeed but we have error code, fail and set error */
    if (Ret && Context.dwErrCode != ERROR_SUCCESS)
    {
        Ret = FALSE;
        SetLastError(Context.dwErrCode);
    }

    return Ret;
}


/*
 * @implemented
 */
BOOL
WINAPI
EnumPageFilesW(PENUM_PAGE_FILE_CALLBACKW pCallbackRoutine,
               LPVOID lpContext)
{
    PWSTR Colon;
    NTSTATUS Status;
    DWORD Size = INIT_MEMORY_SIZE, Needed;
    ENUM_PAGE_FILE_INFORMATION Information;
    PSYSTEM_PAGEFILE_INFORMATION PageFileInfoArray, PageFileInfo;

    /* First loop till we have all the information about page files */
    do
    {
        PageFileInfoArray = LocalAlloc(LMEM_FIXED, Size);
        if (PageFileInfoArray == NULL)
        {
            SetLastError(RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES));
            return FALSE;
        }

        Status = NtQuerySystemInformation(SystemPageFileInformation, PageFileInfoArray, Size, &Needed);
        if (NT_SUCCESS(Status))
        {
            break;
        }

        LocalFree(PageFileInfoArray);

        /* In case we have unexpected status, quit */
        if (Status != STATUS_INFO_LENGTH_MISMATCH)
        {
            SetLastError(RtlNtStatusToDosError(Status));
            return FALSE;
        }

        /* If needed size is smaller than actual size, guess it's something to add to our current size */
        if (Needed <= Size)
        {
            Size += Needed;
        }
        /* Otherwise, take it as size to allocate */
        else
        {
            Size = Needed;
        }
    }
    while (TRUE);

    /* Start browsing all our entries */
    PageFileInfo = PageFileInfoArray;
    do
    {
        /* Ensure we really have an entry */
        if (Needed < sizeof(SYSTEM_PAGEFILE_INFORMATION))
        {
            break;
        }

        /* Prepare structure to hand to the user */
        Information.Reserved = 0;
        Information.cb = sizeof(Information);
        Information.TotalSize = PageFileInfo->TotalSize;
        Information.TotalInUse = PageFileInfo->TotalInUse;
        Information.PeakUsage = PageFileInfo->PeakUsage;

        /* Search for colon */
        Colon = wcschr(PageFileInfo->PageFileName.Buffer, L':');
        /* If it's found and not at the begin of the string */
        if (Colon != 0 && Colon != PageFileInfo->PageFileName.Buffer)
        {
            /* We can call the user callback routine with the colon */
            --Colon;
            pCallbackRoutine(lpContext, &Information, Colon);
        }

        /* If no next entry, then, it's over */
        if (PageFileInfo->NextEntryOffset == 0 || PageFileInfo->NextEntryOffset > Needed)
        {
            break;
        }

        /* Jump to next entry while keeping accurate bytes left count */
        Needed -= PageFileInfo->NextEntryOffset;
        PageFileInfo = (PSYSTEM_PAGEFILE_INFORMATION)((ULONG_PTR)PageFileInfo + PageFileInfo->NextEntryOffset);
    }
    while (TRUE);

    LocalFree(PageFileInfoArray);
    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
GetPerformanceInfo(PPERFORMANCE_INFORMATION pPerformanceInformation,
                   DWORD cb)
{
    NTSTATUS Status;
    SYSTEM_BASIC_INFORMATION SystemBasicInfo;
    SYSTEM_PERFORMANCE_INFORMATION SystemPerfInfo;
    SYSTEM_FILECACHE_INFORMATION SystemFileCacheInfo;
    PSYSTEM_PROCESS_INFORMATION ProcInfoArray, SystemProcInfo;
    DWORD Size = INIT_MEMORY_SIZE, Needed, ProcCount, ThreadsCount, HandleCount;

    /* Validate output buffer */
    if (cb < sizeof(PERFORMANCE_INFORMATION))
    {
        SetLastError(RtlNtStatusToDosError(STATUS_INFO_LENGTH_MISMATCH));
        return FALSE;
    }

    /* First, gather as many information about the system as possible */
    Status = NtQuerySystemInformation(SystemBasicInformation,
                                      &SystemBasicInfo,
                                      sizeof(SystemBasicInfo),
                                      NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    Status = NtQuerySystemInformation(SystemPerformanceInformation,
                                      &SystemPerfInfo,
                                      sizeof(SystemPerfInfo),
                                      NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    Status = NtQuerySystemInformation(SystemFileCacheInformation,
                                      &SystemFileCacheInfo,
                                      sizeof(SystemFileCacheInfo),
                                      NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    /* Then loop till we have all the information about processes */
    do
    {
        ProcInfoArray = LocalAlloc(LMEM_FIXED, Size);
        if (ProcInfoArray == NULL)
        {
            SetLastError(RtlNtStatusToDosError(STATUS_INSUFFICIENT_RESOURCES));
            return FALSE;
        }

        Status = NtQuerySystemInformation(SystemProcessInformation,
                                          ProcInfoArray,
                                          Size,
                                          &Needed);
        if (NT_SUCCESS(Status))
        {
            break;
        }

        LocalFree(ProcInfoArray);

        /* In case we have unexpected status, quit */
        if (Status != STATUS_INFO_LENGTH_MISMATCH)
        {
            SetLastError(RtlNtStatusToDosError(Status));
            return FALSE;
        }

        /* If needed size is smaller than actual size, guess it's something to add to our current size */
        if (Needed <= Size)
        {
            Size += Needed;
        }
        /* Otherwise, take it as size to allocate */
        else
        {
            Size = Needed;
        }
    } while (TRUE);

    /* Start browsing all our entries */
    ProcCount = 0;
    HandleCount = 0;
    ThreadsCount = 0;
    SystemProcInfo = ProcInfoArray;
    do
    {
        /* Ensure we really have an entry */
        if (Needed < sizeof(SYSTEM_PROCESS_INFORMATION))
        {
            break;
        }

        /* Sum procs, threads and handles */
        ++ProcCount;
        ThreadsCount += SystemProcInfo->NumberOfThreads;
        HandleCount += SystemProcInfo->HandleCount;

        /* If no next entry, then, it's over */
        if (SystemProcInfo->NextEntryOffset == 0 || SystemProcInfo->NextEntryOffset > Needed)
        {
            break;
        }

        /* Jump to next entry while keeping accurate bytes left count */
        Needed -= SystemProcInfo->NextEntryOffset;
        SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
    }
    while (TRUE);

    LocalFree(ProcInfoArray);

    /* Output data */
    pPerformanceInformation->CommitTotal = SystemPerfInfo.CommittedPages;
    pPerformanceInformation->CommitLimit = SystemPerfInfo.CommitLimit;
    pPerformanceInformation->CommitPeak = SystemPerfInfo.PeakCommitment;
    pPerformanceInformation->PhysicalTotal = SystemBasicInfo.NumberOfPhysicalPages;
    pPerformanceInformation->PhysicalAvailable = SystemPerfInfo.AvailablePages;
    pPerformanceInformation->SystemCache = SystemFileCacheInfo.CurrentSizeIncludingTransitionInPages;
    pPerformanceInformation->KernelNonpaged = SystemPerfInfo.NonPagedPoolPages;
    pPerformanceInformation->PageSize = SystemBasicInfo.PageSize;
    pPerformanceInformation->cb = sizeof(PERFORMANCE_INFORMATION);
    pPerformanceInformation->KernelTotal = SystemPerfInfo.PagedPoolPages + SystemPerfInfo.NonPagedPoolPages;
    pPerformanceInformation->KernelPaged = SystemPerfInfo.PagedPoolPages;
    pPerformanceInformation->HandleCount = HandleCount;
    pPerformanceInformation->ProcessCount = ProcCount;
    pPerformanceInformation->ThreadCount = ThreadsCount;

    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
GetProcessMemoryInfo(HANDLE Process,
                     PPROCESS_MEMORY_COUNTERS ppsmemCounters,
                     DWORD cb)
{
    NTSTATUS Status;
    VM_COUNTERS_EX Counters;

    /* Validate output size
     * It can be either PROCESS_MEMORY_COUNTERS or PROCESS_MEMORY_COUNTERS_EX
     */
    if (cb < sizeof(PROCESS_MEMORY_COUNTERS))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    _SEH2_TRY
    {
        ppsmemCounters->PeakPagefileUsage = 0;

        /* Query counters */
        Status = NtQueryInformationProcess(Process,
                                           ProcessVmCounters,
                                           &Counters,
                                           sizeof(Counters),
                                           NULL);
        if (!NT_SUCCESS(Status))
        {
            SetLastError(RtlNtStatusToDosError(Status));
            _SEH2_YIELD(return FALSE);
        }

        /* Properly set cb, according to what we received */
        if (cb >= sizeof(PROCESS_MEMORY_COUNTERS_EX))
        {
            ppsmemCounters->cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
        }
        else
        {
            ppsmemCounters->cb = sizeof(PROCESS_MEMORY_COUNTERS);
        }

        /* Output data */
        ppsmemCounters->PageFaultCount = Counters.PageFaultCount;
        ppsmemCounters->PeakWorkingSetSize = Counters.PeakWorkingSetSize;
        ppsmemCounters->WorkingSetSize = Counters.WorkingSetSize;
        ppsmemCounters->QuotaPeakPagedPoolUsage = Counters.QuotaPeakPagedPoolUsage;
        ppsmemCounters->QuotaPagedPoolUsage = Counters.QuotaPagedPoolUsage;
        ppsmemCounters->QuotaPeakNonPagedPoolUsage = Counters.QuotaPeakNonPagedPoolUsage;
        ppsmemCounters->QuotaNonPagedPoolUsage = Counters.QuotaNonPagedPoolUsage;
        ppsmemCounters->PagefileUsage = Counters.PagefileUsage;
        ppsmemCounters->PeakPagefileUsage = Counters.PeakPagefileUsage;
        /* And if needed, additionnal field for _EX version */
        if (cb >= sizeof(PROCESS_MEMORY_COUNTERS_EX))
        {
            ((PPROCESS_MEMORY_COUNTERS_EX)ppsmemCounters)->PrivateUsage = Counters.PrivateUsage;
        }
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(RtlNtStatusToDosError(_SEH2_GetExceptionCode()));
        _SEH2_YIELD(return FALSE);
    }
    _SEH2_END;

    return TRUE;
}


/*
 * @implemented
 */
BOOL
WINAPI
QueryWorkingSet(HANDLE hProcess,
                PVOID pv,
                DWORD cb)
{
    NTSTATUS Status;

    /* Simply forward the call */
    Status = NtQueryVirtualMemory(hProcess,
                                  NULL,
                                  MemoryWorkingSetList,
                                  pv,
                                  cb,
                                  NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    return TRUE;
}

/*
 * @implemented
 */
BOOL
WINAPI
QueryWorkingSetEx(IN HANDLE hProcess,
                  IN OUT PVOID pv,
                  IN DWORD cb)
{
    NTSTATUS Status;

    /* Simply forward the call */
    Status = NtQueryVirtualMemory(hProcess,
                                  NULL,
                                  MemoryWorkingSetExList,
                                  pv,
                                  cb,
                                  NULL);
    if (!NT_SUCCESS(Status))
    {
        SetLastError(RtlNtStatusToDosError(Status));
        return FALSE;
    }

    return TRUE;
}

/* EOF */
