/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 *  drivers.c - Driver Loading Functions.
 *
 *  Copyright (c) 2000 Apple Computer, Inc.
 *
 *  DRI: Josh de Cesare
 */

#include "libsaio.h"
#include "memory.h"
#include "kernBootStruct.h"
#include "nbp.h"
#include "boot.h"

enum {
  kTagTypeNone = 0,
  kTagTypeDict,
  kTagTypeKey,
  kTagTypeString,
  kTagTypeInteger,
  kTagTypeData,
  kTagTypeDate,
  kTagTypeFalse,
  kTagTypeTrue,
  kTagTypeArray
};

#define kXMLTagPList   "plist "
#define kXMLTagDict    "dict"
#define kXMLTagKey     "key"
#define kXMLTagString  "string"
#define kXMLTagInteger "integer"
#define kXMLTagData    "data"
#define kXMLTagDate    "date"
#define kXMLTagFalse   "false/"
#define kXMLTagTrue    "true/"
#define kXMLTagArray   "array"

#define kPropCFBundleIdentifier ("CFBundleIdentifier")
#define kPropCFBundleExecutable ("CFBundleExecutable")
#define kPropOSBundleRequired   ("OSBundleRequired")
#define kPropOSBundleLibraries  ("OSBundleLibraries")
#define kPropIOKitPersonalities ("IOKitPersonalities")
#define kPropIONameMatch        ("IONameMatch")

struct Tag {
  long       type;
  char       *string;
  struct Tag *tag;
  struct Tag *tagNext;
};
typedef struct Tag Tag, *TagPtr;

struct Module {  
  struct Module *nextModule;
  long          willLoad;
  TagPtr        dict;
  char          *plistAddr;
  long          plistLength;
  char          *driverPath;
};
typedef struct Module Module, *ModulePtr;

struct DriverInfo {
  char *plistAddr;
  long plistLength;
  void *moduleAddr;
  long moduleLength;
};
typedef struct DriverInfo DriverInfo, *DriverInfoPtr;

#define kDriverPackageSignature1 'MKXT'
#define kDriverPackageSignature2 'MOSX'

struct DriversPackage {
  unsigned long signature1;
  unsigned long signature2;
  unsigned long length;
  unsigned long alder32;
  unsigned long version;
  unsigned long numDrivers;
  unsigned long reserved1;
  unsigned long reserved2;
};
typedef struct DriversPackage DriversPackage;

static long FileLoadDrivers(char *dirSpec, long plugin);
static long NetLoadDrivers(char *dirSpec);
static long LoadDriverMKext(char *fileSpec);
static long LoadDriverPList(char *dirSpec, char *name);
static long LoadMatchedModules(void);
static long MatchPersonalities(void);
static long MatchLibraries(void);
static TagPtr GetProperty(TagPtr dict, char *key);
// static ModulePtr FindModule(char *name);
static long ParseXML(char *buffer, ModulePtr *module, TagPtr *personalities);
static long ParseNextTag(char *buffer, TagPtr *tag);
static long ParseTagList(char *buffer, TagPtr *tag, long type, long empty);
static long ParseTagKey(char *buffer, TagPtr *tag);
static long ParseTagString(char *buffer, TagPtr *tag);
static long ParseTagInteger(char *buffer, TagPtr *tag);
static long ParseTagData(char *buffer, TagPtr *tag);
static long ParseTagDate(char *buffer, TagPtr *tag);
static long ParseTagBoolean(char *buffer, TagPtr *tag, long type);
static long GetNextTag(char *buffer, char **tag, long *start);
static long FixDataMatchingTag(char *buffer, char *tag);
static TagPtr NewTag(void);
static void FreeTag(TagPtr tag);
static char *NewSymbol(char *string);
static void FreeSymbol(char *string);
// static void DumpTag(TagPtr tag, long depth);

static ModulePtr gModuleHead, gModuleTail;
static TagPtr    gPersonalityHead, gPersonalityTail;
static char *    gExtensionsSpec;
static char *    gDriverSpec;
static char *    gFileSpec;

//==========================================================================
// BootX shim functions.

#include <ufs/ufs/dir.h>

