/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/config.cc
 * DESCRIPTION:     XT Boot Loader Configuration
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 *                  Aiken Harris <harraiken91@gmail.com>
 */

#include <xtldr.hh>


/**
 * Returns a boolean value of the specified configuration key.
 *
 * @param ConfigName
 *        Specifies the configuration key to return its boolean representation.
 *
 * @return This routine returns a boolean representation of the configuration value.
 *
 * @since XT 1.0
 */
XTCDECL
BOOLEAN
Configuration::GetBooleanValue(IN PCWSTR ConfigName)
{
    PWCHAR Value;

    /* Get config value */
    GetValue(ConfigName, &Value);

    /* Check if option is enabled */
    if(RTL::WideString::CompareWideStringInsensitive(Value, L"ENABLED", 0) == 0 ||
       RTL::WideString::CompareWideStringInsensitive(Value, L"ON", 0) == 0 ||
       RTL::WideString::CompareWideStringInsensitive(Value, L"TRUE", 0) == 0 ||
       RTL::WideString::CompareWideStringInsensitive(Value, L"YES", 0) == 0)
    {
        /* This option is enabled */
        return TRUE;
    }

    /* Return FALSE by default */
    return FALSE;
}

/**
 * @brief Retrieves the value of a specific OS boot option from a list.
 *
 * @param Options
 *        A pointer to the head of a list of XTBL_CONFIG_ENTRY structures.
 *
 * @param OptionName
 *        A pointer to wide string that contains the name of the boot option to retrieve.
 *
 * @param OptionValue
 *        A pointer to a variable that receives a pointer to the retrieved boot option's value.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::GetBootOptionValue(IN PLIST_ENTRY Options,
                                  IN PCWSTR OptionName,
                                  OUT PWCHAR *OptionValue)
{
    PXTBL_CONFIG_ENTRY ConfigEntry;
    PLIST_ENTRY ConfigList;
    ULONG KeyLength, ValueLength;
    EFI_STATUS Status;

    /* Assume the option will not be found */
    *OptionValue = NULLPTR;

    /* Get the length of the option name we are looking for */
    KeyLength = RTL::WideString::WideStringLength(OptionName, 0);

    /* Start iterating from the first entry in the options list */
    ConfigList = Options->Flink;
    while(ConfigList != Options)
    {
        /* Get the container record for the current config entry */
        ConfigEntry = CONTAIN_RECORD(ConfigList, XTBL_CONFIG_ENTRY, Flink);

        /* Compare the current entry's name with the requested option name */
        if(RTL::WideString::CompareWideStringInsensitive(ConfigEntry->Name, OptionName, KeyLength) == 0)
        {
            /* Found the option, now prepare to copy its value */
            ValueLength = RTL::WideString::WideStringLength(ConfigEntry->Value, 0);

            /* Allocate memory for the output value string */
            Status = Memory::AllocatePool((ValueLength + 1) * sizeof(WCHAR), (PVOID *)OptionValue);
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Memory allocation failure, print debug message and return status code */
                Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
                *OptionValue = NULLPTR;
                return Status;
            }

            /* Copy the value and NULL-terminate the new string */
            RTL::Memory::CopyMemory(*OptionValue, ConfigEntry->Value, ValueLength * sizeof(WCHAR));
            (*OptionValue)[ValueLength] = L'\0';

            /* Successfully retrieved the option value, return success */
            return STATUS_EFI_SUCCESS;
        }

        /* Move to the next entry in the list */
        ConfigList = ConfigList->Flink;
    }

    /* Option not found */
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Retrieves the list of user-editable boot options.
 *
 * @param OptionsArray
 *        A pointer to a variable that will receive the pointer to the array of editable option names.
 *
 * @param OptionsCount
 *        A pointer to a variable that will be updated with the number of elements in the OptionsArray.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
Configuration::GetEditableOptions(OUT PCWSTR **OptionsArray,
                                  OUT PULONG OptionsCount)
{
    ULONG Count = 0;

    /* Return a pointer to the global array of editable options */
    *OptionsArray = EditableConfigOptions;

    /* Calculate the number of elements in the array */
    while(EditableConfigOptions[Count])
    {
        Count++;
    }

    /* Return the number of elements */
    *OptionsCount = Count;
}

