/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS cabinet manager
 * FILE:        tools/cabman/dfp.cpp
 * PURPOSE:     Directive file parser
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 *              Colin Finck <mail@colinfinck.de>
 * NOTES:       The directive file format is similar to the
 *              directive file format used by Microsoft's MAKECAB
 * REVISIONS:
 *   CSH 21/03-2001 Created
 *   CSH 15/08-2003 Made it portable
 *   CF  04/05-2007 Reformatted the code to be more consistent and use TABs instead of spaces
 *   CF  04/05-2007 Made it compatible with 64-bit operating systems
 *   CF  18/08-2007 Use typedefs64.h and the Windows types for compatibility with 64-bit operating systems
 */
#include <stdlib.h>
#include <stdio.h>
#include "cabinet.h"
#include "dfp.h"


#if defined(WIN32)
#define GetSizeOfFile(handle) _GetSizeOfFile(handle)
static LONG _GetSizeOfFile(FILEHANDLE handle)
{
	ULONG size = GetFileSize(handle, NULL);
	if (size == INVALID_FILE_SIZE)
		return -1;

	return size;
}
#define ReadFileData(handle, buffer, size, bytesread) _ReadFileData(handle, buffer, size, bytesread)
static bool _ReadFileData(FILEHANDLE handle, void* buffer, ULONG size, PULONG bytesread)
{
	return ReadFile(handle, buffer, size, (LPDWORD)bytesread, NULL);
}
#else
#define GetSizeOfFile(handle) _GetSizeOfFile(handle)
static LONG _GetSizeOfFile(FILEHANDLE handle)
{
	LONG size;
	fseek(handle, 0, SEEK_END);
	size = ftell(handle);
	fseek(handle, 0, SEEK_SET);
	return size;
}
#define ReadFileData(handle, buffer, size, bytesread) _ReadFileData(handle, buffer, size, bytesread)
static bool _ReadFileData(FILEHANDLE handle, void* buffer, ULONG size, PULONG bytesread)
{
	*bytesread = fread(buffer, 1, size, handle);
	return *bytesread == size;
}
#endif

/* CDFParser */

CDFParser::CDFParser()
/*
 * FUNCTION: Default constructor
 */
{
	InfFileOnly     = false;
	DontGenerateInf = false;

	FileBuffer     = NULL;
	FileLoaded     = false;
	CurrentOffset  = 0;
	CurrentLine    = 0;
	CabinetCreated = false;
	DiskCreated    = false;
	FolderCreated  = false;
	CabinetName    = NULL;
	DiskLabel      = NULL;
	MaxDiskSize    = NULL;

	MaxDiskSizeAllSet      = false;
	CabinetNameTemplateSet = false;
	DiskLabelTemplateSet   = false;
	InfFileNameSet         = false;

	InfModeEnabled = false;
	InfFileHandle = NULL;

	strcpy(FileRelativePath, "");
}

CDFParser::~CDFParser()
/*
 * FUNCTION: Default destructor
 */
{
	PCABINET_NAME CNPrev;
	PCABINET_NAME CNNext;
	PDISK_NUMBER DNPrev;
	PDISK_NUMBER DNNext;

	if (FileBuffer)
		FreeMemory(FileBuffer);
	CNNext = CabinetName;
	while (CNNext != NULL)
	{
		CNPrev = CNNext->Next;
		FreeMemory(CNNext);
		CNNext = CNPrev;
	}
	CNNext = DiskLabel;
	while (CNNext != NULL)
	{
		CNPrev = CNNext->Next;
		FreeMemory(CNNext);
		CNNext = CNPrev;
	}
	DNNext = MaxDiskSize;
	while (DNNext != NULL)
	{
		DNPrev = DNNext->Next;
		FreeMemory(DNNext);
		DNNext = DNPrev;
	}

	if (InfFileHandle != NULL)
		CloseFile(InfFileHandle);
}