#define kLoadAddr     TFTP_ADDR
#define kLoadSize     TFTP_LEN

#define kPageSize     4096
#define RoundPage(x)  ((((unsigned)(x)) + kPageSize - 1) & ~(kPageSize - 1))

static long
LoadFile( char * fileSpec )
{
    unsigned long count = TFTP_LEN;
    unsigned long addr  = TFTP_ADDR;

	if ( gBootDev == kBootDevNetwork )
    {
        if ( nbpTFTPReadFile(fileSpec, &count, addr) != nbpStatusSuccess )
            return -1;
    }
    else
    {
        int fd = open( fileSpec, 0 );
        if ( fd < 0 )
            return -1;

        count = read( fd, (char *) addr, count );
        close(fd);
    }
    return count;
}

static long  gImageFirstBootXAddr;
static long  gImageLastKernelAddr;

static void *
AllocateBootXMemory( long size )
{
	long addr = gImageFirstBootXAddr - size;
  
	if ( addr < gImageLastKernelAddr ) return 0;

    bzero(addr, size);

	gImageFirstBootXAddr = addr;
  
	return (void *)addr;
}

static long
AllocateKernelMemory( long inSize )
{
	long addr = gImageLastKernelAddr;

    gImageLastKernelAddr += RoundPage(inSize);

    if ( gImageLastKernelAddr > gImageFirstBootXAddr )
        stop( "AllocateKernelMemory error" );
        
    kernBootStruct->ksize = gImageLastKernelAddr - kernBootStruct->kaddr;

    return addr;
}

static long
AllocateMemoryRange(char * rangeName, long start, long length, long type)
{
    if ( kernBootStruct->numBootDrivers < NDRIVERS )
    {
        int num = kernBootStruct->numBootDrivers;

        kernBootStruct->driverConfig[num].address = start;
        kernBootStruct->driverConfig[num].size    = length;
        kernBootStruct->driverConfig[num].type    = type;
        kernBootStruct->numBootDrivers++;
    }
    else
    {
        stop( "AllocateMemoryRange error" );
    }
    return 0;
}

// Map BootX file types to UFS file types defined in ufs/ufs/dir.h.

enum {
	kUnknownFileType    = DT_UNKNOWN,
	kFlatFileType       = DT_REG,
	kDirectoryFileType  = DT_DIR,
	kLinkFileType       = DT_LNK
};

static long 
GetFileInfo( char * dirSpec, char * name, long * flags, long * time )
{
    struct dirstuff * dir;
    struct direct *   entry = 0;

    dir = opendir(dirSpec);
    if ( dir )
    {
        while (( entry = readdir(dir) ))
        {
            if ( strcmp( entry->d_name, name ) == 0 )
            {
                *flags = entry->d_type;
                *time  = 0;
                break;
            }
        }
        closedir(dir);
    }
    return ( entry ) ? 0 : -1;
}

// Map BootX types to boot counterparts.

#define gBootFileType    gBootDev
enum {
    kNetworkDeviceType = kBootDevNetwork,
    kBlockDeviceType   = kBootDevHardDisk
};

static struct dirstuff *
OpenDir( char * dirSpec )
{
    return opendir(dirSpec);
}

static void
CloseDir( struct dirstuff * dirStuff )
{
    closedir(dirStuff);
}

static long
GetDirEntry( struct dirstuff * dir, long * dirIndex, char ** name, 
             long * flags, long * time)
{
    if ( dir )
    {
        struct direct * entry = readdir(dir);
    
        if ( entry )
        {
            *name  = entry->d_name;
            *flags = entry->d_type;
            *time  = 0;
            return 0;
        }
    }
    return (-1);
}

static long
InitDriverSupport()
{
    gExtensionsSpec = (char *) malloc( 4096 );
    gDriverSpec     = (char *) malloc( 4096 );
    gFileSpec       = (char *) malloc( 4096 );

    if ( !gExtensionsSpec || !gDriverSpec || !gFileSpec )
        stop( "InitDriverSupport error" );

    gImageLastKernelAddr = RoundPage( kernBootStruct->kaddr +
                                      kernBootStruct->ksize );

    gImageFirstBootXAddr = ( KERNEL_ADDR + KERNEL_LEN );

    return 0;
}

