/*
 * PROJECT:     ReactOS Setup Library
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     INI file parser that caches contents of INI file in memory.
 * COPYRIGHT:   Copyright 2002-2018 Royce Mitchell III
 */

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

#include "precomp.h"

#include "inicache.h"

#define NDEBUG
#include <debug.h>

/* PRIVATE FUNCTIONS ********************************************************/

static
PINICACHEKEY
IniCacheFreeKey(
    PINICACHEKEY Key)
{
    PINICACHEKEY Next;

    if (Key == NULL)
        return NULL;

    Next = Key->Next;
    if (Key->Name != NULL)
    {
        RtlFreeHeap(ProcessHeap, 0, Key->Name);
        Key->Name = NULL;
    }

    if (Key->Data != NULL)
    {
        RtlFreeHeap(ProcessHeap, 0, Key->Data);
        Key->Data = NULL;
    }

    RtlFreeHeap(ProcessHeap, 0, Key);

    return Next;
}


static
PINICACHESECTION
IniCacheFreeSection(
    PINICACHESECTION Section)
{
    PINICACHESECTION Next;

    if (Section == NULL)
        return NULL;

    Next = Section->Next;
    while (Section->FirstKey != NULL)
    {
        Section->FirstKey = IniCacheFreeKey(Section->FirstKey);
    }
    Section->LastKey = NULL;

    if (Section->Name != NULL)
    {
        RtlFreeHeap(ProcessHeap, 0, Section->Name);
        Section->Name = NULL;
    }

    RtlFreeHeap(ProcessHeap, 0, Section);

    return Next;
}


static
PINICACHEKEY
IniCacheFindKey(
     PINICACHESECTION Section,
     PWCHAR Name,
     ULONG NameLength)
{
    PINICACHEKEY Key;

    Key = Section->FirstKey;
    while (Key != NULL)
    {
        if (NameLength == wcslen(Key->Name))
        {
            if (_wcsnicmp(Key->Name, Name, NameLength) == 0)
                break;
        }

        Key = Key->Next;
    }

    return Key;
}


static
PINICACHEKEY
IniCacheAddKey(
    PINICACHESECTION Section,
    PCHAR Name,
    ULONG NameLength,
    PCHAR Data,
    ULONG DataLength)
{
    PINICACHEKEY Key;
    ULONG i;

    Key = NULL;

    if (Section == NULL ||
        Name == NULL ||
        NameLength == 0 ||
        Data == NULL ||
        DataLength == 0)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    Key = (PINICACHEKEY)RtlAllocateHeap(ProcessHeap,
                                        HEAP_ZERO_MEMORY,
                                        sizeof(INICACHEKEY));
    if (Key == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    Key->Name = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                        0,
                                        (NameLength + 1) * sizeof(WCHAR));
    if (Key->Name == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Key);
        return NULL;
    }

    /* Copy value name */
    for (i = 0; i < NameLength; i++)
    {
        Key->Name[i] = (WCHAR)Name[i];
    }
    Key->Name[NameLength] = 0;

    Key->Data = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                        0,
                                        (DataLength + 1) * sizeof(WCHAR));
    if (Key->Data == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Key->Name);
        RtlFreeHeap(ProcessHeap, 0, Key);
        return NULL;
    }

    /* Copy value data */
    for (i = 0; i < DataLength; i++)
    {
        Key->Data[i] = (WCHAR)Data[i];
    }
    Key->Data[DataLength] = 0;


    if (Section->FirstKey == NULL)
    {
        Section->FirstKey = Key;
        Section->LastKey = Key;
    }
    else
    {
        Section->LastKey->Next = Key;
        Key->Prev = Section->LastKey;
        Section->LastKey = Key;
    }

  return Key;
}