void CDFParser::WriteInfLine(char* InfLine)
{
	char buf[MAX_PATH];
	char eolbuf[2];
	char* destpath;
#if defined(WIN32)
	ULONG BytesWritten;
#endif

	if (DontGenerateInf)
		return;

	if (InfFileHandle == NULL)
	{
		if (!InfFileNameSet)
			/* FIXME: Use cabinet name with extension .inf */
			return;

		destpath = GetDestinationPath();
		if (strlen(destpath) > 0)
		{
			strcpy(buf, destpath);
			strcat(buf, InfFileName);
		}
		else
			strcpy(buf, InfFileName);

		/* Create .inf file, overwrite if it already exists */
#if defined(WIN32)
		InfFileHandle = CreateFile(buf,     // Create this file
			GENERIC_WRITE,                  // Open for writing
			0,                              // No sharing
			NULL,                           // No security
			CREATE_ALWAYS,                  // Create or overwrite
			FILE_ATTRIBUTE_NORMAL,          // Normal file 
			NULL);                          // No attribute template
		if (InfFileHandle == INVALID_HANDLE_VALUE)
		{
			DPRINT(MID_TRACE, ("Error creating '%lu'.\n", (ULONG)GetLastError()));
			return;
		}
#else /* !WIN32 */
		InfFileHandle = fopen(buf, "wb"); 
		if (InfFileHandle == NULL)
		{
			DPRINT(MID_TRACE, ("Error creating '%lu'.\n", (ULONG)errno));
			return;
		}
#endif
	}

#if defined(WIN32)
	if (!WriteFile(InfFileHandle, InfLine, (DWORD)strlen(InfLine), (LPDWORD)&BytesWritten, NULL))
	{
		DPRINT(MID_TRACE, ("ERROR WRITING '%lu'.\n", (ULONG)GetLastError()));
		return;
	}
#else
	if (fwrite(InfLine, strlen(InfLine), 1, InfFileHandle) < 1)
		return;
#endif

	eolbuf[0] = 0x0d;
	eolbuf[1] = 0x0a;

#if defined(WIN32)
	if (!WriteFile(InfFileHandle, eolbuf, sizeof(eolbuf), (LPDWORD)&BytesWritten, NULL))
	{
		DPRINT(MID_TRACE, ("ERROR WRITING '%lu'.\n", (ULONG)GetLastError()));
		return;
	}
#else
	if (fwrite(eolbuf, 1, sizeof(eolbuf), InfFileHandle) < 1)
		return;
#endif
}


ULONG CDFParser::Load(char* FileName)
/*
 * FUNCTION: Loads a directive file into memory
 * ARGUMENTS:
 *     FileName = Pointer to name of directive file
 * RETURNS:
 *     Status of operation
 */
{
	ULONG BytesRead;
	LONG FileSize;

	if (FileLoaded)
		return CAB_STATUS_SUCCESS;

	/* Create cabinet file, overwrite if it already exists */
#if defined(WIN32)
	FileHandle = CreateFile(FileName, // Create this file
		GENERIC_READ,                 // Open for reading
		0,                            // No sharing
		NULL,                         // No security
		OPEN_EXISTING,                // Open the file
		FILE_ATTRIBUTE_NORMAL,        // Normal file
		NULL);                        // No attribute template
	if (FileHandle == INVALID_HANDLE_VALUE)
		return CAB_STATUS_CANNOT_OPEN;
#else /* !WIN32 */
	FileHandle = fopen(FileName, "rb"); 
	if (FileHandle == NULL)
		return CAB_STATUS_CANNOT_OPEN;
#endif

	FileSize = GetSizeOfFile(FileHandle);
	if (FileSize == -1)
	{
		CloseFile(FileHandle);
		return CAB_STATUS_CANNOT_OPEN;
	}

	FileBufferSize = (ULONG)FileSize;

	FileBuffer = (char*)AllocateMemory(FileBufferSize);
	if (!FileBuffer)
	{
		CloseFile(FileHandle);
		return CAB_STATUS_NOMEMORY;
	}

	if (!ReadFileData(FileHandle, FileBuffer, FileBufferSize, &BytesRead))
	{
		CloseFile(FileHandle);
		FreeMemory(FileBuffer);
		FileBuffer = NULL;
		return CAB_STATUS_CANNOT_READ;
	}

	CloseFile(FileHandle);

	FileLoaded = true;

	DPRINT(MAX_TRACE, ("File (%lu bytes)\n", FileBufferSize));

	return CAB_STATUS_SUCCESS;
}