static unsigned long
Alder32( unsigned char * buffer, long length )
{
    long          cnt;
    unsigned long result, lowHalf, highHalf;
    
    lowHalf  = 1;
    highHalf = 0;
  
	for ( cnt = 0; cnt < length; cnt++ )
    {
        if ((cnt % 5000) == 0)
        {
            lowHalf  %= 65521L;
            highHalf %= 65521L;
        }
    
        lowHalf  += buffer[cnt];
        highHalf += lowHalf;
    }

	lowHalf  %= 65521L;
	highHalf %= 65521L;
  
	result = (highHalf << 16) | lowHalf;
  
	return result;
}

//==========================================================================
// LoadDrivers

long LoadDrivers( char * dirSpec )
{
    if ( InitDriverSupport() != 0 )
        return 0;

    if ( gBootFileType == kNetworkDeviceType )
    {
        NetLoadDrivers(dirSpec);
    }
    else /* if ( gBootFileType == kBlockDeviceType ) */
    {
        strcpy(gExtensionsSpec, dirSpec);
        strcat(gExtensionsSpec, "System/Library/");
        FileLoadDrivers(gExtensionsSpec, 0);
    }
#if 0
    else
    {
        return 0;
    }
#endif

    MatchPersonalities();
  
    MatchLibraries();

    LoadMatchedModules();

    return 0;
}

//==========================================================================
// FileLoadDrivers

static long
FileLoadDrivers( char * dirSpec, long plugin )
{
    long   ret, length, index, flags, time;
    char * name;
    struct dirstuff * dir;

    if ( !plugin )
    {
        long time2;

        ret = GetFileInfo(dirSpec, "Extensions.mkext", &flags, &time);
        if ((ret == 0) && (flags == kFlatFileType))
        {
            ret = GetFileInfo(dirSpec, "Extensions", &flags, &time2);
            if ((ret != 0) || (flags == kDirectoryFileType) || (time > time2))
            {
                sprintf(gDriverSpec, "%sExtensions.mkext", dirSpec);
                verbose("LoadDrivers: Loading from [%s]\n", gDriverSpec);
                if (LoadDriverMKext(gDriverSpec) == 0) return 0;
            }
        }

        strcat(dirSpec, "Extensions");
    }

    verbose("LoadDrivers: Loading from [%s]\n", dirSpec);

    // INTEL addition
    dir = OpenDir( dirSpec );

    index = 0;
    while (1) {
        // INTEL modification
        ret = GetDirEntry(dir, &index, &name, &flags, &time);
        if (ret == -1) break;

        // Make sure this is a directory.
        if (flags != kDirectoryFileType) continue;
        
        // Make sure this is a kext.
        length = strlen(name);
        if (strcmp(name + length - 5, ".kext")) continue;

        if (!plugin)
            sprintf(gDriverSpec, "%s/%s/Contents/PlugIns", dirSpec, name);

        ret = LoadDriverPList(dirSpec, name);
        if (ret != 0)
        {
            // printf("LoadDrivers: failed\n");
        }

        if (!plugin) 
            ret = FileLoadDrivers(gDriverSpec, 1);
    }

    // INTEL addition
    if ( dir ) CloseDir( dir );
    
    return 0;
}

//==========================================================================
// 

static long
NetLoadDrivers( char * dirSpec )
{
    long tries;

#if 0
    long cnt;

    // Get the name of the kernel
    cnt = strlen(gBootFile);
    while (cnt--) {
        if ((gBootFile[cnt] == '\\')  || (gBootFile[cnt] == ',')) {
        cnt++;
        break;
        }
    }
#endif

    // INTEL modification
    sprintf(gDriverSpec, "%s%s.mkext", dirSpec, kernBootStruct->bootFile);
    
    verbose("NetLoadDrivers: Loading from [%s]\n", gDriverSpec);
    
    tries = 3;
    while (tries--)
    {
        if (LoadDriverMKext(gDriverSpec) == 0) break;
    }
    if (tries == -1) return -1;

    return 0;
}

//==========================================================================
// loadDriverMKext

