/*
 * PROJECT:         ReactOS Windows-Compatible Session Manager
 * LICENSE:         BSD 2-Clause License
 * FILE:            base/system/smss/pagefile.c
 * PURPOSE:         Main SMSS Code
 * PROGRAMMERS:     Alex Ionescu
 */

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

#include "smss.h"

#define NDEBUG
#include <debug.h>

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

//
// Constants
//
#define STANDARD_PAGING_FILE_NAME       L"\\??\\?:\\pagefile.sys"
#define STANDARD_DRIVE_LETTER_OFFSET    4
#define MAX_PAGING_FILES                16  // See also ntoskrnl/include/internal/mm.h
#define MEGABYTE                        (1024 * 1024)

/* Minimum pagefile size is 256 pages (1 MB) */
// #define MINIMUM_PAGEFILE_SIZE           (256ULL * PAGE_SIZE)

/* Maximum pagefile sizes for different architectures */
#define GIGABYTE                        (1024ULL * MEGABYTE)
#define TERABYTE                        (1024ULL * GIGABYTE)

// NOTE: No changes for NTDDI_WIN10
#if (NTDDI_VERSION >= NTDDI_WINBLUE) // NTDDI_WIN81
#define MAXIMUM_PAGEFILE_SIZE32         ((1ULL * 1024 * 1024 - 1) * PAGE_SIZE)
                                     // PAGE_ROUND_DOWN(4ULL * GIGABYTE - 1)
#else
/* 4095 MB */
#define MAXIMUM_PAGEFILE_SIZE32         (4095ULL * MEGABYTE)
#endif

// NOTE: No changes for NTDDI_WIN10
#if (NTDDI_VERSION >= NTDDI_WINBLUE) // NTDDI_WIN81
#define MAXIMUM_PAGEFILE_SIZE64         ((4ULL * 1024 * 1024 * 1024 - 1) * PAGE_SIZE)
                                     // PAGE_ROUND_DOWN(16ULL * TERABYTE - 1)
#else
/* 16 TB */
#define MAXIMUM_PAGEFILE_SIZE64         (16ULL * TERABYTE)
#endif

#if defined(_M_IX86)
    #define MAXIMUM_PAGEFILE_SIZE       MAXIMUM_PAGEFILE_SIZE32
    /* PAE uses the same size as x64 */
    #define MAXIMUM_PAGEFILE_SIZE_PAE   MAXIMUM_PAGEFILE_SIZE64
#elif defined (_M_AMD64) || defined(_M_ARM64)
    #define MAXIMUM_PAGEFILE_SIZE       MAXIMUM_PAGEFILE_SIZE64
#elif defined (_M_IA64)
/* 32 TB */
    #define MAXIMUM_PAGEFILE_SIZE       (32ULL * TERABYTE)
#elif defined(_M_ARM)
/* Around 2 GB */
    // NOTE: No changes for NTDDI_WIN10
    #if (NTDDI_VERSION >= NTDDI_WINBLUE) // NTDDI_WIN81
    #define MAXIMUM_PAGEFILE_SIZE       ((512ULL * 1024 - 1) * PAGE_SIZE)
                                     // PAGE_ROUND_DOWN(2ULL * GIGABYTE - 1)
    #else
/* 4095 MB */
    #define MAXIMUM_PAGEFILE_SIZE       MAXIMUM_PAGEFILE_SIZE32
    #endif
#else
/* On unknown architectures, default to either one of the 32 or 64 bit sizes */
#pragma message("Unknown architecture")
    #ifdef _WIN64
    #define MAXIMUM_PAGEFILE_SIZE       MAXIMUM_PAGEFILE_SIZE64
    #else
    #define MAXIMUM_PAGEFILE_SIZE       MAXIMUM_PAGEFILE_SIZE32
    #endif
#endif

/* This should be 32 MB, but we need more than that for 2nd stage setup */
#define MINIMUM_TO_KEEP_FREE            (256 * MEGABYTE)
#define FUZZ_FACTOR                     (16 * MEGABYTE)

//
// Structure and flags describing each pagefile
//
#define SMP_PAGEFILE_CREATED            0x01
#define SMP_PAGEFILE_DEFAULT            0x02
#define SMP_PAGEFILE_SYSTEM_MANAGED     0x04
#define SMP_PAGEFILE_WAS_TOO_BIG        0x08
#define SMP_PAGEFILE_ON_ANY_DRIVE       0x10
#define SMP_PAGEFILE_EMERGENCY          0x20
#define SMP_PAGEFILE_DUMP_PROCESSED     0x40
typedef struct _SMP_PAGEFILE_DESCRIPTOR
{
    LIST_ENTRY Entry;
    UNICODE_STRING Name;
    UNICODE_STRING Token;
    LARGE_INTEGER MinSize;
    LARGE_INTEGER MaxSize;
    LARGE_INTEGER ActualMinSize;
    LARGE_INTEGER ActualMaxSize;
    ULONG Flags;
} SMP_PAGEFILE_DESCRIPTOR, *PSMP_PAGEFILE_DESCRIPTOR;

//
// Structure and flags describing each volume
//
#define SMP_VOLUME_INSERTED             0x01
#define SMP_VOLUME_PAGEFILE_CREATED     0x04
#define SMP_VOLUME_IS_BOOT              0x08
typedef struct _SMP_VOLUME_DESCRIPTOR
{
    LIST_ENTRY Entry;
    USHORT Flags;
    USHORT PageFileCount;
    WCHAR DriveLetter;
    LARGE_INTEGER FreeSpace;
    FILE_FS_DEVICE_INFORMATION DeviceInfo;
} SMP_VOLUME_DESCRIPTOR, *PSMP_VOLUME_DESCRIPTOR;