ULONG CDFParser::Parse()
/*
 * FUNCTION: Parses a loaded directive file
 * RETURNS:
 *     Status of operation
 */
{
	bool Command;
	ULONG Status;

	if (!FileLoaded)
		return CAB_STATUS_NOFILE;

	while (ReadLine())
	{
		Command = false;

		if (InfModeEnabled)
		{
			bool WriteLine = true;
			while (CurrentToken != TokenEnd)
			{
				switch (CurrentToken)
				{
					case TokenIdentifier:
						if (Command)
						{
							/* Command */
							Status = PerformCommand();
							if (Status == CAB_STATUS_FAILURE)
								WriteLine = true;
							else
								if (!InfModeEnabled)
									WriteLine = false;

							CurrentToken = TokenEnd;
							continue;
						}
						else
						{
							WriteLine = true;
							CurrentToken = TokenEnd;
							continue;
						}
						break;

					case TokenSpace:
						break;

					case TokenPeriod:
						Command = true;
						break;

					default:
						WriteLine = true;
						CurrentToken = TokenEnd;
						continue;
				}
				NextToken();
			}
			if (WriteLine)
				WriteInfLine(Line);
		}
		else
		{
			while (CurrentToken != TokenEnd)
			{
				switch (CurrentToken)
				{
					case TokenInteger:
						sprintf(CurrentString, "%lu", CurrentInteger);
					case TokenIdentifier:
					case TokenString:
						if (Command)
						{
							/* Command */
							Status = PerformCommand();

							if (Status == CAB_STATUS_FAILURE)
							{
								printf("Directive file contains errors at line %lu.\n", (ULONG)CurrentLine);
								DPRINT(MID_TRACE, ("Error while executing command.\n"));
							}

							if (Status != CAB_STATUS_SUCCESS)
								return Status;
						}
						else
						{
							/* File copy */
							Status = PerformFileCopy();

							if (Status != CAB_STATUS_SUCCESS)
							{
								printf("Directive file contains errors at line %lu.\n", (ULONG)CurrentLine);
								DPRINT(MID_TRACE, ("Error while copying file.\n"));
							}

							if (Status != CAB_STATUS_SUCCESS)
								return Status;
						}
						break;

					case TokenSpace:
						break;

					case TokenSemi:
						CurrentToken = TokenEnd;
						continue;

					case TokenPeriod:
						Command = true;
						break;

					default:
						printf("Directive file contains errors at line %lu.\n", (ULONG)CurrentLine);
						DPRINT(MID_TRACE, ("Token is (%lu).\n", (ULONG)CurrentToken));
						return CAB_STATUS_SUCCESS;
					}
					NextToken();
			}
		}
	}

	if (!InfFileOnly)
	{
		  printf("\nWriting cabinet. This may take a while...\n\n");

		if (DiskCreated)
		{
			Status = WriteDisk(false);
			if (Status == CAB_STATUS_SUCCESS)
				Status = CloseDisk();
			if (Status != CAB_STATUS_SUCCESS)
			{
				DPRINT(MIN_TRACE, ("Cannot write disk (%lu).\n", (ULONG)Status));
				return Status;
			}
		}

		if (CabinetCreated)
		{
			Status = CloseCabinet();
			if (Status != CAB_STATUS_SUCCESS)
			{
				DPRINT(MIN_TRACE, ("Cannot close cabinet (%lu).\n", (ULONG)Status));
				return Status;
			}
		}

		printf("\nDone.\n");
	}

	return CAB_STATUS_SUCCESS;
}


void CDFParser::SetFileRelativePath(char* Path)
/*
 * FUNCTION: Sets path where files in the .dff is assumed relative to
 * ARGUMENTS:
 *    Path = Pointer to string with path
 */
{
	strcpy(FileRelativePath, Path);
	ConvertPath(FileRelativePath, false);
	if (strlen(FileRelativePath) > 0)
		NormalizePath(FileRelativePath, MAX_PATH);
}


bool CDFParser::OnDiskLabel(ULONG Number, char* Label)
/*
 * FUNCTION: Called when a disk needs a label
 * ARGUMENTS:
 *     Number = Cabinet number that needs a label
 *     Label  = Pointer to buffer to place label of disk
 * RETURNS:
 *     true if a disk label was returned, false if not
 */
{
	char Buffer[20];
	ULONG i;
	int j;
	char ch;

	Number += 1;

	DPRINT(MID_TRACE, ("Giving disk (%lu) a label...\n", (ULONG)Number));

	if (GetDiskName(&DiskLabel, Number, Label))
		return true;

	if (DiskLabelTemplateSet)
	{
		j = 0;
		strcpy(Label, "");
		for (i = 0; i < strlen(DiskLabelTemplate); i++)
		{
			ch = DiskLabelTemplate[i];
			if (ch == '*')
			{
				sprintf(Buffer, "%lu", Number);
				strcat(Label, Buffer);
				j += (LONG)strlen(Buffer);
			}
			else
			{
				Label[j] = ch;
				j++;
			}
			Label[j] = '\0';
		}

		DPRINT(MID_TRACE, ("Giving disk (%s) as a label...\n", Label));

		return true;
	}
	else
		return false;
}