static long
LoadDriverMKext( char * fileSpec )
{
    long             driversAddr, driversLength;
    char             segName[32];
    DriversPackage * package = (DriversPackage *)kLoadAddr;

#define GetPackageElement(e)     bswap32(package-> ## e)

    // Load the MKext.
    if (LoadFile(fileSpec) == -1) return -1;

    // Verify the MKext.
    if (( GetPackageElement(signature1) != kDriverPackageSignature1) ||
        ( GetPackageElement(signature2) != kDriverPackageSignature2) ||
        ( GetPackageElement(length)      > kLoadSize )               ||
        ( GetPackageElement(alder32)    !=
          Alder32((char *)&package->version, GetPackageElement(length) - 0x10) ) )
    {
        return -1;
    }

    // Make space for the MKext.
    driversLength = GetPackageElement(length);
    driversAddr   = AllocateKernelMemory(driversLength);
    
    // Copy the MKext.
    memcpy((void *)driversAddr, (void *)kLoadAddr, driversLength);

    // Add the MKext to the memory map.
    sprintf(segName, "DriversPackage-%lx", driversAddr);
    AllocateMemoryRange(segName, driversAddr, driversLength,
                        kBootDriverTypeMKEXT);

    return 0;
}

//==========================================================================
// LoadDriverPList

static long
LoadDriverPList( char * dirSpec, char * name )
{
    long      length, driverPathLength;
    ModulePtr module;
    TagPtr    personalities;
    char *    buffer = 0;
    char *	  tmpDriverPath = 0;
    long      ret = -1;

    do {
        // Save the driver path.
        
        sprintf(gFileSpec, "%s/%s/Contents/MacOS/", dirSpec, name);
        driverPathLength = strlen(gFileSpec);

        tmpDriverPath = malloc(driverPathLength + 1);
        if (tmpDriverPath == 0) break;

        strcpy(tmpDriverPath, gFileSpec);
  
        // Construct the file spec to the plist, then load it.

        sprintf(gFileSpec, "%s/%s/Contents/Info.plist", dirSpec, name);

        length = LoadFile(gFileSpec);
        if (length == -1) break;

        buffer = malloc(length + 1);
        if (buffer == 0) break;

        strncpy(buffer, (char *)kLoadAddr, length);

        // Parse the plist.

        ret = ParseXML(buffer, &module, &personalities);
        if (ret != 0) break;

        // Allocate memory for the driver path and the plist.

        module->driverPath = AllocateBootXMemory(driverPathLength + 1);
        module->plistAddr  = AllocateBootXMemory(length + 1);
  
        if ((module->driverPath == 0) || (module->plistAddr == 0))
            break;

        // Save the driver path in the module.

        strcpy(module->driverPath, tmpDriverPath);

        // Add the plist to the module.

        strncpy(module->plistAddr, (char *)kLoadAddr, length);
        module->plistLength = length + 1;
  
        // Add the module to the end of the module list.
        
        if (gModuleHead == 0)
            gModuleHead = module;
        else
            gModuleTail->nextModule = module;
        gModuleTail = module;
  
        // Add the persionalities to the personality list.
    
        if (personalities) personalities = personalities->tag;
        while (personalities != 0)
        {
            if (gPersonalityHead == 0)
                gPersonalityHead = personalities->tag;
            else
                gPersonalityTail->tagNext = personalities->tag;
            
            gPersonalityTail = personalities->tag;
            personalities = personalities->tagNext;
        }
        
        ret = 0;
    }
    while (0);
    
    if ( buffer )        free( buffer );
    if ( tmpDriverPath ) free( tmpDriverPath );

    return ret;
}

//==========================================================================
// LoadMatchedModules