LIST_ENTRY SmpPagingFileDescriptorList, SmpVolumeDescriptorList;
BOOLEAN SmpRegistrySpecifierPresent;
ULONG SmpNumberOfPagingFiles;

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

VOID
NTAPI
SmpPagingFileInitialize(VOID)
{
    /* Initialize the two lists */
    InitializeListHead(&SmpPagingFileDescriptorList);
    InitializeListHead(&SmpVolumeDescriptorList);
}

NTSTATUS
NTAPI
SmpCreatePagingFileDescriptor(IN PUNICODE_STRING PageFileToken)
{
    NTSTATUS Status;
    ULONG MinSize = 0, MaxSize = 0;
    BOOLEAN SystemManaged = FALSE, ZeroSize = TRUE;
    PSMP_PAGEFILE_DESCRIPTOR Descriptor, ListDescriptor;
    ULONG i;
    WCHAR c;
    PLIST_ENTRY NextEntry;
    UNICODE_STRING PageFileName, Arguments, SecondArgument;

    /* Make sure we don't have too many */
    if (SmpNumberOfPagingFiles >= MAX_PAGING_FILES)
    {
        DPRINT1("SMSS:PFILE: Too many paging files specified - %lu\n",
                SmpNumberOfPagingFiles);
        return STATUS_TOO_MANY_PAGING_FILES;
    }

    /* Parse the specified and get the name and arguments out of it */
    DPRINT("SMSS:PFILE: Paging file specifier `%wZ'\n", PageFileToken);
    Status = SmpParseCommandLine(PageFileToken,
                                 NULL,
                                 &PageFileName,
                                 NULL,
                                 &Arguments);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        DPRINT1("SMSS:PFILE: SmpParseCommandLine(%wZ) failed - Status == %lx\n",
                PageFileToken, Status);
        return Status;
    }

    /* Set the variable to let everyone know we have a pagefile token */
    SmpRegistrySpecifierPresent = TRUE;

    /* Parse the arguments, if any */
    if (Arguments.Buffer)
    {
        /* Parse the pagefile size */
        for (i = 0; i < Arguments.Length / sizeof(WCHAR); i++)
        {
            /* Check if it's zero */
            c = Arguments.Buffer[i];
            if ((c != L' ') && (c != L'\t') && (c != L'0'))
            {
                /* It isn't, break out */
                ZeroSize = FALSE;
                break;
            }
        }
    }

    /* Was a pagefile not specified, or was it specified with no size? */
    if (!(Arguments.Buffer) || (ZeroSize))
    {
        /* In this case, the system will manage its size */
        SystemManaged = TRUE;
    }
    else
    {
        /* We do have a size, so convert the arguments into a number */
        Status = RtlUnicodeStringToInteger(&Arguments, 0, &MinSize);
        if (!NT_SUCCESS(Status))
        {
            /* Fail */
            RtlFreeUnicodeString(&PageFileName);
            RtlFreeUnicodeString(&Arguments);
            return Status;
        }

        /* Now advance to the next argument */
        for (i = 0; i < Arguments.Length / sizeof(WCHAR); i++)
        {
            /* Found a space -- second argument must start here */
            if (Arguments.Buffer[i] == L' ')
            {
                /* Use the rest of the arguments as a maximum size */
                SecondArgument.Buffer = &Arguments.Buffer[i];
                SecondArgument.Length = (USHORT)(Arguments.Length -
                                        i * sizeof(WCHAR));
                SecondArgument.MaximumLength = (USHORT)(Arguments.MaximumLength -
                                               i * sizeof(WCHAR));
                Status = RtlUnicodeStringToInteger(&SecondArgument, 0, &MaxSize);
                if (!NT_SUCCESS(Status))
                {
                    /* Fail */
                    RtlFreeUnicodeString(&PageFileName);
                    RtlFreeUnicodeString(&Arguments);
                    return Status;
                }

                break;
            }
        }
    }

    /* We are done parsing arguments */
    RtlFreeUnicodeString(&Arguments);

    /* Now we can allocate our descriptor */
    Descriptor = RtlAllocateHeap(RtlGetProcessHeap(),
                                 HEAP_ZERO_MEMORY,
                                 sizeof(SMP_PAGEFILE_DESCRIPTOR));
    if (!Descriptor)
    {
        /* Fail if we couldn't */
        RtlFreeUnicodeString(&PageFileName);
        return STATUS_NO_MEMORY;
    }

    /* Capture all our data into the descriptor */
    Descriptor->Token = *PageFileToken;
    Descriptor->Name = PageFileName;
    Descriptor->MinSize.QuadPart = MinSize * MEGABYTE;
    Descriptor->MaxSize.QuadPart = MaxSize * MEGABYTE;
    if (SystemManaged)
        Descriptor->Flags |= SMP_PAGEFILE_SYSTEM_MANAGED;
    Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] =
    RtlUpcaseUnicodeChar(Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET]);
    if (Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] == '?')
    {
        Descriptor->Flags |= SMP_PAGEFILE_ON_ANY_DRIVE;
    }

    /* Now loop the existing descriptors */
    NextEntry = SmpPagingFileDescriptorList.Flink;
    do
    {
        /* Are there none, or have we looped back to the beginning? */
        if (NextEntry == &SmpPagingFileDescriptorList)
        {
            /* This means no duplicates exist, so insert our descriptor! */
            InsertTailList(&SmpPagingFileDescriptorList, &Descriptor->Entry);
            SmpNumberOfPagingFiles++;
            DPRINT("SMSS:PFILE: Created descriptor for `%wZ' (`%wZ')\n",
                    PageFileToken, &Descriptor->Name);
            return STATUS_SUCCESS;
        }

        /* Keep going until we find a duplicate, unless we are in "any" mode */
        ListDescriptor = CONTAINING_RECORD(NextEntry, SMP_PAGEFILE_DESCRIPTOR, Entry);
        NextEntry = NextEntry->Flink;
    } while (!(ListDescriptor->Flags & SMP_PAGEFILE_ON_ANY_DRIVE) ||
             !(Descriptor->Flags & SMP_PAGEFILE_ON_ANY_DRIVE));

    /* We found a duplicate, so skip this descriptor/pagefile and fail */
    DPRINT1("SMSS:PFILE: Skipping duplicate specifier `%wZ'\n", PageFileToken);
    RtlFreeUnicodeString(&PageFileName);
    RtlFreeHeap(RtlGetProcessHeap(), 0, Descriptor);
    return STATUS_INVALID_PARAMETER;
}