bool CDFParser::OnCabinetName(ULONG Number, char* Name)
/*
 * FUNCTION: Called when a cabinet needs a name
 * ARGUMENTS:
 *     Number = Disk number that needs a name
 *     Name   = Pointer to buffer to place name of cabinet
 * RETURNS:
 *     true if a cabinet name was returned, false if not
 */
{
	char Buffer[MAX_PATH];
	ULONG i;
	int j;
	char ch;

	Number += 1;

	DPRINT(MID_TRACE, ("Giving cabinet (%lu) a name...\n", (ULONG)Number));

	if (GetDiskName(&CabinetName, Number, Buffer))
	{
		strcpy(Name, GetDestinationPath());
		strcat(Name, Buffer);
		return true;
	}

	if (CabinetNameTemplateSet)
	{
		strcpy(Name, GetDestinationPath());
		j = (LONG)strlen(Name);
		for (i = 0; i < strlen(CabinetNameTemplate); i++)
		{
			ch = CabinetNameTemplate[i];
			if (ch == '*')
			{
				sprintf(Buffer, "%lu", Number);
				strcat(Name, Buffer);
				j += (LONG)strlen(Buffer);
			}
			else
			{
				Name[j] = ch;
				j++;
			}
			Name[j] = '\0';
		}

		DPRINT(MID_TRACE, ("Giving cabinet (%s) as a name...\n", Name));
		return true;
	}
	else
		return false;
}


bool CDFParser::SetDiskName(PCABINET_NAME *List, ULONG Number, char* String)
/*
 * FUNCTION: Sets an entry in a list
 * ARGUMENTS:
 *     List   = Address of pointer to list
 *     Number = Disk number
 *     String = Pointer to string
 * RETURNS:
 *     false if there was not enough free memory available
 */
{
	PCABINET_NAME CN;

	CN = *List;
	while (CN != NULL)
	{
		if (CN->DiskNumber == Number)
		{
			strcpy(CN->Name, String);
			return true;
		}
		CN = CN->Next;
	}

	CN = (PCABINET_NAME)AllocateMemory(sizeof(CABINET_NAME));
	if (!CN)
		return false;

	CN->DiskNumber = Number;
	strcpy(CN->Name, String);

	CN->Next = *List;
	*List = CN;

	return true;
}


bool CDFParser::GetDiskName(PCABINET_NAME *List, ULONG Number, char* String)
/*
 * FUNCTION: Returns an entry in a list
 * ARGUMENTS:
 *     List   = Address of pointer to list
 *     Number = Disk number
 *     String = Address of buffer to copy string to
 * RETURNS:
 *     false if there was not enough free memory available
 */
{
	PCABINET_NAME CN;

	CN = *List;
	while (CN != NULL)
	{
		if (CN->DiskNumber == Number)
		{
			strcpy(String, CN->Name);
			return true;
		}
		CN = CN->Next;
	}

	return false;
}


bool CDFParser::SetDiskNumber(PDISK_NUMBER *List, ULONG Number, ULONG Value)
/*
 * FUNCTION: Sets an entry in a list
 * ARGUMENTS:
 *     List   = Address of pointer to list
 *     Number = Disk number
 *     Value  = Value to set
 * RETURNS:
 *     false if there was not enough free memory available
 */
{
	PDISK_NUMBER DN;

	DN = *List;
	while (DN != NULL)
	{
		if (DN->DiskNumber == Number)
		{
			DN->Number = Value;
			return true;
		}
		DN = DN->Next;
	}

	DN = (PDISK_NUMBER)AllocateMemory(sizeof(DISK_NUMBER));
	if (!DN)
		return false;

	DN->DiskNumber = Number;
	DN->Number = Value;

	DN->Next = *List;
	*List = DN;

	return true;
}


bool CDFParser::GetDiskNumber(PDISK_NUMBER *List, ULONG Number, PULONG Value)
/*
 * FUNCTION: Returns an entry in a list
 * ARGUMENTS:
 *     List   = Address of pointer to list
 *     Number = Disk number
 *     Value  = Address of buffer to place value
 * RETURNS:
 *     true if the entry was found
 */
{
	PDISK_NUMBER DN;

	DN = *List;
	while (DN != NULL)
	{
		if (DN->DiskNumber == Number)
		{
			*Value = DN->Number;
			return true;
		}
		DN = DN->Next;
	}

	return false;
}


bool CDFParser::DoDiskLabel(ULONG Number, char* Label)
/*
 * FUNCTION: Sets the label of a disk
 * ARGUMENTS:
 *     Number = Disk number
 *     Label  = Pointer to label of disk
 * RETURNS:
 *     false if there was not enough free memory available
 */
{
	DPRINT(MID_TRACE, ("Setting label of disk (%lu) to '%s'\n", (ULONG)Number, Label));

	return SetDiskName(&DiskLabel, Number, Label);
}


void CDFParser::DoDiskLabelTemplate(char* Template)
/*
 * FUNCTION: Sets a disk label template to use
 * ARGUMENTS:
 *     Template = Pointer to disk label template
 */
{
	DPRINT(MID_TRACE, ("Setting disk label template to '%s'\n", Template));

	strcpy(DiskLabelTemplate, Template);
	DiskLabelTemplateSet = true;
}