/**
 * Returns a value of the specified configuration key.
 *
 * @param ConfigName
 *        Specifies the configuration key to return its value.
 *
 * @return This routine returns a pointer to the configuration value, or NULLPTR if key was not found.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::GetValue(IN PCWSTR ConfigName,
                        OUT PWCHAR *ConfigValue)
{
    PXTBL_CONFIG_ENTRY ConfigEntry;
    PLIST_ENTRY ConfigListEntry;
    SIZE_T KeyLength, ValueLength;
    EFI_STATUS Status;
    PWCHAR Value;

    /* Assume the option will not be found */
    *ConfigValue = NULLPTR;

    /* Get config entry name length */
    KeyLength = RTL::WideString::WideStringLength(ConfigName, 0);

    /* Iterate through config entries */
    ConfigListEntry = Config.Flink;
    while(ConfigListEntry != &Config)
    {
        /* Get config entry */
        ConfigEntry = CONTAIN_RECORD(ConfigListEntry, XTBL_CONFIG_ENTRY, Flink);

        /* Check if requested configuration found */
        if(RTL::WideString::CompareWideStringInsensitive(ConfigEntry->Name, ConfigName, KeyLength) == 0)
        {
            /* Get value length */
            ValueLength = RTL::WideString::WideStringLength(ConfigEntry->Value, 0);

            /* Allocate memory for value */
            Status = Memory::AllocatePool((ValueLength + 1) * sizeof(WCHAR), (PVOID *)&Value);
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Memory allocation failure, return NULLPTR */
                Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
                return Status;
            }

            /* Copy value and return it */
            RTL::Memory::CopyMemory(Value, ConfigEntry->Value, ValueLength * sizeof(WCHAR));
            Value[ValueLength] = L'\0';
            *ConfigValue = Value;
            return STATUS_EFI_SUCCESS;
        }

        /* Move to the next config entry */
        ConfigListEntry = ConfigListEntry->Flink;
    }

    /* Config entry not found, return NULLPTR */
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Initializes a list of operating systems for XTLDR boot menu.
 *
 * @param MenuEntries
 *        Supplies a pointer to memory area where operating systems list will be stored.
 *
 * @param EntriesCount
 *        Supplies a pointer to memory area where number of menu entries will be stored.
 *
 * @param DefaultId
 *        Supplies a pointer to memory area where ID of default menu entry will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::InitializeBootMenuList(IN ULONG MaxNameLength,
                                      OUT PXTBL_BOOTMENU_ITEM *MenuEntries,
                                      OUT PULONG EntriesCount,
                                      OUT PULONG DefaultId)
{
    EFI_GUID VendorGuid = XT_BOOT_LOADER_PROTOCOL_GUID;
    PWCHAR DefaultMenuEntry, LastBooted, MenuEntryName, VisibleName;
    PLIST_ENTRY MenuEntrySectionList, MenuEntryList;
    PXTBL_CONFIG_SECTION MenuEntrySection;
    PXTBL_CONFIG_ENTRY MenuEntryOption;
    ULONG DefaultOS, NameLength,NumberOfEntries;
    PXTBL_BOOTMENU_ITEM OsList;
    EFI_STATUS Status;

    /* Set default values */
    DefaultOS = 0;
    NumberOfEntries = 0;

    /* Get default menu entry from configuration */
    Configuration::GetValue(L"DEFAULT", &DefaultMenuEntry);

    /* Check if configuration allows to use last booted OS */
    if(Configuration::GetBooleanValue(L"KEEPLASTBOOT"))
    {
        /* Attempt to get last booted Operating System from NVRAM */
        Status = EfiUtils::GetEfiVariable(&VendorGuid, L"XtLdrLastBootOS", (PVOID*)&LastBooted);
        if(Status == STATUS_EFI_SUCCESS)
        {
            /* Set default menu entry to last booted OS */
            DefaultMenuEntry = LastBooted;
        }
    }

    /* Iterate through menu items to get a total number of entries */
    MenuEntrySectionList = BootMenuList->Flink;
    while(MenuEntrySectionList != BootMenuList)
    {
        /* Increase number of menu entries, and simply get next item */
        NumberOfEntries++;
        MenuEntrySectionList = MenuEntrySectionList->Flink;
    }

    /* Allocate memory for the OS list depending on the item count */
    Status = Memory::AllocatePool(NumberOfEntries * sizeof(XTBL_BOOTMENU_ITEM), (PVOID*)&OsList);
    if(Status != STATUS_EFI_SUCCESS || !OsList)
    {
        /* Memory allocation failure */
        return STATUS_EFI_OUT_OF_RESOURCES;
    }

    /* Reset counter and iterate through all menu items once again */
    NumberOfEntries = 0;
    MenuEntrySectionList = BootMenuList->Flink;
    while(MenuEntrySectionList != BootMenuList)
    {
        /* NULLify menu entry name */
        MenuEntryName = NULLPTR;

        /* Get menu section */
        MenuEntrySection = CONTAIN_RECORD(MenuEntrySectionList, XTBL_CONFIG_SECTION, Flink);

        /* Check if this is the default menu entry */
        if((RTL::WideString::WideStringLength(MenuEntrySection->SectionName, 0) == RTL::WideString::WideStringLength(DefaultMenuEntry, 0)) &&
           (RTL::WideString::CompareWideStringInsensitive(MenuEntrySection->SectionName, DefaultMenuEntry, 0) == 0))
        {
            /* Set default OS ID */
            DefaultOS = NumberOfEntries;
        }

        /* Iterate through all entry parameters */
        MenuEntryList = MenuEntrySection->Options.Flink;
        while(MenuEntryList != &MenuEntrySection->Options)
        {
            /* Get menu entry parameter */
            MenuEntryOption = CONTAIN_RECORD(MenuEntryList, XTBL_CONFIG_ENTRY, Flink);

            /* Check if this is the menu entry display name */
            if(RTL::WideString::CompareWideStringInsensitive(MenuEntryOption->Name, L"SYSTEMNAME", 0) == 0)
            {
                /* Set menu entry display name */
                MenuEntryName = MenuEntryOption->Value;
            }

            /* Get next parameter for this menu entry */
            MenuEntryList = MenuEntryList->Flink;
        }

        /* Add OS to the boot menu list */
        OsList[NumberOfEntries].FullName = MenuEntryName;
        OsList[NumberOfEntries].ShortName = MenuEntrySection->SectionName;
        OsList[NumberOfEntries].Options = &MenuEntrySection->Options;

        /* Check if the menu entry name fits the maximum length */
        NameLength = RTL::WideString::WideStringLength(MenuEntryName, 0);
        if(NameLength > MaxNameLength)
        {
            /* Menu entry name is too long, allocate memory for shorter name visible in the boot menu */
            Status = Memory::AllocatePool((MaxNameLength + 1) * sizeof(WCHAR), (PVOID*)&VisibleName);
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Memory allocation failure */
                return STATUS_EFI_OUT_OF_RESOURCES;
            }

            /* Copy shorter name and append "..." at the end */
            RTL::Memory::CopyMemory(VisibleName, MenuEntryName, (MaxNameLength - 3) * sizeof(WCHAR));
            RTL::Memory::CopyMemory(VisibleName + MaxNameLength - 3, L"...", 3 * sizeof(WCHAR));
            VisibleName[MaxNameLength] = L'\0';

            /* Set visible menu entry name */
            OsList[NumberOfEntries].EntryName = VisibleName;
        }
        else
        {
            /* Menu entry name fits the maximum length, use it as is */
            OsList[NumberOfEntries].EntryName = MenuEntryName;
        }

        /* Get next menu entry */
        MenuEntrySectionList = MenuEntrySectionList->Flink;
        NumberOfEntries++;
    }

    /* Set return values */
    *DefaultId = DefaultOS;
    *EntriesCount = NumberOfEntries;
    *MenuEntries = OsList;

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Initializes the XTLDR configuration subsystem.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
Configuration::InitializeConfiguration()
{
    /* Initialize XTLDR configuration linked lists */
    RTL::LinkedList::InitializeListHead(&Config);
}