NTSTATUS
NTAPI
SmpGetPagingFileSize(IN PUNICODE_STRING FileName,
                     OUT PLARGE_INTEGER Size)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE FileHandle;
    FILE_STANDARD_INFORMATION StandardInfo;

    DPRINT("SMSS:PFILE: Trying to get size for `%wZ'\n", FileName);
    Size->QuadPart = 0;

    InitializeObjectAttributes(&ObjectAttributes,
                               FileName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenFile(&FileHandle,
                        FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_SYNCHRONOUS_IO_NONALERT);
    if (!NT_SUCCESS(Status)) return Status;

    Status = NtQueryInformationFile(FileHandle,
                                    &IoStatusBlock,
                                    &StandardInfo,
                                    sizeof(StandardInfo),
                                    FileStandardInformation);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("SMSS:PFILE: Failed query for size potential pagefile `%wZ' with status %X\n",
                FileName, Status);
        NtClose(FileHandle);
        return Status;
    }

    NtClose(FileHandle);
    Size->QuadPart = StandardInfo.AllocationSize.QuadPart;
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
SmpDeletePagingFile(IN PUNICODE_STRING FileName)
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE FileHandle;
    FILE_DISPOSITION_INFORMATION Disposition;

    /* Open the page file */
    InitializeObjectAttributes(&ObjectAttributes,
                               FileName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenFile(&FileHandle,
                        DELETE,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_NON_DIRECTORY_FILE);
    if (NT_SUCCESS(Status))
    {
        /* Delete it */
        Disposition.DeleteFile = TRUE;
        Status = NtSetInformationFile(FileHandle,
                                      &IoStatusBlock,
                                      &Disposition,
                                      sizeof(Disposition),
                                      FileDispositionInformation);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("SMSS:PFILE: Failed to delete page file `%wZ' (status %X)\n",
                    FileName, Status);
        }
        else
        {
            DPRINT("SMSS:PFILE: Deleted stale paging file - %wZ\n", FileName);
        }

        /* Close the handle */
        NtClose(FileHandle);
    }
    else
    {
        DPRINT1("SMSS:PFILE: Failed to open for deletion page file `%wZ' (status %X)\n",
                FileName, Status);
    }

    /* All done */
    return Status;
}

