/*
 *  ReactOS kernel
 *  Copyright (C) 2002 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/* $Id: inicache.c 25315 2007-01-05 20:19:21Z cwittich $
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS text-mode setup
 * FILE:            subsys/system/usetup/inicache.c
 * PURPOSE:         INI file parser that caches contents of INI file in memory
 * PROGRAMMER:      Royce Mitchell III
 *                  Eric Kohl
 */

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

#include "usetup.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,
				      0,
				      sizeof(INICACHEKEY));
  if (Key == NULL)
    {
      DPRINT("RtlAllocateHeap() failed\n");
      return(NULL);
    }

  RtlZeroMemory(Key,
		sizeof(INICACHEKEY));


  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);
}

#if 0
static PINICACHESECTION
IniCacheFindSection(PINICACHE Cache,
		    PWCHAR Name,
		    ULONG NameLength)
{
  PINICACHESECTION Section = NULL;

  if (Cache == NULL || Name == NULL || NameLength == 0)
    {
      return(NULL);
    }

  Section = Cache->FirstSection;

  /* iterate through list of sections */
  while (Section != NULL)
    {
      if (NameLength == wcslen(Section->Name))
	{
	  /* are the contents the same too? */
	  if (_wcsnicmp(Section->Name, Name, NameLength) == 0)
	    break;
	}

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

  return(Section);
}
#endif

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,
					      0,
					      sizeof(INICACHESECTION));
  if (Section == NULL)
    {
      DPRINT("RtlAllocateHeap() failed\n");
      return(NULL);
    }
  RtlZeroMemory(Section,
		sizeof(INICACHESECTION));

  /* 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
IniCacheLoad(PINICACHE *Cache,
	     PUNICODE_STRING FileName,
	     BOOLEAN String)
{
  OBJECT_ATTRIBUTES ObjectAttributes;
  FILE_STANDARD_INFORMATION FileInfo;
  IO_STATUS_BLOCK IoStatusBlock;
  HANDLE FileHandle;
  NTSTATUS Status;
  PCHAR FileBuffer;
  ULONG FileLength;
  PCHAR Ptr;
  LARGE_INTEGER FileOffset;

  PINICACHESECTION Section;
  PINICACHEKEY Key;

  PCHAR SectionName;
  ULONG SectionNameSize;

  PCHAR KeyName;
  ULONG KeyNameSize;

  PCHAR KeyValue;
  ULONG KeyValueSize;

  *Cache = NULL;

  /* Open ini file */
  InitializeObjectAttributes(&ObjectAttributes,
			     FileName,
			     0,
			     NULL,
			     NULL);

  Status = NtOpenFile(&FileHandle,
		      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");

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

  FileLength = FileInfo.EndOfFile.u.LowPart;

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

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

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

  /* Append string terminator */
  FileBuffer[FileLength] = 0;

  NtClose(FileHandle);

  if (!NT_SUCCESS(Status))
    {
      DPRINT("NtReadFile() failed (Status %lx)\n", Status);
      RtlFreeHeap(ProcessHeap,
		  0,
		  FileBuffer);
      return(Status);
    }


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

  /* Initialize inicache header */
  RtlZeroMemory(*Cache,
		sizeof(INICACHE));

  /* 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);

	  DPRINT1("[%.*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);

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

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

  /* Free file buffer */
  RtlFreeHeap(ProcessHeap,
	      0,
	      FileBuffer);

  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,
		  INSERTATION_TYPE InsertationType,
		  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,
				      0,
				      sizeof(INICACHEKEY));
  if (Key == NULL)
    {
      DPRINT("RtlAllocateHeap() failed\n");
      return(NULL);
    }
  RtlZeroMemory(Key,
		sizeof(INICACHEKEY));

  /* 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 ((InsertationType == INSERT_FIRST) ||
	   ((InsertationType == 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 ((InsertationType == 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 ((InsertationType == INSERT_LAST) ||
	   ((InsertationType == INSERT_AFTER) && ((AnchorKey == NULL) || (AnchorKey == Section->LastKey))))
    {
      Section->LastKey->Next = Key;
      Key->Prev = Section->LastKey;
      Section->LastKey = Key;
    }
  else if ((InsertationType == INSERT_AFTER) && (AnchorKey != NULL))
    {
      /* Insert before 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,
				     0,
				     sizeof(INICACHE));
  if (Cache == NULL)
    {
      DPRINT("RtlAllocateHeap() failed\n");
      return(NULL);
    }

  /* Initialize inicache header */
  RtlZeroMemory(Cache,
		sizeof(INICACHE));

  return(Cache);
}


NTSTATUS
IniCacheSave(PINICACHE Cache,
	     PWCHAR FileName)
{
  UNICODE_STRING Name;
  PINICACHESECTION Section;
  PINICACHEKEY Key;
  ULONG BufferSize;
  PCHAR Buffer;
  PCHAR Ptr;
  ULONG Len;
  NTSTATUS Status;

  OBJECT_ATTRIBUTES ObjectAttributes;
  IO_STATUS_BLOCK IoStatusBlock;
  LARGE_INTEGER Offset;
  HANDLE FileHandle;


  /* 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 */
    }
  BufferSize++; /* Null-terminator */

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

  /* Allocate file buffer */
  Buffer = (CHAR*) RtlAllocateHeap(ProcessHeap,
			   0,
			   BufferSize);
  if (Buffer == NULL)
    {
      DPRINT1("RtlAllocateHeap() failed\n");
      return(STATUS_INSUFFICIENT_RESOURCES);
    }
  RtlZeroMemory(Buffer, BufferSize);

  /* 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;
	}
    }

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

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

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

  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);
      NtClose(FileHandle);
      RtlFreeHeap(ProcessHeap,
		  0,
		  Buffer);
      return(Status);
    }

  NtClose(FileHandle);

  RtlFreeHeap(ProcessHeap,
	      0,
	      Buffer);

  return(STATUS_SUCCESS);
}


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,
					      0,
					      sizeof(INICACHESECTION));
  if (Section == NULL)
    {
      DPRINT("RtlAllocateHeap() failed\n");
      return(NULL);
    }
  RtlZeroMemory(Section,
		sizeof(INICACHESECTION));

  /* 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 */