/**
 * Loads and parses XTLDR configuration file.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::LoadConfiguration()
{
    PLIST_ENTRY SectionListEntry;
    EFI_STATUS Status;
    PCHAR ConfigData;

    /* Initialize configuration pointer */
    RTL::LinkedList::InitializeListHead(&ConfigSections);

    /* Read data from configuration file */
    Status = ReadConfigFile(XTBL_LOADER_DIRECTORY_PATH, L"XTLDR.INI", &ConfigData);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to read config file, try with architecture specific directory */
        Status = ReadConfigFile(XTBL_ARCH_LOADER_DIRECTORY_PATH, L"XTLDR.INI", &ConfigData);
    }

    /* Check if configuration was read successfully */
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to load configuration */
        Debug::Print(L"Failed to load FS0:/EFI/BOOT/XTLDR/XTLDR.INI configuration file (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Parse configuration data */
    Status = ParseConfigFile(ConfigData, &ConfigSections);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to parse configuration */
        Debug::Print(L"Failed to parse FS0:/EFI/BOOT/XTLDR/XTLDR.INI configuration file (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Iterate through config sections */
    SectionListEntry = ConfigSections.Flink;
    while(SectionListEntry != &ConfigSections)
    {
        /* Get config section */
        PXTBL_CONFIG_SECTION Section = CONTAIN_RECORD(SectionListEntry, XTBL_CONFIG_SECTION, Flink);

        /* Look for global XTLDR configuration section */
        if(RTL::WideString::CompareWideStringInsensitive(Section->SectionName, L"XTLDR", 5) == 0)
        {
            /* Update global configuration */
            UpdateConfiguration(&Section->Options);

            /* Remove XTLDR section from the list */
            RTL::LinkedList::RemoveEntryList(SectionListEntry);
            break;
        }

        /* Move to the next section */
        SectionListEntry = SectionListEntry->Flink;
    }

    /* Update boot menu OS list */
    BootMenuList = &ConfigSections;

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Parses command line arguments and updates global configuration.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::ParseCommandLine(VOID)
{
    EFI_GUID LIPGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
    PEFI_LOADED_IMAGE_PROTOCOL LoadedImage;
    PWCHAR Argument, Key, LastArg, Value;
    PXTBL_CONFIG_ENTRY Option;
    EFI_STATUS Status;
    SIZE_T KeyLength, ValueLength;
    LIST_ENTRY Config;

    /* Initialize configuration list */
    RTL::LinkedList::InitializeListHead(&Config);

    /* Handle loaded image protocol */
    Status = XtLoader::GetEfiSystemTable()->BootServices->HandleProtocol(XtLoader::GetEfiImageHandle(),
                                                                         &LIPGuid, (PVOID *)&LoadedImage);
    if(Status == STATUS_EFI_SUCCESS)
    {
        /* Check if launched from UEFI shell */
        if(LoadedImage && LoadedImage->LoadOptions)
        {
            /* Tokenize provided options */
            Argument = RTL::WideString::TokenizeWideString((PWCHAR)LoadedImage->LoadOptions, L" ", &LastArg);

            /* Iterate over all arguments passed to boot loader */
            while(Argument != NULLPTR)
            {
                /* Store key name */
                Key = Argument;

                /* Find end of the key */
                while(*Argument != L'=' && *Argument != L'\0' && *Argument != L'\n')
                {
                    /* Advance to the next character */
                    Argument++;
                }

                /* Mark end of the key and advance to the next character */
                *Argument = L'\0';
                Argument++;

                /* Store value */
                Value = Argument;

                /* Find end of the value */
                while(*Argument != L'\0' && *Argument != L'\n')
                {
                    /* Advance to the next character */
                    Argument++;
                }

                /* Mark end of the value and advance to the next character */
                *Argument = L'\0';
                Argument++;

                /* Get length of the key and its value */
                KeyLength = RTL::WideString::WideStringLength(Key, 0);
                ValueLength = RTL::WideString::WideStringLength(Value, 0);

                /* Check if argument is valid */
                if(KeyLength == 0 || ValueLength == 0)
                {
                    /* Invalid argument, skip to the next one */
                    continue;
                }

                /* Allocate memory for new option */
                Status = Memory::AllocatePool(sizeof(XTBL_CONFIG_ENTRY), (PVOID*)&Option);
                if(Status == STATUS_EFI_SUCCESS)
                {
                    /* Allocate more memory for option name */
                    Status = Memory::AllocatePool(sizeof(WCHAR) * (KeyLength + 1), (PVOID*)&Option->Name);
                    if(Status == STATUS_EFI_SUCCESS)
                    {
                        /* Allocate even more memory for option value */
                        Status = Memory::AllocatePool(sizeof(WCHAR) * (ValueLength + 1), (PVOID*)&Option->Value);
                    }
                }
                if(Status != STATUS_EFI_SUCCESS)
                {
                    /* Some memory allocation failed */
                    return Status;
                }

                /* Set entry name and value */
                RTL::Memory::CopyMemory(Option->Name, Key, (KeyLength * sizeof(WCHAR)));
                RTL::Memory::CopyMemory(Option->Value, Value, (ValueLength * sizeof(WCHAR)));
                Option->Name[KeyLength] = L'\0';
                Option->Value[ValueLength] = L'\0';

                /* Add entry to the list */
                RTL::LinkedList::InsertTailList(&Config, &Option->Flink);

                /* Take next argument */
                Argument = RTL::WideString::TokenizeWideString(NULLPTR, L" ", &LastArg);
            }

            /* Update global configuration */
            UpdateConfiguration(&Config);
        }
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Parses configuration INI file.
 *
 * @param RawConfig
 *        Suplies a pointer to configuration INI file to be parsed.
 *
 * @param Configuration
 *        Supplies a pointer to memory region where parsed configuration will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::ParseConfigFile(IN CONST PCHAR RawConfig,
                               OUT PLIST_ENTRY Configuration)
{
    SIZE_T SectionLength, KeyLength, ValueLength;
    PCHAR InputData, Key, SectionName, Value;
    PXTBL_CONFIG_SECTION Section;
    PXTBL_CONFIG_ENTRY Option;
    EFI_STATUS Status;

    /* Initialize pointers */
    InputData = RawConfig;
    Section = NULLPTR;
    Option = NULLPTR;
    SectionName = NULLPTR;
    Key = NULLPTR;
    Value = NULLPTR;

    /* Analyze configuration data until end of file is reached */
    while(*InputData != '\0')
    {
        if(*InputData == ';' || *InputData == '#')
        {
            /* Skip comment until end of the line */
            while(*InputData != '\0' && *InputData != '\n')
            {
                /* Advance to the next character */
                InputData++;
            }
        }
        else if(*InputData == ' ' || *InputData == '\t' || *InputData == '\r' || *InputData == '\n')
        {
            /* Skip whitespaces */
            InputData++;
        }
        else if(*InputData == '[')
        {
            /* Skip leading bracket */
            InputData++;

            /* Store section name */
            SectionName = InputData;

            /* Find end of the section name */
            while(*InputData != ']' && *InputData != '\0' && *InputData != '\r' && *InputData != '\n')
            {
                /* Advance to the next character */
                InputData++;
            }

            /* Check if end of the section name is reached */
            if(*InputData != ']')
            {
                /* Section name does not end */
                return STATUS_EFI_INVALID_PARAMETER;
            }

            /* Mark end of the section name and advance to the next character */
            *InputData = '\0';
            InputData++;

            /* Remove leading and trailing spaces from section name */
            SectionName = RTL::String::TrimString(SectionName);

            /* Find length of the section name */
            SectionLength = RTL::String::StringLength(SectionName, 0);

            /* Allocate memory for new section */
            Status = Memory::AllocatePool(sizeof(XTBL_CONFIG_SECTION), (PVOID*)&Section);
            if(Status == STATUS_EFI_SUCCESS)
            {
                /* Allocate more memory for section name */
                Status = Memory::AllocatePool(sizeof(WCHAR) * (SectionLength + 1), (PVOID*)&Section->SectionName);
            }
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Some memory allocation failed */
                return Status;
            }

            /* Initialize new section and convert its name to wide string */
            RTL::LinkedList::InitializeListHead(&Section->Options);
            RTL::String::StringToWideString(Section->SectionName, (PCSTR*)&SectionName, SectionLength);

            /* Ensure string is NULL-terminated and add new section to the configuration list */
            Section->SectionName[SectionLength] = L'\0';
            RTL::LinkedList::InsertTailList(Configuration, &Section->Flink);
        }
        else
        {
            /* Store key */
            Key = InputData;

            /* Find end of the key */
            while(*InputData != '=' && *InputData != '\0' && *InputData != '\r' && *InputData != '\n')
            {
                /* Advance to the next character */
                InputData++;
            }

            /* Check if end of the key is reached */
            if(*InputData != '=')
            {
                /* Key name does not end */
                return STATUS_EFI_INVALID_PARAMETER;
            }

            /* Mark end of the key and advance to the next character */
            *InputData = 0;
            InputData++;

            /* Skip all leading spaces in the value */
            while(*InputData == ' ')
            {
                /* Advance to the next character */
                InputData++;
            }

            /* Store value */
            Value = InputData;

            /* Find end of the value */
            while(*InputData != '\0' && *InputData != '\r' && *InputData != '\n')
            {
                /* Advance to the next character */
                InputData++;
            }

            /* Mark end of the value and advance to the next character */
            *InputData = 0;
            InputData++;

            /* Remove leading and trailing spaces from key and value */
            Key = RTL::String::TrimString(Key);
            Value = RTL::String::TrimString(Value);

            /* Find length of the key and its value */
            KeyLength = RTL::String::StringLength(Key, 0);
            ValueLength = RTL::String::StringLength(Value, 0);

            /* Allocate memory for new option */
            Status = Memory::AllocatePool(sizeof(XTBL_CONFIG_ENTRY), (PVOID*)&Option);
            if(Status == STATUS_EFI_SUCCESS)
            {
                /* Allocate more memory for option name */
                Status = Memory::AllocatePool(sizeof(WCHAR) * (KeyLength + 1), (PVOID*)&Option->Name);
                if(Status == STATUS_EFI_SUCCESS)
                {
                    /* Allocate even more memory for option value */
                    Status = Memory::AllocatePool(sizeof(WCHAR) * (ValueLength + 1), (PVOID*)&Option->Value);
                }
            }
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Some memory allocation failed */
                return Status;
            }

            /* Remove leading quotes from the value */
            if(*Value == '"' || *Value == '\'')
            {
                Value++;
            }

            /* Remove trailing quotes from the value */
            if(Value[ValueLength - 2] == '"' || Value[ValueLength - 2] == '\'')
            {
                Value[ValueLength - 2] = '\0';
            }

            /* Convert key and value to wide strings */
            RTL::String::StringToWideString(Option->Name, (PCSTR*)&Key, RTL::String::StringLength(Key, 0) + 1);
            RTL::String::StringToWideString(Option->Value, (PCSTR*)&Value, RTL::String::StringLength(Value, 0) + 1);

            /* Ensure strings are NULL-terminated and add new option to the list */
            Option->Name[KeyLength] = L'\0';
            Option->Value[ValueLength] = L'\0';
            RTL::LinkedList::InsertTailList(&Section->Options, &Option->Flink);
        }
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Loads configuration file from the specified directory on the FS0:/ drive.
 *
 * @param ConfigDirectory
 *        Specifies a path to the directory containing the configuration file.
 *
 * @param ConfigFile
 *        Specifies the name of the configuration file.
 *
 * @param ConfigData
 *        Provides a buffer to store the data read from the configuration file.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::ReadConfigFile(IN PCWSTR ConfigDirectory,
                              IN PCWSTR ConfigFile,
                              OUT PCHAR *ConfigData)
{
    PEFI_FILE_HANDLE DirHandle, FsHandle;
    EFI_HANDLE DiskHandle;
    EFI_STATUS Status;
    SIZE_T FileSize;

    /* Open EFI volume */
    Status = Volume::OpenVolume(NULLPTR, &DiskHandle, &FsHandle);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open a volume */
        return Status;
    }

    /* Open specified directory, containing the configuration file and close the FS immediately */
    Status = FsHandle->Open(FsHandle, &DirHandle, (PWCHAR)ConfigDirectory, EFI_FILE_MODE_READ, 0);
    FsHandle->Close(FsHandle);

    /* Check if directory opened successfully */
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open directory */
        Volume::CloseVolume(&DiskHandle);
        return Status;
    }

    /* Read configuration file and close directory */
    Status = Volume::ReadFile(DirHandle, ConfigFile, (PVOID *)ConfigData, &FileSize);
    DirHandle->Close(DirHandle);

    /* Close EFI volume */
    Volume::CloseVolume(&DiskHandle);

    /* Return read status */
    return Status;
}


/**
 * Sets the value of a specific OS boot option in a list, or adds it if it doesn't exist.
 *
 * @param Options
 *       A pointer to the head of a list of XTBL_CONFIG_ENTRY structures.
 *
 * @param OptionName
 *       A pointer to a wide string that contains the name of the boot option to set.
 *
 * @param OptionValue
 *       A pointer to a wide string that contains the new value for the boot option.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::SetBootOptionValue(IN PLIST_ENTRY Options,
                                  IN PCWSTR OptionName,
                                  IN PCWSTR OptionValue)
{
    PXTBL_CONFIG_ENTRY ConfigEntry;
    PLIST_ENTRY ConfigList;
    ULONG Length;
    EFI_STATUS Status;

    /* Get the length of the option name we are looking for */
    Length = RTL::WideString::WideStringLength(OptionName, 0);

    /* Start iterating from the first entry in the options list */
    ConfigList = Options->Flink;
    while(ConfigList != Options)
    {
        /* Get the container record for the current config entry */
        ConfigEntry = CONTAIN_RECORD(ConfigList, XTBL_CONFIG_ENTRY, Flink);

        /* Compare the current entry's name with the requested option name */
        if(RTL::WideString::CompareWideStringInsensitive(ConfigEntry->Name, OptionName, Length) == 0)
        {
            /* Found the option, get its length */
            Length = RTL::WideString::WideStringLength(OptionValue, 0);

            /* Reallocate memory for the new value */
            Status = Memory::FreePool(ConfigEntry->Value);
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Failed to free memory, return status code */
                return Status;
            }

            /* Allocate new memory for the updated value */
            Status = Memory::AllocatePool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Value);
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Memory allocation failure, print debug message and return status code */
                Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status);
                return Status;
            }

            /* Copy the value and NULL-terminate the new string */
            RTL::Memory::CopyMemory(ConfigEntry->Value, OptionValue, Length * sizeof(WCHAR));
            ConfigEntry->Value[Length] = L'\0';
            return STATUS_EFI_SUCCESS;
        }

        /* Move to the next entry in the list */
        ConfigList = ConfigList->Flink;
    }

    /* Option not found, allocate memory for the new one */
    Status = Memory::AllocatePool(sizeof(XTBL_CONFIG_ENTRY), (PVOID *)&ConfigEntry);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, print debug message and return status code */
        Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status);
        return Status;
    }

    /* Allocate memory for the option name */
    Length = RTL::WideString::WideStringLength(OptionName, 0);
    Status = Memory::AllocatePool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Name);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, print debug message and return status code */
        Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status);
        Memory::FreePool(ConfigEntry);
        return Status;
    }

    /* Copy the option name and NULL-terminate the new string */
    RTL::Memory::CopyMemory(ConfigEntry->Name, OptionName, Length * sizeof(WCHAR));
    ConfigEntry->Name[Length] = L'\0';

    /* Allocate memory for the option value */
    Length = RTL::WideString::WideStringLength(OptionValue, 0);
    Status = Memory::AllocatePool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Value);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure, print debug message and return status code */
        Debug::Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\\n", Status);
        Memory::FreePool(ConfigEntry->Name);
        Memory::FreePool(ConfigEntry);
        return Status;
    }

    /* Copy the value and NULL-terminate the new string */
    RTL::Memory::CopyMemory(ConfigEntry->Value, OptionValue, Length * sizeof(WCHAR));
    ConfigEntry->Value[Length] = L'\0';

    /* Insert the new config entry at the end of the options list */
    RTL::LinkedList::InsertTailList(Options, &ConfigEntry->Flink);

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Updates existing configuration value.
 *
 * @param ConfigName
 *        Specifies the configuration key to update.
 *
 * @param ConfigValue
 *        Specifies the new configuration value.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