bool CDFParser::DoCabinetName(ULONG Number, char* Name)
/*
 * FUNCTION: Sets the name of a cabinet
 * ARGUMENTS:
 *     Number = Disk number
 *     Name   = Pointer to name of cabinet
 * RETURNS:
 *     false if there was not enough free memory available
 */
{
	DPRINT(MID_TRACE, ("Setting name of cabinet (%lu) to '%s'\n", (ULONG)Number, Name));

	return SetDiskName(&CabinetName, Number, Name);
}


void CDFParser::DoCabinetNameTemplate(char* Template)
/*
 * FUNCTION: Sets a cabinet name template to use
 * ARGUMENTS:
 *     Template = Pointer to cabinet name template
 */
{
	DPRINT(MID_TRACE, ("Setting cabinet name template to '%s'\n", Template));

	strcpy(CabinetNameTemplate, Template);
	CabinetNameTemplateSet = true;
}


ULONG CDFParser::DoMaxDiskSize(bool NumberValid, ULONG Number)
/*
 * FUNCTION: Sets the maximum disk size
 * ARGUMENTS:
 *     NumberValid = true if disk number is valid
 *     Number      = Disk number
 * RETURNS:
 *     Status of operation
 * NOTES:
 *     Standard sizes are 2.88M, 1.44M, 1.25M, 1.2M, 720K, 360K, and CDROM
 */
{
	ULONG A, B, Value;

	if (IsNextToken(TokenInteger, true))
	{
		A = CurrentInteger;

		if (IsNextToken(TokenPeriod, false))
		{
			if (!IsNextToken(TokenInteger, false))
				return CAB_STATUS_FAILURE;

			B = CurrentInteger;

		}
		else
			B = 0;

		if (CurrentToken == TokenIdentifier)
		{
			switch (CurrentString[0])
			{
				case 'K':
					if (B != 0)
						return CAB_STATUS_FAILURE;

					if (A == 720)
						/* 720K disk */
						Value = 730112;
					else if (A == 360)
						/* 360K disk */
						Value = 362496;
					else
						return CAB_STATUS_FAILURE;
					break;

				case 'M':
					if (A == 1)
					{
						if (B == 44)
							/* 1.44M disk */
							Value = 1457664;
						else if (B == 25)
							/* 1.25M disk */
							Value = 1300000; // FIXME: Value?
						else if (B == 2)
							/* 1.2M disk */
							Value = 1213952;
						else
							return CAB_STATUS_FAILURE;
					}
					else if (A == 2)
					{
						if (B == 88)
							/* 2.88M disk */
							Value = 2915328;
						else
							return CAB_STATUS_FAILURE;
					}
					else
						return CAB_STATUS_FAILURE;
					break;

				default:
					DPRINT(MID_TRACE, ("Bad suffix (%c)\n", CurrentString[0]));
					return CAB_STATUS_FAILURE;
			}
		}
		else
			Value = A;
	}
	else
	{
		if ((CurrentToken != TokenString) &&
			(strcasecmp(CurrentString, "CDROM") != 0))
			return CAB_STATUS_FAILURE;
		/* CDROM */
		Value = 640*1024*1024;  // FIXME: Correct size for CDROM?
	}

	if (NumberValid)
		return (SetDiskNumber(&MaxDiskSize, Number, Value)?
			CAB_STATUS_SUCCESS : CAB_STATUS_FAILURE);

	MaxDiskSizeAll    = Value;
	MaxDiskSizeAllSet = true;

	SetMaxDiskSize(Value);

	return CAB_STATUS_SUCCESS;
}


void CDFParser::DoInfFileName(char* FileName)
/*
 * FUNCTION: Sets filename of the generated .inf file
 * ARGUMENTS:
 *     FileName = Pointer to .inf filename
 */
{
	DPRINT(MID_TRACE, ("Setting .inf filename to '%s'\n", FileName));

	strcpy(InfFileName, FileName);
	InfFileNameSet = true;
}

ULONG CDFParser::SetupNewDisk()
/*
 * FUNCTION: Sets up parameters for a new disk
 * RETURNS:
 *     Status of operation
 */
{
	ULONG Value;

	if (!GetDiskNumber(&MaxDiskSize, GetCurrentDiskNumber(), &Value))
	{
		if (MaxDiskSizeAllSet)
			Value = MaxDiskSizeAll;
		else
			Value = 0;
	}
	SetMaxDiskSize(Value);

	return CAB_STATUS_SUCCESS;
}