static
PINICACHESECTION
IniCacheAddSection(
    PINICACHE Cache,
    PCHAR Name,
    ULONG NameLength)
{
    PINICACHESECTION Section = NULL;
    ULONG i;

    if (Cache == NULL || Name == NULL || NameLength == 0)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    Section = (PINICACHESECTION)RtlAllocateHeap(ProcessHeap,
                                                HEAP_ZERO_MEMORY,
                                                sizeof(INICACHESECTION));
    if (Section == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    /* Allocate and initialize section name */
    Section->Name = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                            0,
                                            (NameLength + 1) * sizeof(WCHAR));
    if (Section->Name == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Section);
        return NULL;
    }

    /* Copy section name */
    for (i = 0; i < NameLength; i++)
    {
        Section->Name[i] = (WCHAR)Name[i];
    }
    Section->Name[NameLength] = 0;

    /* Append section */
    if (Cache->FirstSection == NULL)
    {
        Cache->FirstSection = Section;
        Cache->LastSection = Section;
    }
    else
    {
        Cache->LastSection->Next = Section;
        Section->Prev = Cache->LastSection;
        Cache->LastSection = Section;
    }

    return Section;
}


static
PCHAR
IniCacheSkipWhitespace(
    PCHAR Ptr)
{
    while (*Ptr != 0 && isspace(*Ptr))
        Ptr++;

    return (*Ptr == 0) ? NULL : Ptr;
}


static
PCHAR
IniCacheSkipToNextSection(
    PCHAR Ptr)
{
    while (*Ptr != 0 && *Ptr != '[')
    {
        while (*Ptr != 0 && *Ptr != L'\n')
        {
            Ptr++;
        }

        Ptr++;
    }

    return (*Ptr == 0) ? NULL : Ptr;
}


static
PCHAR
IniCacheGetSectionName(
    PCHAR Ptr,
    PCHAR *NamePtr,
    PULONG NameSize)
{
    ULONG Size = 0;
    CHAR Name[256];

    *NamePtr = NULL;
    *NameSize = 0;

    /* Skip whitespace */
    while (*Ptr != 0 && isspace(*Ptr))
    {
        Ptr++;
    }

    *NamePtr = Ptr;

    while (*Ptr != 0 && *Ptr != ']')
    {
        Size++;
        Ptr++;
    }
    Ptr++;

    while (*Ptr != 0 && *Ptr != L'\n')
    {
        Ptr++;
    }
    Ptr++;

    *NameSize = Size;

    strncpy(Name, *NamePtr, Size);
    Name[Size] = 0;

    DPRINT("SectionName: '%s'\n", Name);

    return Ptr;
}


static
PCHAR
IniCacheGetKeyName(
    PCHAR Ptr,
    PCHAR *NamePtr,
    PULONG NameSize)
{
    ULONG Size = 0;

    *NamePtr = NULL;
    *NameSize = 0;

    while (Ptr && *Ptr)
    {
        *NamePtr = NULL;
        *NameSize = 0;
        Size = 0;

        /* Skip whitespace and empty lines */
        while (isspace(*Ptr) || *Ptr == '\n' || *Ptr == '\r')
        {
            Ptr++;
        }
        if (*Ptr == 0)
        {
            continue;
        }

        *NamePtr = Ptr;

        while (*Ptr != 0 && !isspace(*Ptr) && *Ptr != '=' && *Ptr != ';')
        {
            Size++;
            Ptr++;
        }
        if (*Ptr == ';')
        {
            while (*Ptr != 0 && *Ptr != '\r' && *Ptr != '\n')
            {
                Ptr++;
            }
        }
        else
        {
            *NameSize = Size;
            break;
        }
    }

  return Ptr;
}


static
PCHAR
IniCacheGetKeyValue(
    PCHAR Ptr,
    PCHAR *DataPtr,
    PULONG DataSize,
    BOOLEAN String)
{
    ULONG Size = 0;

    *DataPtr = NULL;
    *DataSize = 0;

    /* Skip whitespace */
    while (*Ptr != 0 && isspace(*Ptr))
    {
        Ptr++;
    }

    /* Check and skip '=' */
    if (*Ptr != '=')
    {
        return NULL;
    }
    Ptr++;

    /* Skip whitespace */
    while (*Ptr != 0 && isspace(*Ptr))
    {
        Ptr++;
    }

    if (*Ptr == '"' && String)
    {
        Ptr++;

        /* Get data */
        *DataPtr = Ptr;
        while (*Ptr != '"')
        {
            Ptr++;
            Size++;
        }
        Ptr++;

        while (*Ptr && *Ptr != '\r' && *Ptr != '\n')
        {
            Ptr++;
        }
    }
    else
    {
        /* Get data */
        *DataPtr = Ptr;
        while (*Ptr != 0 && *Ptr != '\r' && *Ptr != ';')
        {
            Ptr++;
            Size++;
        }
    }

    /* Skip to next line */
    if (*Ptr == '\r')
        Ptr++;
    if (*Ptr == '\n')
        Ptr++;

    *DataSize = Size;

    return Ptr;
}