NTSTATUS
NTAPI
SmpGetVolumeFreeSpace(IN PSMP_VOLUME_DESCRIPTOR Volume)
{
    NTSTATUS Status;
    LARGE_INTEGER FreeSpace, FinalFreeSpace;
    FILE_FS_SIZE_INFORMATION SizeInfo;
    IO_STATUS_BLOCK IoStatusBlock;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING VolumeName;
    HANDLE VolumeHandle;
    WCHAR PathString[32];
    ASSERT(Volume->Flags & SMP_VOLUME_IS_BOOT); // ASSERT says "BootVolume == 1"

    /* Build the standard path */
    wcscpy(PathString, L"\\??\\A:\\");
    RtlInitUnicodeString(&VolumeName, PathString);
    VolumeName.Buffer[STANDARD_DRIVE_LETTER_OFFSET] = Volume->DriveLetter;
    DPRINT("SMSS:PFILE: Querying volume `%wZ' for free space\n", &VolumeName);

    /* Open the volume */
    InitializeObjectAttributes(&ObjectAttributes,
                               &VolumeName,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenFile(&VolumeHandle,
                        FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("SMSS:PFILE: Open volume `%wZ' failed with status %X\n", &VolumeName, Status);
        return Status;
    }

    /* Now get size information on the volume */
    Status = NtQueryVolumeInformationFile(VolumeHandle,
                                          &IoStatusBlock,
                                          &SizeInfo,
                                          sizeof(SizeInfo),
                                          FileFsSizeInformation);
    if (!NT_SUCCESS(Status))
    {
        /* We failed */
        DPRINT1("SMSS:PFILE: Query volume `%wZ' (handle %p) for size failed"
                " with status %X\n",
                &VolumeName,
                VolumeHandle,
                Status);
        NtClose(VolumeHandle);
        return Status;
    }
    NtClose(VolumeHandle);

    /* Compute how much free space we have */
    FreeSpace.QuadPart = SizeInfo.AvailableAllocationUnits.QuadPart *
                         SizeInfo.SectorsPerAllocationUnit;
    FinalFreeSpace.QuadPart = FreeSpace.QuadPart * SizeInfo.BytesPerSector;

    /* Check if there is less than 32 MB free so we don't starve the disk */
    if (FinalFreeSpace.QuadPart <= MINIMUM_TO_KEEP_FREE)
    {
        /* In this case, act as if there is no free space */
        Volume->FreeSpace.QuadPart = 0;
    }
    else
    {
        /* Trim off 32 MB to give the disk a bit of breathing room */
        Volume->FreeSpace.QuadPart = FinalFreeSpace.QuadPart -
                                     MINIMUM_TO_KEEP_FREE;
    }

    return STATUS_SUCCESS;
}

PSMP_VOLUME_DESCRIPTOR
NTAPI
SmpSearchVolumeDescriptor(IN WCHAR DriveLetter)
{
    WCHAR UpLetter;
    PSMP_VOLUME_DESCRIPTOR Volume = NULL;
    PLIST_ENTRY NextEntry;

    /* Use upper case to reduce differences */
    UpLetter = RtlUpcaseUnicodeChar(DriveLetter);

    /* Loop each volume */
    NextEntry = SmpVolumeDescriptorList.Flink;
    while (NextEntry != &SmpVolumeDescriptorList)
    {
        /* Grab the entry */
        Volume = CONTAINING_RECORD(NextEntry, SMP_VOLUME_DESCRIPTOR, Entry);

        /* Make sure it's a valid entry with an uppcase drive letter */
        ASSERT(Volume->Flags & SMP_VOLUME_INSERTED); // Volume->Initialized in ASSERT
        ASSERT(Volume->DriveLetter >= L'A' && Volume->DriveLetter <= L'Z');

        /* Break if it matches, if not, keep going */
        if (Volume->DriveLetter == UpLetter) break;
        NextEntry = NextEntry->Flink;
    }

    /* Return the volume if one was found */
    if (NextEntry == &SmpVolumeDescriptorList) Volume = NULL;
    return Volume;
}

NTSTATUS
NTAPI
SmpCreatePagingFile(IN PUNICODE_STRING Name,
                    IN PLARGE_INTEGER MinSize,
                    IN PLARGE_INTEGER MaxSize,
                    IN ULONG Priority)
{
    NTSTATUS Status;

    /* Tell the kernel to create the pagefile */
    Status = NtCreatePagingFile(Name, MinSize, MaxSize, Priority);
    if (NT_SUCCESS(Status))
    {
        DPRINT("SMSS:PFILE: NtCreatePagingFile(%wZ, 0x%I64X, 0x%I64X) succeeded\n",
                Name,
                MinSize->QuadPart,
                MaxSize->QuadPart);
    }
    else
    {
        DPRINT1("SMSS:PFILE: NtCreatePagingFile(%wZ, 0x%I64X, 0x%I64X) failed with %X\n",
                Name,
                MinSize->QuadPart,
                MaxSize->QuadPart,
                Status);
    }

    /* Return the status */
    return Status;
}

NTSTATUS
NTAPI
SmpCreatePagingFileOnFixedDrive(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor,
                                IN PLARGE_INTEGER FuzzFactor,
                                IN PLARGE_INTEGER MinimumSize)
{
    PSMP_VOLUME_DESCRIPTOR Volume;
    BOOLEAN ShouldDelete;
    NTSTATUS Status;
    LARGE_INTEGER PageFileSize;
    ASSERT(Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] != L'?');

    /* Try to find the volume descriptor for this drive letter */
    ShouldDelete = FALSE;
    Volume = SmpSearchVolumeDescriptor(Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET]);
    if (!Volume)
    {
        /* Couldn't find it, fail */
        DPRINT1("SMSS:PFILE: No volume descriptor for `%wZ'\n",
                &Descriptor->Name);
        return STATUS_INVALID_PARAMETER;
    }

    /* Check if this is the boot volume */
    if (Volume->Flags & SMP_VOLUME_IS_BOOT)
    {
        /* Check if we haven't yet processed a crash dump on this volume */
        if (!(Descriptor->Flags & SMP_PAGEFILE_DUMP_PROCESSED))
        {
            /* Try to find a crash dump and extract it */
            DPRINT("SMSS:PFILE: Checking for crash dump in `%wZ' on boot volume\n",
                    &Descriptor->Name);
            SmpCheckForCrashDump(&Descriptor->Name);

            /* Update how much free space we have now that we extracted a dump */
            Status = SmpGetVolumeFreeSpace(Volume);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("SMSS:PFILE: Failed to query free space for boot volume `%wC'\n",
                        Volume->DriveLetter);
            }
            else
            {
                DPRINT("Queried free space for boot volume `%wC: 0x%I64x'\n",
                        Volume->DriveLetter, Volume->FreeSpace.QuadPart);
            }

            /* Don't process crashdump on this volume anymore */
            Descriptor->Flags |= SMP_PAGEFILE_DUMP_PROCESSED;
        }
    }
    else
    {
        /* Crashdumps can only be on the boot volume */
        DPRINT("SMSS:PFILE: Skipping crash dump checking for `%wZ' on non boot"
                "volume `%wC'\n",
                &Descriptor->Name,
                Volume->DriveLetter);
    }

    /* Update the size after dump extraction */
    Descriptor->ActualMinSize = Descriptor->MinSize;
    Descriptor->ActualMaxSize = Descriptor->MaxSize;

    /* Check how big we can make the pagefile */
    Status = SmpGetPagingFileSize(&Descriptor->Name, &PageFileSize);
    if (NT_SUCCESS(Status) && PageFileSize.QuadPart > 0) ShouldDelete = TRUE;
    DPRINT("SMSS:PFILE: Detected size 0x%I64X for future paging file `%wZ'\n",
            PageFileSize,
            &Descriptor->Name);
    DPRINT("SMSS:PFILE: Free space on volume `%wC' is 0x%I64X\n",
            Volume->DriveLetter,
            Volume->FreeSpace.QuadPart);

    /* Now update our size and make sure none of these are too big */
    PageFileSize.QuadPart += Volume->FreeSpace.QuadPart;
    if (Descriptor->ActualMinSize.QuadPart > PageFileSize.QuadPart)
    {
        Descriptor->ActualMinSize = PageFileSize;
    }
    if (Descriptor->ActualMaxSize.QuadPart > PageFileSize.QuadPart)
    {
        Descriptor->ActualMaxSize = PageFileSize;
    }
    DPRINT("SMSS:PFILE: min 0x%I64X, max 0x%I64X, real min 0x%I64X\n",
            Descriptor->ActualMinSize.QuadPart,
            Descriptor->ActualMaxSize.QuadPart,
            MinimumSize->QuadPart);

    /* Keep going until we've created a pagefile of the right size */
    while (Descriptor->ActualMinSize.QuadPart >= MinimumSize->QuadPart)
    {
        /* Call NT to do it */
        Status = SmpCreatePagingFile(&Descriptor->Name,
                                     &Descriptor->ActualMinSize,
                                     &Descriptor->ActualMaxSize,
                                     0);
        if (NT_SUCCESS(Status))
        {
            /* We're done, update flags and increase the count */
            Descriptor->Flags |= SMP_PAGEFILE_CREATED;
            Volume->Flags |= SMP_VOLUME_PAGEFILE_CREATED;
            Volume->PageFileCount++;
            break;
        }

        /* We failed, try a slightly smaller pagefile */
        Descriptor->ActualMinSize.QuadPart -= FuzzFactor->QuadPart;
    }

    /* Check if we weren't able to create it */
    if (Descriptor->ActualMinSize.QuadPart < MinimumSize->QuadPart)
    {
        /* Delete the current page file and fail */
        if (ShouldDelete)
        {
            SmpDeletePagingFile(&Descriptor->Name);

            /* FIXFIX: Windows Vista does this, and it seems like we should too, so try to see if this fixes KVM */
            Volume->FreeSpace.QuadPart = PageFileSize.QuadPart;
        }
        DPRINT1("SMSS:PFILE: Failing for min 0x%I64X, max 0x%I64X, real min 0x%I64X\n",
                Descriptor->ActualMinSize.QuadPart,
                Descriptor->ActualMaxSize.QuadPart,
                MinimumSize->QuadPart);
        Status = STATUS_DISK_FULL;
    }

    /* Return the status */
    return Status;
}

