/*
 *  ReactOS kernel
 *  Copyright (C) 2006 ReactOS Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS hive maker
 * FILE:            tools/mkhive/cmi.c
 * PURPOSE:         Registry file manipulation routines
 * PROGRAMMERS:     Hervé Poussineau
 *                  Hermès Bélusca-Maïto
 */

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

#define NDEBUG
#include "mkhive.h"

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

PVOID
NTAPI
CmpAllocate(
    IN SIZE_T Size,
    IN BOOLEAN Paged,
    IN ULONG Tag)
{
    return (PVOID)malloc((size_t)Size);
}

VOID
NTAPI
CmpFree(
    IN PVOID Ptr,
    IN ULONG Quota)
{
    free(Ptr);
}

static BOOLEAN
NTAPI
CmpFileRead(
    IN PHHIVE RegistryHive,
    IN ULONG FileType,
    IN PULONG FileOffset,
    OUT PVOID Buffer,
    IN SIZE_T BufferLength)
{
    PCMHIVE CmHive = (PCMHIVE)RegistryHive;
    FILE *File = CmHive->FileHandles[HFILE_TYPE_PRIMARY];
    if (fseek(File, *FileOffset, SEEK_SET) != 0)
        return FALSE;

    return (fread(Buffer, 1, BufferLength, File) == BufferLength);
}

static BOOLEAN
NTAPI
CmpFileWrite(
    IN PHHIVE RegistryHive,
    IN ULONG FileType,
    IN PULONG FileOffset,
    IN PVOID Buffer,
    IN SIZE_T BufferLength)
{
    PCMHIVE CmHive = (PCMHIVE)RegistryHive;
    FILE *File = CmHive->FileHandles[HFILE_TYPE_PRIMARY];
    if (fseek(File, *FileOffset, SEEK_SET) != 0)
        return FALSE;

    return (fwrite(Buffer, 1, BufferLength, File) == BufferLength);
}

static BOOLEAN
NTAPI
CmpFileSetSize(
    IN PHHIVE RegistryHive,
    IN ULONG FileType,
    IN ULONG FileSize,
    IN ULONG OldFileSize)
{
    DPRINT1("CmpFileSetSize() unimplemented\n");
    return FALSE;
}

static BOOLEAN
NTAPI
CmpFileFlush(
    IN PHHIVE RegistryHive,
    IN ULONG FileType,
    PLARGE_INTEGER FileOffset,
    ULONG Length)
{
    PCMHIVE CmHive = (PCMHIVE)RegistryHive;
    FILE *File = CmHive->FileHandles[HFILE_TYPE_PRIMARY];
    return (fflush(File) == 0);
}