/* PUBLIC FUNCTIONS *********************************************************/

NTSTATUS
IniCacheLoadFromMemory(
    PINICACHE *Cache,
    PCHAR FileBuffer,
    ULONG FileLength,
    BOOLEAN String)
{
    PCHAR Ptr;

    PINICACHESECTION Section;
    PINICACHEKEY Key;

    PCHAR SectionName;
    ULONG SectionNameSize;

    PCHAR KeyName;
    ULONG KeyNameSize;

    PCHAR KeyValue;
    ULONG KeyValueSize;

    /* Allocate inicache header */
    *Cache = (PINICACHE)RtlAllocateHeap(ProcessHeap,
                                        HEAP_ZERO_MEMORY,
                                        sizeof(INICACHE));
    if (*Cache == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Parse ini file */
    Section = NULL;
    Ptr = FileBuffer;
    while (Ptr != NULL && *Ptr != 0)
    {
        Ptr = IniCacheSkipWhitespace(Ptr);
        if (Ptr == NULL)
            continue;

        if (*Ptr == '[')
        {
            Section = NULL;
            Ptr++;

            Ptr = IniCacheGetSectionName(Ptr,
                                         &SectionName,
                                         &SectionNameSize);

            DPRINT("[%.*s]\n", SectionNameSize, SectionName);

            Section = IniCacheAddSection(*Cache,
                                         SectionName,
                                         SectionNameSize);
            if (Section == NULL)
            {
                DPRINT("IniCacheAddSection() failed\n");
                Ptr = IniCacheSkipToNextSection(Ptr);
                continue;
            }
        }
        else
        {
            if (Section == NULL)
            {
                Ptr = IniCacheSkipToNextSection(Ptr);
                continue;
            }

            Ptr = IniCacheGetKeyName(Ptr,
                                     &KeyName,
                                     &KeyNameSize);

            Ptr = IniCacheGetKeyValue(Ptr,
                                      &KeyValue,
                                      &KeyValueSize,
                                      String);

            DPRINT("'%.*s' = '%.*s'\n", KeyNameSize, KeyName, KeyValueSize, KeyValue);

            Key = IniCacheAddKey(Section,
                                 KeyName,
                                 KeyNameSize,
                                 KeyValue,
                                 KeyValueSize);
            if (Key == NULL)
            {
                DPRINT("IniCacheAddKey() failed\n");
            }
        }
    }

    return STATUS_SUCCESS;
}

NTSTATUS
IniCacheLoadByHandle(
    PINICACHE *Cache,
    HANDLE FileHandle,
    BOOLEAN String)
{
    NTSTATUS Status;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_STANDARD_INFORMATION FileInfo;
    PCHAR FileBuffer;
    ULONG FileLength;
    LARGE_INTEGER FileOffset;

    *Cache = NULL;

    /* Query file size */
    Status = NtQueryInformationFile(FileHandle,
                                    &IoStatusBlock,
                                    &FileInfo,
                                    sizeof(FILE_STANDARD_INFORMATION),
                                    FileStandardInformation);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("NtQueryInformationFile() failed (Status %lx)\n", Status);
        return Status;
    }

    FileLength = FileInfo.EndOfFile.u.LowPart;

    DPRINT("File size: %lu\n", FileLength);

    /* Allocate file buffer with NULL-terminator */
    FileBuffer = (CHAR*)RtlAllocateHeap(ProcessHeap,
                                        0,
                                        FileLength + 1);
    if (FileBuffer == NULL)
    {
        DPRINT1("RtlAllocateHeap() failed\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Read file */
    FileOffset.QuadPart = 0ULL;
    Status = NtReadFile(FileHandle,
                        NULL,
                        NULL,
                        NULL,
                        &IoStatusBlock,
                        FileBuffer,
                        FileLength,
                        &FileOffset,
                        NULL);

    /* Append NULL-terminator */
    FileBuffer[FileLength] = 0;

    if (!NT_SUCCESS(Status))
    {
        DPRINT("NtReadFile() failed (Status %lx)\n", Status);
        goto Quit;
    }

    Status = IniCacheLoadFromMemory(Cache, FileBuffer, FileLength, String);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("IniCacheLoadFromMemory() failed (Status %lx)\n", Status);
    }

Quit:
    /* Free the file buffer, and return */
    RtlFreeHeap(ProcessHeap, 0, FileBuffer);
    return Status;
}

NTSTATUS
IniCacheLoad(
    PINICACHE *Cache,
    PWCHAR FileName,
    BOOLEAN String)
{
    NTSTATUS Status;
    UNICODE_STRING Name;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE FileHandle;

    *Cache = NULL;

    /* Open the INI file */
    RtlInitUnicodeString(&Name, FileName);

    InitializeObjectAttributes(&ObjectAttributes,
                               &Name,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtOpenFile(&FileHandle,
                        FILE_GENERIC_READ | SYNCHRONIZE,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("NtOpenFile() failed (Status %lx)\n", Status);
        return Status;
    }

    DPRINT("NtOpenFile() successful\n");

    Status = IniCacheLoadByHandle(Cache, FileHandle, String);

    /* Close the INI file */
    NtClose(FileHandle);
    return Status;
}


VOID
IniCacheDestroy(
    PINICACHE Cache)
{
    if (Cache == NULL)
        return;

    while (Cache->FirstSection != NULL)
    {
        Cache->FirstSection = IniCacheFreeSection(Cache->FirstSection);
    }
    Cache->LastSection = NULL;

    RtlFreeHeap(ProcessHeap, 0, Cache);
}


PINICACHESECTION
IniCacheGetSection(
    PINICACHE Cache,
    PWCHAR Name)
{
    PINICACHESECTION Section = NULL;

    if (Cache == NULL || Name == NULL)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    /* Iterate through list of sections */
    Section = Cache->FirstSection;
    while (Section != NULL)
    {
        DPRINT("Comparing '%S' and '%S'\n", Section->Name, Name);

        /* Are the section names the same? */
        if (_wcsicmp(Section->Name, Name) == 0)
            return Section;

        /* Get the next section */
        Section = Section->Next;
    }

    DPRINT("Section not found\n");

    return NULL;
}


NTSTATUS
IniCacheGetKey(
    PINICACHESECTION Section,
    PWCHAR KeyName,
    PWCHAR *KeyData)
{
    PINICACHEKEY Key;

    if (Section == NULL || KeyName == NULL || KeyData == NULL)
    {
        DPRINT("Invalid parameter\n");
        return STATUS_INVALID_PARAMETER;
    }

    *KeyData = NULL;

    Key = IniCacheFindKey(Section, KeyName, wcslen(KeyName));
    if (Key == NULL)
    {
        return STATUS_INVALID_PARAMETER;
    }

    *KeyData = Key->Data;

    return STATUS_SUCCESS;
}


PINICACHEITERATOR
IniCacheFindFirstValue(
    PINICACHESECTION Section,
    PWCHAR *KeyName,
    PWCHAR *KeyData)
{
    PINICACHEITERATOR Iterator;
    PINICACHEKEY Key;

    if (Section == NULL || KeyName == NULL || KeyData == NULL)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    Key = Section->FirstKey;
    if (Key == NULL)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    *KeyName = Key->Name;
    *KeyData = Key->Data;

    Iterator = (PINICACHEITERATOR)RtlAllocateHeap(ProcessHeap,
                                                  0,
                                                  sizeof(INICACHEITERATOR));
    if (Iterator == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    Iterator->Section = Section;
    Iterator->Key = Key;

    return Iterator;
}


BOOLEAN
IniCacheFindNextValue(
    PINICACHEITERATOR Iterator,
    PWCHAR *KeyName,
    PWCHAR *KeyData)
{
    PINICACHEKEY Key;

    if (Iterator == NULL || KeyName == NULL || KeyData == NULL)
    {
        DPRINT("Invalid parameter\n");
        return FALSE;
    }

    Key = Iterator->Key->Next;
    if (Key == NULL)
    {
        DPRINT("No more entries\n");
        return FALSE;
    }

    *KeyName = Key->Name;
    *KeyData = Key->Data;

    Iterator->Key = Key;

    return TRUE;
}


VOID
IniCacheFindClose(
    PINICACHEITERATOR Iterator)
{
    if (Iterator == NULL)
        return;

    RtlFreeHeap(ProcessHeap, 0, Iterator);
}


PINICACHEKEY
IniCacheInsertKey(
    PINICACHESECTION Section,
    PINICACHEKEY AnchorKey,
    INSERTION_TYPE InsertionType,
    PWCHAR Name,
    PWCHAR Data)
{
    PINICACHEKEY Key;

    Key = NULL;

    if (Section == NULL ||
        Name == NULL ||
        *Name == 0 ||
        Data == NULL ||
        *Data == 0)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    /* Allocate key buffer */
    Key = (PINICACHEKEY)RtlAllocateHeap(ProcessHeap,
                                        HEAP_ZERO_MEMORY,
                                        sizeof(INICACHEKEY));
    if (Key == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    /* Allocate name buffer */
    Key->Name = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                        0,
                                        (wcslen(Name) + 1) * sizeof(WCHAR));
    if (Key->Name == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Key);
        return NULL;
    }

    /* Copy value name */
    wcscpy(Key->Name, Name);

    /* Allocate data buffer */
    Key->Data = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                        0,
                                        (wcslen(Data) + 1) * sizeof(WCHAR));
    if (Key->Data == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Key->Name);
        RtlFreeHeap(ProcessHeap, 0, Key);
        return NULL;
    }

    /* Copy value data */
    wcscpy(Key->Data, Data);

    /* Insert key into section */
    if (Section->FirstKey == NULL)
    {
        Section->FirstKey = Key;
        Section->LastKey = Key;
    }
    else if ((InsertionType == INSERT_FIRST) ||
             ((InsertionType == INSERT_BEFORE) && ((AnchorKey == NULL) || (AnchorKey == Section->FirstKey))))
    {
        /* Insert at the head of the list */
        Section->FirstKey->Prev = Key;
        Key->Next = Section->FirstKey;
        Section->FirstKey = Key;
    }
    else if ((InsertionType == INSERT_BEFORE) && (AnchorKey != NULL))
    {
        /* Insert before the anchor key */
        Key->Next = AnchorKey;
        Key->Prev = AnchorKey->Prev;
        AnchorKey->Prev->Next = Key;
        AnchorKey->Prev = Key;
    }
    else if ((InsertionType == INSERT_LAST) ||
             ((InsertionType == INSERT_AFTER) && ((AnchorKey == NULL) || (AnchorKey == Section->LastKey))))
    {
        Section->LastKey->Next = Key;
        Key->Prev = Section->LastKey;
        Section->LastKey = Key;
    }
    else if ((InsertionType == INSERT_AFTER) && (AnchorKey != NULL))
    {
        /* Insert after the anchor key */
        Key->Next = AnchorKey->Next;
        Key->Prev = AnchorKey;
        AnchorKey->Next->Prev = Key;
        AnchorKey->Next = Key;
    }

    return Key;
}