static long
LoadMatchedModules( void )
{
    TagPtr        prop;
    ModulePtr     module;
    char          *fileName, segName[32];
    DriverInfoPtr driver;
    long          length, driverAddr, driverLength;
  
    module = gModuleHead;

    while (module != 0)
    {
        if (module->willLoad)
        {
            prop = GetProperty(module->dict, kPropCFBundleExecutable);
            if (prop != 0)
            {
                fileName = prop->string;
                sprintf(gFileSpec, "%s%s", module->driverPath, fileName);
                length = LoadFile(gFileSpec);
            }
            else
                length = 0;

            if (length != -1)
            {
                // Make make in the image area.
                driverLength = sizeof(DriverInfo) + module->plistLength + length;
                driverAddr = AllocateKernelMemory(driverLength);

                // Set up the DriverInfo.
                driver = (DriverInfoPtr)driverAddr;
                driver->plistAddr = (char *)(driverAddr + sizeof(DriverInfo));
                driver->plistLength = module->plistLength;
                if (length != 0)
                {
                    driver->moduleAddr = (void *)(driverAddr + sizeof(DriverInfo) +
					                     module->plistLength);
                    driver->moduleLength = length;
                }
                else
                {
                    driver->moduleAddr   = 0;
                    driver->moduleLength = 0;
                }

                // Save the plist and module.
                strcpy(driver->plistAddr, module->plistAddr);
                if (length != 0)
                {
                    memcpy(driver->moduleAddr, (void *)kLoadAddr, driver->moduleLength);
                }

                // Add an entry to the memory map.
                sprintf(segName, "Driver-%lx", (unsigned long)driver);
                AllocateMemoryRange(segName, driverAddr, driverLength,
                                    kBootDriverTypeKEXT);
            }
        }
        module = module->nextModule;
    }

    return 0;
}

//==========================================================================
// MatchPersonalities

static long
MatchPersonalities( void )
{
#warning IONameMatch support not implemented
    return 0;
}

//==========================================================================
// MatchLibraries

static long
MatchLibraries( void )
{
    TagPtr     prop, prop2;
    ModulePtr  module, module2;
    long       done;

    do {
        done = 1;
        module = gModuleHead;
        
        while (module != 0)
        {
            if (module->willLoad == 1)
            {
                prop = GetProperty(module->dict, kPropOSBundleLibraries);
                if (prop != 0)
                {
                    prop = prop->tag;
                    while (prop != 0)
                    {
                        module2 = gModuleHead;
                        while (module2 != 0)
                        {
                            prop2 = GetProperty(module2->dict, kPropCFBundleIdentifier);
                            if ((prop2 != 0) && (!strcmp(prop->string, prop2->string)))
                            {
                                if (module2->willLoad == 0) module2->willLoad = 1;
                                break;
                            }
                            module2 = module2->nextModule;
                        }
                        prop = prop->tagNext;
                    }
                }
                module->willLoad = 2;
                done = 0;
            }
            module = module->nextModule;
        }
    }
    while (!done);

    return 0;
}

//==========================================================================
// GetProperty

static TagPtr
GetProperty( TagPtr dict, char * key )
{
    TagPtr tagList, tag;

    if (dict->type != kTagTypeDict) return 0;
    
    tag = 0;
    tagList = dict->tag;
    while (tagList)
    {
        tag = tagList;
        tagList = tag->tagNext;
        
        if ((tag->type != kTagTypeKey) || (tag->string == 0)) continue;
        
        if (!strcmp(tag->string, key)) return tag->tag;
    }
    
    return 0;
}

//==========================================================================
// FindModule

#if NOTDEF
static ModulePtr
FindModule( char * name )
{
    ModulePtr module;
    TagPtr    prop;
    
    module = gModuleHead;
    
    while (module != 0)
    {
        prop = GetProperty(module->dict, kPropCFBundleIdentifier);
        if ((prop != 0) && !strcmp(name, prop->string)) break;
        module = module->nextModule;
    }
    
    return module;
}
#endif /* NOTDEF */

//==========================================================================
// ParseXML