ULONG CDFParser::PerformSetCommand()
/*
 * FUNCTION: Performs a set variable command
 * RETURNS:
 *     Status of operation
 */
{
	SETTYPE SetType;
	bool NumberValid = false;
	ULONG Number = 0;

	if (!IsNextToken(TokenIdentifier, true))
		return CAB_STATUS_FAILURE;

	if (strcasecmp(CurrentString, "DiskLabel") == 0)
		SetType = stDiskLabel;
	else if (strcasecmp(CurrentString, "DiskLabelTemplate") == 0)
		SetType = stDiskLabelTemplate;
	else if (strcasecmp(CurrentString, "CabinetName") == 0)
		SetType = stCabinetName;
	else if (strcasecmp(CurrentString, "CabinetNameTemplate") == 0)
		SetType = stCabinetNameTemplate;
	else if (strcasecmp(CurrentString, "MaxDiskSize") == 0)
		SetType = stMaxDiskSize;
	else if (strcasecmp(CurrentString, "InfFileName") == 0)
		SetType = stInfFileName;
	else
		return CAB_STATUS_FAILURE;

	if ((SetType == stDiskLabel) || (SetType == stCabinetName))
	{
		if (!IsNextToken(TokenInteger, false))
			return CAB_STATUS_FAILURE;
		Number = CurrentInteger;

		if (!IsNextToken(TokenEqual, true))
			return CAB_STATUS_FAILURE;
	}
	else if (SetType == stMaxDiskSize)
	{
		if (IsNextToken(TokenInteger, false))
		{
			NumberValid = true;
			Number = CurrentInteger;
		}
		else
		{
			NumberValid = false;
			while (CurrentToken == TokenSpace)
				NextToken();
			if (CurrentToken != TokenEqual)
				return CAB_STATUS_FAILURE;
		}
	}
	else if (!IsNextToken(TokenEqual, true))
			return CAB_STATUS_FAILURE;

	if (SetType != stMaxDiskSize)
	{
		if (!IsNextToken(TokenString, true))
			return CAB_STATUS_FAILURE;
	}

	switch (SetType)
	{
		case stDiskLabel:
			if (!DoDiskLabel(Number, CurrentString))
				DPRINT(MIN_TRACE, ("Not enough available free memory.\n"));
			return CAB_STATUS_SUCCESS;

		case stCabinetName:
			if (!DoCabinetName(Number, CurrentString))
				DPRINT(MIN_TRACE, ("Not enough available free memory.\n"));
			return CAB_STATUS_SUCCESS;

		case stDiskLabelTemplate:
			DoDiskLabelTemplate(CurrentString);
			return CAB_STATUS_SUCCESS;

		case stCabinetNameTemplate:
			DoCabinetNameTemplate(CurrentString);
			return CAB_STATUS_SUCCESS;

		case stMaxDiskSize:
			return DoMaxDiskSize(NumberValid, Number);

		case stInfFileName:
			DoInfFileName(CurrentString);
			return CAB_STATUS_SUCCESS;

		default:
			return CAB_STATUS_FAILURE;
	}
}


ULONG CDFParser::PerformNewCommand()
/*
 * FUNCTION: Performs a new disk|cabinet|folder command
 * RETURNS:
 *     Status of operation
 */
{
	NEWTYPE NewType;
	ULONG Status;

	if (!IsNextToken(TokenIdentifier, true))
		return CAB_STATUS_FAILURE;

	if (strcasecmp(CurrentString, "Disk") == 0)
		NewType = ntDisk;
	else if (strcasecmp(CurrentString, "Cabinet") == 0)
		NewType = ntCabinet;
	else if (strcasecmp(CurrentString, "Folder") == 0)
		NewType = ntFolder;
	else
		return CAB_STATUS_FAILURE;

	switch (NewType)
	{
		case ntDisk:
			if (DiskCreated)
			{
				Status = WriteDisk(true);
				if (Status == CAB_STATUS_SUCCESS)
					Status = CloseDisk();
				if (Status != CAB_STATUS_SUCCESS)
				{
					DPRINT(MIN_TRACE, ("Cannot write disk (%lu).\n", (ULONG)Status));
					return CAB_STATUS_SUCCESS;
				}
				DiskCreated = false;
			}

			Status = NewDisk();
			if (Status != CAB_STATUS_SUCCESS)
			{
				DPRINT(MIN_TRACE, ("Cannot create disk (%lu).\n", (ULONG)Status));
				return CAB_STATUS_SUCCESS;
			}
			DiskCreated = true;
			SetupNewDisk();
			return CAB_STATUS_SUCCESS;

		case ntCabinet:
			if (DiskCreated)
			{
				Status = WriteDisk(true);
				if (Status == CAB_STATUS_SUCCESS)
					Status = CloseDisk();
				if (Status != CAB_STATUS_SUCCESS)
				{
					DPRINT(MIN_TRACE, ("Cannot write disk (%lu).\n", (ULONG)Status));
					return CAB_STATUS_SUCCESS;
				}
				DiskCreated = false;
			}

			Status = NewCabinet();
			if (Status != CAB_STATUS_SUCCESS)
			{
				DPRINT(MIN_TRACE, ("Cannot create cabinet (%lu).\n", (ULONG)Status));
				return CAB_STATUS_SUCCESS;
			}
			DiskCreated = true;
			SetupNewDisk();
			return CAB_STATUS_SUCCESS;

		case ntFolder:
			Status = NewFolder();
			ASSERT(Status == CAB_STATUS_SUCCESS);
			return CAB_STATUS_SUCCESS;

		default:
			return CAB_STATUS_FAILURE;
	}
}