NTSTATUS
CmiInitializeHive(
    IN OUT PCMHIVE Hive,
    IN PCWSTR Name)
{
    NTSTATUS Status;

    RtlZeroMemory(Hive, sizeof(*Hive));

    DPRINT("Hive 0x%p\n", Hive);

    Status = HvInitialize(&Hive->Hive,
                          HINIT_CREATE,
                          HIVE_NOLAZYFLUSH,
                          HFILE_TYPE_PRIMARY,
                          0,
                          CmpAllocate,
                          CmpFree,
                          CmpFileSetSize,
                          CmpFileWrite,
                          CmpFileRead,
                          CmpFileFlush,
                          1,
                          NULL);
    if (!NT_SUCCESS(Status))
    {
        return Status;
    }

    // HACK: See the HACK from r31253
    if (!CmCreateRootNode(&Hive->Hive, Name))
    {
        HvFree(&Hive->Hive);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Add the new hive to the hive list */
    InsertTailList(&CmiHiveListHead,
                   &Hive->HiveList);

    return STATUS_SUCCESS;
}

NTSTATUS
CmiCreateSecurityKey(
    IN PHHIVE Hive,
    IN HCELL_INDEX Cell,
    IN PUCHAR Descriptor,
    IN ULONG DescriptorLength)
{
    HCELL_INDEX SecurityCell;
    PCM_KEY_NODE Node;
    PCM_KEY_SECURITY Security;

    Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
    SecurityCell = HvAllocateCell(Hive,
                                  FIELD_OFFSET(CM_KEY_SECURITY, Descriptor) +
                                  DescriptorLength,
                                  Stable,
                                  HCELL_NIL);
    if (SecurityCell == HCELL_NIL)
    {
        HvReleaseCell(Hive, Cell);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    Node->Security = SecurityCell;
    Security = (PCM_KEY_SECURITY)HvGetCell(Hive, SecurityCell);
    Security->Signature = CM_KEY_SECURITY_SIGNATURE;
    Security->ReferenceCount = 1;
    Security->DescriptorLength = DescriptorLength;

    RtlMoveMemory(&Security->Descriptor,
                  Descriptor,
                  DescriptorLength);

    Security->Flink = Security->Blink = SecurityCell;

    HvReleaseCell(Hive, SecurityCell);
    HvReleaseCell(Hive, Cell);

    return STATUS_SUCCESS;
}

static NTSTATUS
CmiCreateSubKey(
    IN PCMHIVE RegistryHive,
    IN HCELL_INDEX ParentKeyCellOffset,
    IN PCUNICODE_STRING SubKeyName,
    IN BOOLEAN VolatileKey,
    OUT HCELL_INDEX* pNKBOffset)
{
    HCELL_INDEX NKBOffset;
    PCM_KEY_NODE NewKeyCell;
    UNICODE_STRING KeyName;
    HSTORAGE_TYPE Storage;

    /* Skip leading path separator if present */
    if (SubKeyName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR)
    {
        KeyName.Buffer = &SubKeyName->Buffer[1];
        KeyName.Length = KeyName.MaximumLength = SubKeyName->Length - sizeof(WCHAR);
    }
    else
    {
        KeyName = *SubKeyName;
    }

    Storage = (VolatileKey ? Volatile : Stable);

    NKBOffset = HvAllocateCell(&RegistryHive->Hive,
                               FIELD_OFFSET(CM_KEY_NODE, Name) +
                               CmpNameSize(&RegistryHive->Hive, &KeyName),
                               Storage,
                               HCELL_NIL);
    if (NKBOffset == HCELL_NIL)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    NewKeyCell = (PCM_KEY_NODE)HvGetCell(&RegistryHive->Hive, NKBOffset);
    if (NewKeyCell == NULL)
    {
        HvFreeCell(&RegistryHive->Hive, NKBOffset);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    NewKeyCell->Signature = CM_KEY_NODE_SIGNATURE;
    NewKeyCell->Flags = (VolatileKey ? KEY_IS_VOLATILE : 0);
    KeQuerySystemTime(&NewKeyCell->LastWriteTime);
    NewKeyCell->Parent = ParentKeyCellOffset;
    NewKeyCell->SubKeyCounts[Stable] = 0;
    NewKeyCell->SubKeyCounts[Volatile] = 0;
    NewKeyCell->SubKeyLists[Stable] = HCELL_NIL;
    NewKeyCell->SubKeyLists[Volatile] = HCELL_NIL;
    NewKeyCell->ValueList.Count = 0;
    NewKeyCell->ValueList.List = HCELL_NIL;
    NewKeyCell->Security = HCELL_NIL;
    NewKeyCell->Class = HCELL_NIL;
    NewKeyCell->ClassLength = 0;
    NewKeyCell->MaxNameLen = 0;
    NewKeyCell->MaxClassLen = 0;
    NewKeyCell->MaxValueNameLen = 0;
    NewKeyCell->MaxValueDataLen = 0;
    NewKeyCell->NameLength = CmpCopyName(&RegistryHive->Hive, NewKeyCell->Name, &KeyName);
    if (NewKeyCell->NameLength < KeyName.Length) NewKeyCell->Flags |= KEY_COMP_NAME;

    /* Inherit the security from the parent */
    if (ParentKeyCellOffset == HCELL_NIL)
    {
        // We are in fact creating a root key.
        // This is not handled there, but when we
        // call CmCreateRootNode instead.
        ASSERT(FALSE);
    }
    else
    {
        /* Get the parent node */
        PCM_KEY_NODE ParentKeyCell;
        ParentKeyCell = (PCM_KEY_NODE)HvGetCell(&RegistryHive->Hive, ParentKeyCellOffset);

        if (ParentKeyCell)
        {
            /* Inherit the security block of the parent */
            NewKeyCell->Security = ParentKeyCell->Security;
            if (NewKeyCell->Security != HCELL_NIL)
            {
                PCM_KEY_SECURITY Security;
                Security = (PCM_KEY_SECURITY)HvGetCell(&RegistryHive->Hive, NewKeyCell->Security);
                ++Security->ReferenceCount;
                HvReleaseCell(&RegistryHive->Hive, NewKeyCell->Security);
            }

            HvReleaseCell(&RegistryHive->Hive, ParentKeyCellOffset);
        }
    }

    HvReleaseCell(&RegistryHive->Hive, NKBOffset);

    *pNKBOffset = NKBOffset;
    return STATUS_SUCCESS;
}

NTSTATUS
CmiAddSubKey(
    IN PCMHIVE RegistryHive,
    IN HCELL_INDEX ParentKeyCellOffset,
    IN PCUNICODE_STRING SubKeyName,
    IN BOOLEAN VolatileKey,
    OUT HCELL_INDEX *pBlockOffset)
{
    PCM_KEY_NODE ParentKeyCell;
    HCELL_INDEX NKBOffset;
    NTSTATUS Status;

    /* Create the new key */
    Status = CmiCreateSubKey(RegistryHive, ParentKeyCellOffset, SubKeyName, VolatileKey, &NKBOffset);
    if (!NT_SUCCESS(Status))
        return Status;

    /* Mark the parent cell as dirty */
    HvMarkCellDirty(&RegistryHive->Hive, ParentKeyCellOffset, FALSE);

    if (!CmpAddSubKey(&RegistryHive->Hive, ParentKeyCellOffset, NKBOffset))
    {
        /* FIXME: delete newly created cell */
        // CmpFreeKeyByCell(&RegistryHive->Hive, NewCell /*NKBOffset*/, FALSE);
        ASSERT(FALSE);
        return STATUS_UNSUCCESSFUL;
    }

    /* Get the parent node */
    ParentKeyCell = (PCM_KEY_NODE)HvGetCell(&RegistryHive->Hive, ParentKeyCellOffset);
    if (!ParentKeyCell)
    {
        /* FIXME: delete newly created cell */
        return STATUS_UNSUCCESSFUL;
    }
    VERIFY_KEY_CELL(ParentKeyCell);

    /* Update the timestamp */
    KeQuerySystemTime(&ParentKeyCell->LastWriteTime);

    /* Check if we need to update name maximum, update it if so */
    if (ParentKeyCell->MaxNameLen < SubKeyName->Length)
        ParentKeyCell->MaxNameLen = SubKeyName->Length;

    /* Release the cell */
    HvReleaseCell(&RegistryHive->Hive, ParentKeyCellOffset);

    *pBlockOffset = NKBOffset;
    return STATUS_SUCCESS;
}

NTSTATUS
CmiAddValueKey(
    IN PCMHIVE RegistryHive,
    IN PCM_KEY_NODE Parent,
    IN ULONG ChildIndex,
    IN PCUNICODE_STRING ValueName,
    OUT PCM_KEY_VALUE *pValueCell,
    OUT HCELL_INDEX *pValueCellOffset)
{
    NTSTATUS Status;
    HSTORAGE_TYPE Storage;
    PCM_KEY_VALUE NewValueCell;
    HCELL_INDEX NewValueCellOffset;

    Storage = (Parent->Flags & KEY_IS_VOLATILE) ? Volatile : Stable;

    NewValueCellOffset = HvAllocateCell(&RegistryHive->Hive,
                               FIELD_OFFSET(CM_KEY_VALUE, Name) +
                               CmpNameSize(&RegistryHive->Hive, (PUNICODE_STRING)ValueName),
                               Storage,
                               HCELL_NIL);
    if (NewValueCellOffset == HCELL_NIL)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    NewValueCell = (PCM_KEY_VALUE)HvGetCell(&RegistryHive->Hive, NewValueCellOffset);
    if (NewValueCell == NULL)
    {
        HvFreeCell(&RegistryHive->Hive, NewValueCellOffset);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    NewValueCell->Signature = CM_KEY_VALUE_SIGNATURE;
    NewValueCell->NameLength = CmpCopyName(&RegistryHive->Hive,
                                           NewValueCell->Name,
                                           (PUNICODE_STRING)ValueName);

    /* Check for compressed name */
    if (NewValueCell->NameLength < ValueName->Length)
    {
        /* This is a compressed name */
        NewValueCell->Flags = VALUE_COMP_NAME;
    }
    else
    {
        /* No flags to set */
        NewValueCell->Flags = 0;
    }

    NewValueCell->Type = 0;
    NewValueCell->DataLength = 0;
    NewValueCell->Data = HCELL_NIL;

    HvMarkCellDirty(&RegistryHive->Hive, NewValueCellOffset, FALSE);

    /* Check if we already have a value list */
    if (Parent->ValueList.Count)
    {
        /* Then make sure it's valid and dirty it */
        ASSERT(Parent->ValueList.List != HCELL_NIL);
        HvMarkCellDirty(&RegistryHive->Hive, Parent->ValueList.List, FALSE);
    }

    /* Add this value cell to the child list */
    Status = CmpAddValueToList(&RegistryHive->Hive,
                               NewValueCellOffset,
                               ChildIndex,
                               Storage,
                               &Parent->ValueList);

    /* If we failed, free the entire cell, including the data */
    if (!NT_SUCCESS(Status))
    {
        /* Overwrite the status with a known one */
        CmpFreeValue(&RegistryHive->Hive, NewValueCellOffset);
        Status = STATUS_INSUFFICIENT_RESOURCES;
    }
    else
    {
        *pValueCell = NewValueCell;
        *pValueCellOffset = NewValueCellOffset;
        Status = STATUS_SUCCESS;
    }

    return Status;
}