NTSTATUS
NTAPI
SmpCreatePagingFileOnAnyDrive(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor,
                              IN PLARGE_INTEGER FuzzFactor,
                              IN PLARGE_INTEGER MinimumSize)
{
    PSMP_VOLUME_DESCRIPTOR Volume;
    NTSTATUS Status = STATUS_DISK_FULL;
    PLIST_ENTRY NextEntry;
    ASSERT(Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] == L'?');

    /* Loop the volume list */
    NextEntry = SmpVolumeDescriptorList.Flink;
    while (NextEntry != &SmpVolumeDescriptorList)
    {
        /* Get the volume */
        Volume = CONTAINING_RECORD(NextEntry, SMP_VOLUME_DESCRIPTOR, Entry);

        /* Make sure it's inserted and on a valid drive letter */
        ASSERT(Volume->Flags & SMP_VOLUME_INSERTED); // Volume->Initialized in ASSERT
        ASSERT(Volume->DriveLetter >= L'A' && Volume->DriveLetter <= L'Z');

        /* Write the drive letter to try creating it on this volume */
        Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] = Volume->DriveLetter;
        Status = SmpCreatePagingFileOnFixedDrive(Descriptor,
                                                 FuzzFactor,
                                                 MinimumSize);
        if (NT_SUCCESS(Status)) break;

        /* It didn't work, make it an any pagefile again and keep going */
        Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] = L'?';
        NextEntry = NextEntry->Flink;
    }

    /* Return disk full or success */
    return Status;
}

VOID
NTAPI
SmpMakeDefaultPagingFileDescriptor(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor)
{
    /* The default descriptor uses 128 MB as a pagefile size */
    Descriptor->Flags |= SMP_PAGEFILE_DEFAULT;
    Descriptor->MinSize.QuadPart = 128 * MEGABYTE;
    Descriptor->MaxSize.QuadPart = 128 * MEGABYTE;
}

VOID
NTAPI
SmpMakeSystemManagedPagingFileDescriptor(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor)
{
    NTSTATUS Status;
    ULONGLONG MinimumSize, MaximumSize, Ram;
    SYSTEM_BASIC_INFORMATION BasicInfo;

    /* Query the page size of the system, and the amount of RAM */
    Status = NtQuerySystemInformation(SystemBasicInformation,
                                      &BasicInfo,
                                      sizeof(BasicInfo),
                                      NULL);
    if (!NT_SUCCESS(Status))
    {
        /* If we failed, use defaults since we have no idea otherwise */
        DPRINT1("SMSS:PFILE: NtQuerySystemInformation failed with %x\n", Status);
        SmpMakeDefaultPagingFileDescriptor(Descriptor);
        return;
    }

    /* Check how much RAM we have and set three times this amount as maximum */
    Ram = BasicInfo.NumberOfPhysicalPages * BasicInfo.PageSize;
    MaximumSize = 3 * Ram;

    /* If we have more than 1GB, use that as minimum, otherwise, use 1.5X RAM */
    MinimumSize = (Ram >= 1024 * MEGABYTE) ? Ram : MaximumSize / 2;

    /* Write the new sizes in the descriptor and mark it as system managed */
    Descriptor->MinSize.QuadPart = MinimumSize;
    Descriptor->MaxSize.QuadPart = MaximumSize;
    Descriptor->Flags |= SMP_PAGEFILE_SYSTEM_MANAGED;
}