ULONG CDFParser::PerformInfBeginCommand()
/*
 * FUNCTION: Begins inf mode
 * RETURNS:
 *     Status of operation
 */
{
	InfModeEnabled = true;
	return CAB_STATUS_SUCCESS;
}


ULONG CDFParser::PerformInfEndCommand()
/*
 * FUNCTION: Begins inf mode
 * RETURNS:
 *     Status of operation
 */
{
	InfModeEnabled = false;
	return CAB_STATUS_SUCCESS;
}


ULONG CDFParser::PerformCommand()
/*
 * FUNCTION: Performs a command
 * RETURNS:
 *     Status of operation
 */
{
	if (strcasecmp(CurrentString, "Set") == 0)
		return PerformSetCommand();
	if (strcasecmp(CurrentString, "New") == 0)
		return PerformNewCommand();
	if (strcasecmp(CurrentString, "InfBegin") == 0)
		return PerformInfBeginCommand();
	if (strcasecmp(CurrentString, "InfEnd") == 0)
		return PerformInfEndCommand();

	return CAB_STATUS_FAILURE;
}


ULONG CDFParser::PerformFileCopy()
/*
 * FUNCTION: Performs a file copy
 * RETURNS:
 *     Status of operation
 */
{
	ULONG Status;
	ULONG i, j;
	char ch;
	char SrcName[MAX_PATH];
	char DstName[MAX_PATH];
	char InfLine[MAX_PATH];
	char Options[128];
	char BaseFilename[MAX_PATH];

	*SrcName = '\0';
	*DstName = '\0';
	*Options = '\0';

	// source file
	i = CurrentChar;
	while ((i < LineLength) &&
		((ch = Line[i]) != ' ') &&
		 (ch != 0x09) &&
		 (ch != ';'))
	{
		CurrentString[i] = ch;
		i++;
	}
	CurrentString[i] = '\0';
	CurrentToken = TokenString;
	CurrentChar  = i + 1;
	strcpy(BaseFilename, CurrentString);
	strcat(SrcName, BaseFilename);

	// destination
	SkipSpaces();

	if (CurrentToken != TokenEnd)
	{
		j = (ULONG)strlen(CurrentString); i = 0;
		while ((CurrentChar + i < LineLength) &&
			((ch = Line[CurrentChar + i]) != ' ') &&
			 (ch != 0x09) &&
			 (ch != ';'))
		{
			CurrentString[j + i] = ch;
			i++;
		}
		CurrentString[j + i] = '\0';
		CurrentToken = TokenString;
		CurrentChar += i + 1;
		strcpy(DstName, CurrentString);
	}

	// options (it may be empty)
	SkipSpaces ();

	if (CurrentToken != TokenEnd)
	{
		j = (ULONG)strlen(CurrentString); i = 0;
		while ((CurrentChar + i < LineLength) &&
			((ch = Line[CurrentChar + i]) != ' ') &&
			 (ch != 0x09) &&
			 (ch != ';'))
		{
			CurrentString[j + i] = ch;
			i++;
		}
		CurrentString[j + i] = '\0';
		CurrentToken = TokenString;
		CurrentChar += i + 1;
		strcpy(Options, CurrentString);
	}

	if (!CabinetCreated)
	{
		DPRINT(MID_TRACE, ("Creating cabinet.\n"));

		Status = NewCabinet();
		if (Status != CAB_STATUS_SUCCESS)
		{
			DPRINT(MIN_TRACE, ("Cannot create cabinet (%lu).\n", (ULONG)Status));
			printf("Cannot create cabinet.\n");
			return CAB_STATUS_FAILURE;
		}
		CabinetCreated = true;

		DPRINT(MID_TRACE, ("Creating disk.\n"));

		Status = NewDisk();
		if (Status != CAB_STATUS_SUCCESS)
		{
			DPRINT(MIN_TRACE, ("Cannot create disk (%lu).\n", (ULONG)Status));
			printf("Cannot create disk.\n");
			return CAB_STATUS_FAILURE;
		}
		DiskCreated = true;
		SetupNewDisk();
	}

	DPRINT(MID_TRACE, ("Adding file: '%s'   destination: '%s'.\n", SrcName, DstName));

	Status = AddFile(SrcName);
	if (Status == CAB_STATUS_CANNOT_OPEN)
	{
		strcpy(SrcName, FileRelativePath);
		strcat(SrcName, BaseFilename);
		Status = AddFile(SrcName);
	}
	switch (Status)
	{
		case CAB_STATUS_SUCCESS:
			sprintf(InfLine, "%s=%s", GetFileName(SrcName), DstName);
			WriteInfLine(InfLine);
			break;

		case CAB_STATUS_CANNOT_OPEN:
			if (strstr(Options,"optional"))
			{
				Status = CAB_STATUS_SUCCESS;
				printf("Optional file does not exist: %s.\n", SrcName);
			}
			else
				printf("File does not exist: %s.\n", SrcName);
			
			break;

		case CAB_STATUS_NOMEMORY:
			printf("Insufficient memory to add file: %s.\n", SrcName);
			break;

		default:
			printf("Cannot add file: %s (%lu).\n", SrcName, Status);
			break;
	}
	return Status;
}