Configuration::SetValue(IN PCWSTR ConfigName,
                        IN PCWSTR ConfigValue)
{
    PXTBL_CONFIG_ENTRY ConfigEntry;
    PLIST_ENTRY ConfigListEntry;
    EFI_STATUS Status;
    SIZE_T Length;

    /* Get config entry name length */
    Length = RTL::WideString::WideStringLength(ConfigName, 0);

    /* Iterate through config entries */
    ConfigListEntry = Config.Flink;
    while(ConfigListEntry != &Config)
    {
        /* Get config entry */
        ConfigEntry = CONTAIN_RECORD(ConfigListEntry, XTBL_CONFIG_ENTRY, Flink);

        /* Check if requested configuration found */
        if(RTL::WideString::CompareWideStringInsensitive(ConfigEntry->Name, ConfigName, Length) == 0)
        {
            /* Check new config value length */
            Length = RTL::WideString::WideStringLength(ConfigValue, 0);

            /* Reallocate memory for new config value */
            Status = Memory::FreePool(ConfigEntry->Value);
            if(Status == STATUS_EFI_SUCCESS)
            {
                /* Successfully freed memory, allocate a new pool */
                Status = Memory::AllocatePool((Length + 1) * sizeof(WCHAR), (PVOID *)&ConfigEntry->Value);
            }

            /* Check memory reallocation status */
            if(Status != STATUS_EFI_SUCCESS)
            {
                /* Failed to reallocate memory */
                return Status;
            }

            /* Update config value */
            RTL::Memory::CopyMemory(ConfigEntry->Value, ConfigValue, Length * sizeof(WCHAR));
            ConfigEntry->Value[Length] = L'\0';

            /* Return success */
            return STATUS_EFI_SUCCESS;
        }

        /* Move to the next config entry */
        ConfigListEntry = ConfigListEntry->Flink;
    }

    /* Config entry not found */
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Adds new XTLDR configuration entries to the global configuration list. Existing entries are not overwritten.
 *
 * @param NewConfig
 *        Supplies a pointer to a linked list containing new configuration entries.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
Configuration::UpdateConfiguration(IN PLIST_ENTRY NewConfig)
{
    PXTBL_CONFIG_ENTRY ConfigEntry;
    PWCHAR ConfigValue;
    PLIST_ENTRY ConfigListEntry, NextListEntry;

    /* Iterate through new config entries */
    ConfigListEntry = NewConfig->Flink;
    while(ConfigListEntry != NewConfig)
    {
        /* Get new config entry */
        ConfigEntry = CONTAIN_RECORD(ConfigListEntry, XTBL_CONFIG_ENTRY, Flink);

        /* Get next config entry */
        NextListEntry = ConfigListEntry->Flink;

        /* Make sure config entry does not exist yet */
        GetValue(ConfigEntry->Name, &ConfigValue);
        if(ConfigValue == NULLPTR)
        {
            /* Remove new config entry from input list and put it into global config list */
            RTL::LinkedList::RemoveEntryList(&ConfigEntry->Flink);
            RTL::LinkedList::InsertTailList(&Config, &ConfigEntry->Flink);
        }

        /* Move to the next new config entry */
        ConfigListEntry = NextListEntry;
    }
}