NTSTATUS
NTAPI
SmpValidatePagingFileSizes(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor)
{
    NTSTATUS Status = STATUS_SUCCESS;
    BOOLEAN WasTooBig = FALSE;
    ULONGLONG MinSize, MaxSize;
#ifdef _M_IX86
    ULONGLONG MaxPageFileSize =
        (SharedUserData->ProcessorFeatures[PF_PAE_ENABLED])
            ? MAXIMUM_PAGEFILE_SIZE_PAE : MAXIMUM_PAGEFILE_SIZE;
#else
    static const ULONGLONG MaxPageFileSize = MAXIMUM_PAGEFILE_SIZE;
#endif

    /* Capture the min and max */
    MinSize = Descriptor->MinSize.QuadPart;
    MaxSize = Descriptor->MaxSize.QuadPart;

    DPRINT("SMSS:PFILE: Validating sizes for `%wZ' 0x%I64X 0x%I64X\n",
            &Descriptor->Name, MinSize, MaxSize);

    /* Don't let minimum be bigger than maximum */
    if (MinSize > MaxSize)
        MaxSize = MinSize;

    /* Validate the minimum and maximum and trim them if they are too large */
    if (MinSize > MaxPageFileSize)
    {
        WasTooBig = TRUE;
        MinSize = MaxPageFileSize;
    }
    if (MaxSize > MaxPageFileSize)
    {
        WasTooBig = TRUE;
        MaxSize = MaxPageFileSize;
    }

    /* If we trimmed, write a flag in the descriptor */
    if (WasTooBig)
    {
        DPRINT("SMSS:PFILE: Trimmed size of `%wZ' to maximum allowed\n",
                &Descriptor->Name);
        Descriptor->Flags |= SMP_PAGEFILE_WAS_TOO_BIG;
    }

    /* Now write the (possibly trimmed) sizes back */
    Descriptor->MinSize.QuadPart = MinSize;
    Descriptor->MaxSize.QuadPart = MaxSize;
    return Status;
}

NTSTATUS
NTAPI
SmpCreateSystemManagedPagingFile(IN PSMP_PAGEFILE_DESCRIPTOR Descriptor,
                                 IN BOOLEAN DecreaseSize)
{
    LARGE_INTEGER FuzzFactor, Size;

    /* Make sure there is at least 1 paging file and that we are system-managed */
    ASSERT(SmpNumberOfPagingFiles >= 1);
    ASSERT(!IsListEmpty(&SmpPagingFileDescriptorList));
    ASSERT(Descriptor->Flags & SMP_PAGEFILE_SYSTEM_MANAGED);

    /* Keep decreasing the pagefile by this amount if we run out of space */
    FuzzFactor.QuadPart = FUZZ_FACTOR;

    /* Create the descriptor for it (mainly the right sizes) and validate */
    SmpMakeSystemManagedPagingFileDescriptor(Descriptor);
    SmpValidatePagingFileSizes(Descriptor);

    /* Use either the minimum size in the descriptor, or 16 MB in minimal mode */
    Size.QuadPart = DecreaseSize ? 16 * MEGABYTE : Descriptor->MinSize.QuadPart;

    /* Check if this should be a fixed pagefile or an any pagefile */
    if (Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] == '?')
    {
        /* Find a disk for it */
        return SmpCreatePagingFileOnAnyDrive(Descriptor, &FuzzFactor, &Size);
    }

    /* Use the disk that was given */
    return SmpCreatePagingFileOnFixedDrive(Descriptor, &FuzzFactor, &Size);
}

NTSTATUS
NTAPI
SmpCreateEmergencyPagingFile(VOID)
{
    PSMP_PAGEFILE_DESCRIPTOR Descriptor;
    WCHAR Buffer[32];

    /* Allocate a descriptor */
    Descriptor = RtlAllocateHeap(RtlGetProcessHeap(),
                                 HEAP_ZERO_MEMORY,
                                 sizeof(SMP_PAGEFILE_DESCRIPTOR));
    if (!Descriptor) return STATUS_NO_MEMORY;

    /* Initialize it */
    RtlInitUnicodeString(&Descriptor->Token, NULL);

    /* Copy the default pagefile name */
    ASSERT(sizeof(Buffer) >= sizeof(STANDARD_PAGING_FILE_NAME));
    wcscpy(Buffer, STANDARD_PAGING_FILE_NAME);

    /* Fill the rest of the descriptor out */
    RtlInitUnicodeString(&Descriptor->Name, Buffer);
    Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] = '?';
    Descriptor->Flags |= SMP_PAGEFILE_SYSTEM_MANAGED |
                         SMP_PAGEFILE_EMERGENCY |
                         SMP_PAGEFILE_ON_ANY_DRIVE;

    /* Insert it into the descriptor list */
    InsertHeadList(&SmpPagingFileDescriptorList, &Descriptor->Entry);
    SmpNumberOfPagingFiles++;

    /* Go ahead and create it now, with the minimal size possible */
    return SmpCreateSystemManagedPagingFile(Descriptor, TRUE);
}