PINICACHE
IniCacheCreate(VOID)
{
    PINICACHE Cache;

    /* Allocate inicache header */
    Cache = (PINICACHE)RtlAllocateHeap(ProcessHeap,
                                       HEAP_ZERO_MEMORY,
                                       sizeof(INICACHE));
    if (Cache == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    return Cache;
}


NTSTATUS
IniCacheSaveByHandle(
    PINICACHE Cache,
    HANDLE FileHandle)
{
    NTSTATUS Status;
    PINICACHESECTION Section;
    PINICACHEKEY Key;
    ULONG BufferSize;
    PCHAR Buffer;
    PCHAR Ptr;
    ULONG Len;
    IO_STATUS_BLOCK IoStatusBlock;
    LARGE_INTEGER Offset;

    /* Calculate required buffer size */
    BufferSize = 0;
    Section = Cache->FirstSection;
    while (Section != NULL)
    {
        BufferSize += (Section->Name ? wcslen(Section->Name) : 0)
                       + 4; /* "[]\r\n" */

        Key = Section->FirstKey;
        while (Key != NULL)
        {
            BufferSize += wcslen(Key->Name)
                          + (Key->Data ? wcslen(Key->Data) : 0)
                          + 3; /* "=\r\n" */
            Key = Key->Next;
        }

        Section = Section->Next;
        if (Section != NULL)
            BufferSize += 2; /* Extra "\r\n" at end of each section */
    }

    DPRINT("BufferSize: %lu\n", BufferSize);

    /* Allocate file buffer with NULL-terminator */
    Buffer = (CHAR*)RtlAllocateHeap(ProcessHeap,
                                    HEAP_ZERO_MEMORY,
                                    BufferSize + 1);
    if (Buffer == NULL)
    {
        DPRINT1("RtlAllocateHeap() failed\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Fill file buffer */
    Ptr = Buffer;
    Section = Cache->FirstSection;
    while (Section != NULL)
    {
        Len = sprintf(Ptr, "[%S]\r\n", Section->Name);
        Ptr += Len;

        Key = Section->FirstKey;
        while (Key != NULL)
        {
            Len = sprintf(Ptr, "%S=%S\r\n", Key->Name, Key->Data);
            Ptr += Len;
            Key = Key->Next;
        }

        Section = Section->Next;
        if (Section != NULL)
        {
            Len = sprintf(Ptr, "\r\n");
            Ptr += Len;
        }
    }

    /* Write to the INI file */
    Offset.QuadPart = 0LL;
    Status = NtWriteFile(FileHandle,
                         NULL,
                         NULL,
                         NULL,
                         &IoStatusBlock,
                         Buffer,
                         BufferSize,
                         &Offset,
                         NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("NtWriteFile() failed (Status %lx)\n", Status);
        RtlFreeHeap(ProcessHeap, 0, Buffer);
        return Status;
    }

    RtlFreeHeap(ProcessHeap, 0, Buffer);
    return STATUS_SUCCESS;
}

NTSTATUS
IniCacheSave(
    PINICACHE Cache,
    PWCHAR FileName)
{
    NTSTATUS Status;
    UNICODE_STRING Name;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE FileHandle;

    /* Create the INI file */
    RtlInitUnicodeString(&Name, FileName);

    InitializeObjectAttributes(&ObjectAttributes,
                               &Name,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtCreateFile(&FileHandle,
                          FILE_GENERIC_WRITE | SYNCHRONIZE,
                          &ObjectAttributes,
                          &IoStatusBlock,
                          NULL,
                          FILE_ATTRIBUTE_NORMAL,
                          0,
                          FILE_SUPERSEDE,
                          FILE_SYNCHRONOUS_IO_NONALERT | FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE,
                          NULL,
                          0);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("NtCreateFile() failed (Status %lx)\n", Status);
        return Status;
    }

    Status = IniCacheSaveByHandle(Cache, FileHandle);

    /* Close the INI file */
    NtClose(FileHandle);
    return Status;
}


PINICACHESECTION
IniCacheAppendSection(
    PINICACHE Cache,
    PWCHAR Name)
{
    PINICACHESECTION Section = NULL;

    if (Cache == NULL || Name == NULL || *Name == 0)
    {
        DPRINT("Invalid parameter\n");
        return NULL;
    }

    Section = (PINICACHESECTION)RtlAllocateHeap(ProcessHeap,
                                                HEAP_ZERO_MEMORY,
                                                sizeof(INICACHESECTION));
    if (Section == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        return NULL;
    }

    /* Allocate and initialize section name */
    Section->Name = (WCHAR*)RtlAllocateHeap(ProcessHeap,
                                            0,
                                            (wcslen(Name) + 1) * sizeof(WCHAR));
    if (Section->Name == NULL)
    {
        DPRINT("RtlAllocateHeap() failed\n");
        RtlFreeHeap(ProcessHeap, 0, Section);
        return NULL;
    }

    /* Copy section name */
    wcscpy(Section->Name, Name);

    /* Append section */
    if (Cache->FirstSection == NULL)
    {
        Cache->FirstSection = Section;
        Cache->LastSection = Section;
    }
    else
    {
        Cache->LastSection->Next = Section;
        Section->Prev = Cache->LastSection;
        Cache->LastSection = Section;
    }

    return Section;
}

/* EOF */