static long
ParseXML( char * buffer, ModulePtr * module, TagPtr * personalities )
{
	long       length, pos;
	TagPtr     moduleDict, required;
	ModulePtr  tmpModule;
  
    pos = 0;
  
    while (1)
    {
        length = ParseNextTag(buffer + pos, &moduleDict);
        if (length == -1) break;
    
        pos += length;
    
        if (moduleDict == 0) continue;
        if (moduleDict->type == kTagTypeDict) break;
    
        FreeTag(moduleDict);
    }
  
    if (length == -1) return -1;

    required = GetProperty(moduleDict, kPropOSBundleRequired);
    if ( (required == 0) ||
         (required->type != kTagTypeString) ||
         !strcmp(required->string, "Safe Boot"))
    {
        FreeTag(moduleDict);
        return -2;
    }

    tmpModule = AllocateBootXMemory(sizeof(Module));
    if (tmpModule == 0)
    {
        FreeTag(moduleDict);
        return -1;
    }
    tmpModule->dict = moduleDict;
  
    // For now, load any module that has OSBundleRequired != "Safe Boot".

    tmpModule->willLoad = 1;

    *module = tmpModule;
  
    // Get the personalities.

    *personalities = GetProperty(moduleDict, kPropIOKitPersonalities);
  
    return 0;
}

//==========================================================================
// ParseNextTag

static long
ParseNextTag( char * buffer, TagPtr * tag )
{
	long   length, pos;
	char * tagName;

    length = GetNextTag(buffer, &tagName, 0);
    if (length == -1) return -1;

	pos = length;
    if (!strncmp(tagName, kXMLTagPList, 6))
    {
        length = 0;
    }
    else if (!strcmp(tagName, kXMLTagDict))
    {
        length = ParseTagList(buffer + pos, tag, kTagTypeDict, 0);
    }
    else if (!strcmp(tagName, kXMLTagDict "/"))
    {
        length = ParseTagList(buffer + pos, tag, kTagTypeDict, 1);
    }
    else if (!strcmp(tagName, kXMLTagKey))
    {
        length = ParseTagKey(buffer + pos, tag);
    }
    else if (!strcmp(tagName, kXMLTagString))
    {
        length = ParseTagString(buffer + pos, tag);
    }
    else if (!strcmp(tagName, kXMLTagInteger))
    {
        length = ParseTagInteger(buffer + pos, tag);
    }
    else if (!strcmp(tagName, kXMLTagData))
    {
        length = ParseTagData(buffer + pos, tag);
    }
    else if (!strcmp(tagName, kXMLTagDate))
    {
        length = ParseTagDate(buffer + pos, tag);
    }
    else if (!strcmp(tagName, kXMLTagFalse))
    {
        length = ParseTagBoolean(buffer + pos, tag, kTagTypeFalse);
    }
    else if (!strcmp(tagName, kXMLTagTrue))
    {
        length = ParseTagBoolean(buffer + pos, tag, kTagTypeTrue);
    }
    else if (!strcmp(tagName, kXMLTagArray))
    {
        length = ParseTagList(buffer + pos, tag, kTagTypeArray, 0);
    }
    else if (!strcmp(tagName, kXMLTagArray "/"))
    {
        length = ParseTagList(buffer + pos, tag, kTagTypeArray, 1);
    }
    else
    {
        *tag = 0;
        length = 0;
    }
  
    if (length == -1) return -1;
  
    return pos + length;
}

//==========================================================================
// ParseTagList

static long
ParseTagList( char * buffer, TagPtr * tag, long type, long empty )
{
	long   length, pos;
	TagPtr tagList, tmpTag;
  
    tagList = 0;
    pos = 0;
  
    if (!empty)
    {
        while (1)
        {
            length = ParseNextTag(buffer + pos, &tmpTag);
            if (length == -1) break;

            pos += length;
      
            if (tmpTag == 0) break;
            tmpTag->tagNext = tagList;
            tagList = tmpTag;
        }
    
        if (length == -1)
        {
            FreeTag(tagList);
            return -1;
        }
    }
  
    tmpTag = NewTag();
    if (tmpTag == 0)
    {
        FreeTag(tagList);
        return -1;
    }

    tmpTag->type = type;
    tmpTag->string = 0;
    tmpTag->tag = tagList;
    tmpTag->tagNext = 0;
    
    *tag = tmpTag;
    
    return pos;
}

//==========================================================================
// ParseTagKey