void CDFParser::SkipSpaces()
/*
 * FUNCTION: Skips any spaces in the current line
 */
{
	NextToken();
	while (CurrentToken == TokenSpace)
		NextToken();
}


bool CDFParser::IsNextToken(DFP_TOKEN Token, bool NoSpaces)
/*
 * FUNCTION: Checks if next token equals Token
 * ARGUMENTS:
 *     Token  = Token to compare with
 *     SkipSp = true if spaces should be skipped
 * RETURNS:
 *     false if next token is diffrent from Token
 */
{
	if (NoSpaces)
		SkipSpaces();
	else
		NextToken();
	return (CurrentToken == Token);
}


bool CDFParser::ReadLine()
/*
 * FUNCTION: Reads the next line into the line buffer
 * RETURNS:
 *     true if there is a new line, false if not
 */
{
	ULONG i, j;
	char ch;

	if (CurrentOffset >= FileBufferSize)
		return false;

	i = 0;
	while (((j = CurrentOffset + i) < FileBufferSize) && (i < 127) &&
		((ch = FileBuffer[j]) != 0x0D && (ch = FileBuffer[j]) != 0x0A))
	{
		Line[i] = ch;
		i++;
	}

	Line[i]    = '\0';
	LineLength = i;

	if ((FileBuffer[CurrentOffset + i] == 0x0D) && (FileBuffer[CurrentOffset + i + 1] == 0x0A))
		CurrentOffset++;

	CurrentOffset += i + 1;

	CurrentChar = 0;

	CurrentLine++;

	NextToken();

	return true;
}


void CDFParser::NextToken()
/*
 * FUNCTION: Reads the next token from the current line
 */
{
	ULONG i;
	char ch = ' ';

	if (CurrentChar >= LineLength)
	{
		CurrentToken = TokenEnd;
		return;
	}

	switch (Line[CurrentChar])
	{
		case ' ':
		case 0x09:
			CurrentToken = TokenSpace;
			break;

		case ';':
			CurrentToken = TokenSemi;
			break;

		case '=':
			CurrentToken = TokenEqual;
			break;

		case '.':
			CurrentToken = TokenPeriod;
			break;

		case '\\':
			CurrentToken = TokenBackslash;
			break;

		case '"':
			i = 0;
			while ((CurrentChar + i + 1 < LineLength) &&
				((ch = Line[CurrentChar + i + 1]) != '"'))
			{
				CurrentString[i] = ch;
				i++;
			}
			CurrentString[i] = '\0';
			CurrentToken = TokenString;
			CurrentChar += i + 2;
			return;

		default:
			i = 0;
			while ((CurrentChar + i < LineLength) &&
				((ch = Line[CurrentChar + i]) >= '0') && (ch <= '9'))
			{
				CurrentString[i] = ch;
				i++;
			}
			if (i > 0)
			{
				CurrentString[i] = '\0';
				CurrentInteger = atoi((char*)CurrentString);
				CurrentToken = TokenInteger;
				CurrentChar += i;
				return;
			}
			i = 0;
			while (((CurrentChar + i < LineLength) &&
				(((ch = Line[CurrentChar + i]) >= 'a') && (ch <= 'z')) ||
				((ch >= 'A') && (ch <= 'Z')) || (ch == '_')))
			{
				CurrentString[i] = ch;
				i++;
			}
			if (i > 0)
			{
				CurrentString[i] = '\0';
				CurrentToken = TokenIdentifier;
				CurrentChar += i;
				return;
			}
			CurrentToken = TokenEnd;
	}
	CurrentChar++;
}

/* EOF */