NTSTATUS
NTAPI
SmpCreateVolumeDescriptors(VOID)
{
    NTSTATUS Status;
    UNICODE_STRING VolumePath;
    BOOLEAN BootVolumeFound = FALSE;
    WCHAR StartChar, Drive, DriveDiff;
    HANDLE VolumeHandle;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    PROCESS_DEVICEMAP_INFORMATION ProcessInformation;
    FILE_FS_DEVICE_INFORMATION DeviceInfo;
    FILE_FS_SIZE_INFORMATION SizeInfo;
    PSMP_VOLUME_DESCRIPTOR Volume;
    LARGE_INTEGER FreeSpace, FinalFreeSpace;
    WCHAR Buffer[32];

    /* We should be starting with an empty list */
    ASSERT(IsListEmpty(&SmpVolumeDescriptorList));

    /* Query the device map so we can get the drive letters */
    Status = NtQueryInformationProcess(NtCurrentProcess(),
                                       ProcessDeviceMap,
                                       &ProcessInformation.Query,
                                       sizeof(ProcessInformation.Query),
                                       NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("SMSS:PFILE: Query(ProcessDeviceMap) failed with status %X\n",
                Status);
        return Status;
    }

    /* Build the volume string, starting with A: (we'll edit this in place) */
    wcscpy(Buffer, L"\\??\\A:\\");
    RtlInitUnicodeString(&VolumePath, Buffer);

    /* Start with the C drive, except on NEC PC-98 */
    StartChar = IsNEC_98 ? L'A' : L'C';
    for (Drive = StartChar, DriveDiff = StartChar - L'A'; Drive <= L'Z'; Drive++, DriveDiff++)
    {
        /* Skip the disk if it's not in the drive map */
        if (!((1 << DriveDiff) & ProcessInformation.Query.DriveMap)) continue;

        /* Write the drive letter and try to open the volume */
        VolumePath.Buffer[STANDARD_DRIVE_LETTER_OFFSET] = Drive;
        InitializeObjectAttributes(&ObjectAttributes,
                                   &VolumePath,
                                   OBJ_CASE_INSENSITIVE,
                                   NULL,
                                   NULL);
        Status = NtOpenFile(&VolumeHandle,
                            FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
                            &ObjectAttributes,
                            &IoStatusBlock,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE);
        if (!NT_SUCCESS(Status))
        {
            /* Skip the volume if we failed */
            DPRINT1("SMSS:PFILE: Open volume `%wZ' failed with status %X\n",
                    &VolumePath, Status);
            continue;
        }

        /* Now query device information on the volume */
        Status = NtQueryVolumeInformationFile(VolumeHandle,
                                              &IoStatusBlock,
                                              &DeviceInfo,
                                              sizeof(DeviceInfo),
                                              FileFsDeviceInformation);
        if (!NT_SUCCESS(Status))
        {
            /* Move to the next volume if we failed */
            DPRINT1("SMSS:PFILE: Query volume `%wZ' (handle %p) for device info"
                    " failed with status %X\n",
                    &VolumePath,
                    VolumeHandle,
                    Status);
            NtClose(VolumeHandle);
            continue;
        }

        /* Check if this is a fixed disk */
        if (DeviceInfo.Characteristics & (FILE_FLOPPY_DISKETTE |
                                          FILE_READ_ONLY_DEVICE |
                                          FILE_REMOTE_DEVICE |
                                          FILE_REMOVABLE_MEDIA))
        {
            /* It isn't, so skip it */
            DPRINT1("SMSS:PFILE: Volume `%wZ' (%X) cannot store a paging file\n",
                    &VolumePath,
                    DeviceInfo.Characteristics);
            NtClose(VolumeHandle);
            continue;
        }

        /* We found a fixed volume, allocate a descriptor for it */
        Volume = RtlAllocateHeap(RtlGetProcessHeap(),
                                 HEAP_ZERO_MEMORY,
                                 sizeof(SMP_VOLUME_DESCRIPTOR));
        if (!Volume)
        {
            /* Failed to allocate memory, try the next disk */
            DPRINT1("SMSS:PFILE: Failed to allocate a volume descriptor (%u bytes)\n",
                    sizeof(SMP_VOLUME_DESCRIPTOR));
            NtClose(VolumeHandle);
            continue;
        }

        /* Save the drive letter and device information */
        Volume->DriveLetter = Drive;
        Volume->DeviceInfo = DeviceInfo;

        /* Check if this is the boot volume */
        if (RtlUpcaseUnicodeChar(Drive) ==
            RtlUpcaseUnicodeChar(SharedUserData->NtSystemRoot[0]))
        {
            /* Save it */
            ASSERT(BootVolumeFound == FALSE);
            Volume->Flags |= SMP_VOLUME_IS_BOOT;
            BootVolumeFound = TRUE;
        }

        /* Now get size information on the volume */
        Status = NtQueryVolumeInformationFile(VolumeHandle,
                                              &IoStatusBlock,
                                              &SizeInfo,
                                              sizeof(SizeInfo),
                                              FileFsSizeInformation);
        if (!NT_SUCCESS(Status))
        {
            /* We failed -- keep going */
            DPRINT1("SMSS:PFILE: Query volume `%wZ' (handle %p) for size failed"
                    " with status %X\n",
                    &VolumePath,
                    VolumeHandle,
                    Status);
            RtlFreeHeap(RtlGetProcessHeap(), 0, Volume);
            NtClose(VolumeHandle);
            continue;
        }

        /* Done querying volume information, close the handle */
        NtClose(VolumeHandle);

        /* Compute how much free space we have */
        FreeSpace.QuadPart = SizeInfo.AvailableAllocationUnits.QuadPart *
                             SizeInfo.SectorsPerAllocationUnit;
        FinalFreeSpace.QuadPart = FreeSpace.QuadPart * SizeInfo.BytesPerSector;

        /* Check if there is less than 32 MB free so we don't starve the disk */
        if (FinalFreeSpace.QuadPart <= MINIMUM_TO_KEEP_FREE)
        {
            /* In this case, act as if there is no free space */
            Volume->FreeSpace.QuadPart = 0;
        }
        else
        {
            /* Trim off 32 MB to give the disk a bit of breathing room */
            Volume->FreeSpace.QuadPart = FinalFreeSpace.QuadPart -
                                         MINIMUM_TO_KEEP_FREE;
        }

        /* All done, add this volume to our descriptor list */
        InsertTailList(&SmpVolumeDescriptorList, &Volume->Entry);
        Volume->Flags |= SMP_VOLUME_INSERTED;
        DPRINT("SMSS:PFILE: Created volume descriptor for`%wZ'\n", &VolumePath);
    }

    /* We must've found at least the boot volume */
    ASSERT(BootVolumeFound == TRUE);
    ASSERT(!IsListEmpty(&SmpVolumeDescriptorList));
    if (!IsListEmpty(&SmpVolumeDescriptorList)) return STATUS_SUCCESS;

    /* Something is really messed up if we found no disks at all */
    return STATUS_UNEXPECTED_IO_ERROR;
}