static long
ParseTagKey( char * buffer, TagPtr * tag )
{
    long   length, length2;
    char   *string;
    TagPtr tmpTag, subTag;
  
    length = FixDataMatchingTag(buffer, kXMLTagKey);
    if (length == -1) return -1;
  
    length2 = ParseNextTag(buffer + length, &subTag);
    if (length2 == -1) return -1;
  
    tmpTag = NewTag();
    if (tmpTag == 0)
    {
        FreeTag(subTag);
        return -1;
    }
  
    string = NewSymbol(buffer);
    if (string == 0)
    {
        FreeTag(subTag);
        FreeTag(tmpTag);
        return -1;
    }
  
	tmpTag->type = kTagTypeKey;
    tmpTag->string = string;
    tmpTag->tag = subTag;
    tmpTag->tagNext = 0;
  
    *tag = tmpTag;
  
    return length + length2;
}

//==========================================================================
// ParseTagString

static long
ParseTagString( char * buffer, TagPtr * tag )
{
    long   length;
    char * string;
    TagPtr tmpTag;
  
	length = FixDataMatchingTag(buffer, kXMLTagString);
    if (length == -1) return -1;
  
    tmpTag = NewTag();
    if (tmpTag == 0) return -1;
  
    string = NewSymbol(buffer);
    if (string == 0)
    {
        FreeTag(tmpTag);
        return -1;
    }
  
    tmpTag->type = kTagTypeString;
    tmpTag->string = string;
    tmpTag->tag = 0;
    tmpTag->tagNext = 0;
  
    *tag = tmpTag;
  
    return length;
}

//==========================================================================
// ParseTagInteger

static long
ParseTagInteger( char * buffer, TagPtr * tag )
{
    long   length, integer;
    TagPtr tmpTag;
    
    length = FixDataMatchingTag(buffer, kXMLTagInteger);
    if (length == -1) return -1;
    
    tmpTag = NewTag();
    if (tmpTag == 0) return -1;
    
    integer = 0;
    
    tmpTag->type = kTagTypeInteger;
    tmpTag->string = (char *)integer;
    tmpTag->tag = 0;
    tmpTag->tagNext = 0;
    
    *tag = tmpTag;
    
    return length;
}

//==========================================================================
// ParseTagData

static long
ParseTagData( char * buffer, TagPtr * tag )
{
    long   length;
    TagPtr tmpTag;
    
    length = FixDataMatchingTag(buffer, kXMLTagData);
    if (length == -1) return -1;
    
    tmpTag = NewTag();
    if (tmpTag == 0) return -1;
    
    tmpTag->type = kTagTypeData;
    tmpTag->string = 0;
    tmpTag->tag = 0;
    tmpTag->tagNext = 0;
    
    *tag = tmpTag;
    
    return length;
}

//==========================================================================
// ParseTagDate

static long
ParseTagDate( char * buffer, TagPtr * tag )
{
    long   length;
    TagPtr tmpTag;
    
    length = FixDataMatchingTag(buffer, kXMLTagDate);
    if (length == -1) return -1;
    
    tmpTag = NewTag();
    if (tmpTag == 0) return -1;
    
    tmpTag->type = kTagTypeDate;
    tmpTag->string = 0;
    tmpTag->tag = 0;
    tmpTag->tagNext = 0;
    
    *tag = tmpTag;
    
    return length;
}

//==========================================================================
// ParseTagBoolean

static long
ParseTagBoolean( char * buffer, TagPtr * tag, long type )
{
    TagPtr tmpTag;
    
    tmpTag = NewTag();
    if (tmpTag == 0) return -1;
    
    tmpTag->type = type;
    tmpTag->string = 0;
    tmpTag->tag = 0;
    tmpTag->tagNext = 0;
    
    *tag = tmpTag;
    
    return 0;
}

//==========================================================================
// GetNextTag

static long
GetNextTag( char * buffer, char ** tag, long * start )
{
    long cnt, cnt2;

    if (tag == 0) return -1;
    
    // Find the start of the tag.
    cnt = 0;
    while ((buffer[cnt] != '\0') && (buffer[cnt] != '<')) cnt++;
    if (buffer[cnt] == '\0') return -1;
    
    // Find the end of the tag.
    cnt2 = cnt + 1;
    while ((buffer[cnt2] != '\0') && (buffer[cnt2] != '>')) cnt2++;
    if (buffer[cnt2] == '\0') return -1;

    // Fix the tag data.
    *tag = buffer + cnt + 1;
    buffer[cnt2] = '\0';
    if (start) *start = cnt;
    
    return cnt2 + 1;
}

//==========================================================================
// FixDataMatchingTag

static long
FixDataMatchingTag( char * buffer, char * tag )
{
    long   length, start, stop;
    char * endTag;
    
    start = 0;
    while (1)
    {
        length = GetNextTag(buffer + start, &endTag, &stop);
        if (length == -1) return -1;
        
        if ((*endTag == '/') && !strcmp(endTag + 1, tag)) break;
        start += length;
    }
    
    buffer[start + stop] = '\0';
    
    return start + length;
}

//==========================================================================
// NewTag

#define kTagsPerBlock (0x1000)

static TagPtr gTagsFree;

static TagPtr
NewTag( void )
{
	long   cnt;
	TagPtr tag;
  
    if (gTagsFree == 0)
    {
        tag = (TagPtr)AllocateBootXMemory(kTagsPerBlock * sizeof(Tag));
        if (tag == 0) return 0;
        
        // Initalize the new tags.
        for (cnt = 0; cnt < kTagsPerBlock; cnt++)
        {
            tag[cnt].type = kTagTypeNone;
            tag[cnt].string = 0;
            tag[cnt].tag = 0;
            tag[cnt].tagNext = tag + cnt + 1;
        }
        tag[kTagsPerBlock - 1].tagNext = 0;

        gTagsFree = tag;
    }

    tag = gTagsFree;
    gTagsFree = tag->tagNext;
    
    return tag;
}

//==========================================================================
// FreeTag

static void
FreeTag( TagPtr tag )
{
	return;
    if (tag == 0) return;
  
    if (tag->string) FreeSymbol(tag->string);
  
    FreeTag(tag->tag);
    FreeTag(tag->tagNext);
  
    // Clear and free the tag.
    tag->type = kTagTypeNone;
    tag->string = 0;
    tag->tag = 0;
    tag->tagNext = gTagsFree;
    gTagsFree = tag;
}

//==========================================================================
// Symbol object.

struct Symbol
{
  long          refCount;
  struct Symbol *next;
  char          string[1];
};
typedef struct Symbol Symbol, *SymbolPtr;

static SymbolPtr FindSymbol(char * string, SymbolPtr * prevSymbol);

static SymbolPtr gSymbolsHead;

//==========================================================================
// NewSymbol

static char *
NewSymbol( char * string )
{
	SymbolPtr symbol;
  
    // Look for string in the list of symbols.
    symbol = FindSymbol(string, 0);
  
    // Add the new symbol.
    if (symbol == 0)
    {
        symbol = AllocateBootXMemory(sizeof(Symbol) + strlen(string));
        if (symbol == 0) return 0;
    
        // Set the symbol's data.
        symbol->refCount = 0;
        strcpy(symbol->string, string);
    
        // Add the symbol to the list.
        symbol->next = gSymbolsHead;
        gSymbolsHead = symbol;
    }
  
    // Update the refCount and return the string.
    symbol->refCount++;
    return symbol->string;
}

//==========================================================================
// FreeSymbol

static void
FreeSymbol( char * string )
{ 
#if 0
    SymbolPtr symbol, prev;
    
    // Look for string in the list of symbols.
    symbol = FindSymbol(string, &prev);
    if (symbol == 0) return;
    
    // Update the refCount.
    symbol->refCount--;
    
    if (symbol->refCount != 0) return;
    
    // Remove the symbol from the list.
    if (prev != 0) prev->next = symbol->next;
    else gSymbolsHead = symbol->next;
    
    // Free the symbol's memory.
    free(symbol);
#endif
}

//==========================================================================
// FindSymbol

static SymbolPtr
FindSymbol( char * string, SymbolPtr * prevSymbol )
{
	SymbolPtr symbol, prev;
  
	symbol = gSymbolsHead;
	prev = 0;
  
	while (symbol != 0)
    {
        if (!strcmp(symbol->string, string)) break;
    
        prev = symbol;
        symbol = symbol->next;
    }
  
    if ((symbol != 0) && (prevSymbol != 0)) *prevSymbol = prev;
  
    return symbol;
}