NTSTATUS
NTAPI
SmpCreatePagingFiles(VOID)
{
    NTSTATUS Status;
    PSMP_PAGEFILE_DESCRIPTOR Descriptor;
    LARGE_INTEGER Size, FuzzFactor;
    BOOLEAN Created = FALSE;
    PLIST_ENTRY NextEntry;

    /* Check if no paging files were requested */
    if (!(SmpNumberOfPagingFiles) && !(SmpRegistrySpecifierPresent))
    {
        /* The list should be empty -- nothing to do */
        ASSERT(IsListEmpty(&SmpPagingFileDescriptorList));
        DPRINT1("SMSS:PFILE: No paging file was requested\n");
        return STATUS_SUCCESS;
    }

    /* Initialize the volume descriptors so we can know what's available */
    Status = SmpCreateVolumeDescriptors();
    if (!NT_SUCCESS(Status))
    {
        /* We can't make decisions without this, so fail */
        DPRINT1("SMSS:PFILE: Failed to create volume descriptors (status %X)\n",
                Status);
        return Status;
    }

    /* If we fail creating pagefiles, try to reduce by this much each time */
    FuzzFactor.QuadPart = FUZZ_FACTOR;

    /* Loop the descriptor list */
    NextEntry = SmpPagingFileDescriptorList.Flink;
    while (NextEntry != &SmpPagingFileDescriptorList)
    {
        /* Check what kind of descriptor this is */
        Descriptor = CONTAINING_RECORD(NextEntry, SMP_PAGEFILE_DESCRIPTOR, Entry);
        if (Descriptor->Flags & SMP_PAGEFILE_SYSTEM_MANAGED)
        {
            /* This is a system-managed descriptor. Create the correct file */
            DPRINT("SMSS:PFILE: Creating a system managed paging file (`%wZ')\n",
                    &Descriptor->Name);
            Status = SmpCreateSystemManagedPagingFile(Descriptor, FALSE);
            if (!NT_SUCCESS(Status))
            {
                /* We failed -- try again, with size minimization this time */
                DPRINT("SMSS:PFILE: Trying lower sizes for (`%wZ')\n",
                        &Descriptor->Name);
                Status = SmpCreateSystemManagedPagingFile(Descriptor, TRUE);
            }
        }
        else
        {
            /* This is a manually entered descriptor. Validate its size first */
            SmpValidatePagingFileSizes(Descriptor);

            /* Check if this is an ANY pagefile or a FIXED pagefile */
            DPRINT("SMSS:PFILE: Creating a normal paging file (`%wZ')\n",
                    &Descriptor->Name);
            if (Descriptor->Name.Buffer[STANDARD_DRIVE_LETTER_OFFSET] == L'?')
            {
                /* It's an any pagefile, try to create it wherever possible */
                Size = Descriptor->MinSize;
                Status = SmpCreatePagingFileOnAnyDrive(Descriptor,
                                                       &FuzzFactor,
                                                       &Size);
                if (!NT_SUCCESS(Status))
                {
                    /* We failed to create it. Try again with a smaller size */
                    DPRINT("SMSS:PFILE: Trying lower sizes for (`%wZ')\n",
                            &Descriptor->Name);
                    Size.QuadPart = 16 * MEGABYTE;
                    Status = SmpCreatePagingFileOnAnyDrive(Descriptor,
                                                           &FuzzFactor,
                                                           &Size);
                }
            }
            else
            {
                /* It's a fixed pagefile: override the minimum and use ours */
                Size.QuadPart = 16 * MEGABYTE;
                Status = SmpCreatePagingFileOnFixedDrive(Descriptor,
                                                         &FuzzFactor,
                                                         &Size);
            }
        }

        /* Go to the next descriptor */
        if (NT_SUCCESS(Status)) Created = TRUE;
        NextEntry = NextEntry->Flink;
    }

    /* Check if none of the code in our loops above was able to create it */
    if (!Created)
    {
        /* Build an emergency pagefile ourselves */
        DPRINT1("SMSS:PFILE: Creating emergency paging file.\n");
        Status = SmpCreateEmergencyPagingFile();
    }

    /* All done */
    return Status;
}
