/*++

Copyright (c) 1995-2001 Microsoft Corporation

Module Name:

    bootient.c

Abstract:

    Contains the Boot.ini OS boot entry and boot options
    abstraction implementation.

Author:


Revision History:

    None.

--*/

#include <bootient.h>

//
// defines
//
#define BOIOS_SECTION_NAME_START    TEXT('[')
#define BOIOS_SECTION_NAME_END      TEXT(']')
#define BOIOS_SECTION_NAME_START_STR    TEXT("[")
#define BOIOS_SECTION_NAME_END_STR      TEXT("]")


#define BOIOS_BOOTLOADER_SECTION    TEXT("boot loader")
#define BOIOS_OS_SECTION            TEXT("operating systems")
#define BOIOS_TIMEOUT_KEY           TEXT("timeout=")
#define BOIOS_DEFAULT_KEY           TEXT("default=")

#define MAX_BOOT_INI_SIZE           (4 * 1024)

static
PTSTR
BOIOSFixupString(
    IN PTSTR String,
    IN PTSTR SpecialChars
    )
{    
    PTSTR   ResultStr = String;

    //
    // Verify arguments
    //
    if (ResultStr && SpecialChars) {        
        ULONG   Index;
        BOOLEAN DoneWithStart = FALSE;
        TCHAR   Buffer[MAX_PATH * 4] = {0};
        TCHAR   NextIndex = 0;        

        //
        // skip unwanted characters
        //
        for (Index = 0; String[Index]; Index++) {
            if (!_tcschr(SpecialChars, String[Index])) {
                Buffer[NextIndex++] = String[Index];
            }
        }

        //
        // Null terminate the string
        //
        Buffer[NextIndex] = 0;

        if (!NextIndex) {
            ResultStr = NULL;
        } else {
            //
            // Copy back the new string to the 
            // input / output buffer
            //
            _tcscpy(ResultStr, Buffer);
        }
    }

    return ResultStr;
}

//
// BOI_OS_SECTION Methods
//
PBOI_SECTION
BOISectionCreate(
    IN PCTSTR   SectionData
    )
{
    PBOI_SECTION    This = NULL;

    if (SectionData) {
        PTSTR Buffer = (PTSTR)SBE_MALLOC((_tcslen(SectionData) + 1) * sizeof(TCHAR));

        if (Buffer && _tcscpy(Buffer, SectionData)) {
            PTSTR   SectionNameStart = _tcschr(Buffer, BOIOS_SECTION_NAME_START);
            PTSTR   SectionNameEnd = _tcschr(Buffer, BOIOS_SECTION_NAME_END);
            BOOLEAN Result = FALSE;

            if (SectionNameStart && SectionNameEnd && (SectionNameEnd > SectionNameStart)) {
                This = (PBOI_SECTION)SBE_MALLOC(sizeof(BOI_SECTION));

                if (*Buffer && This) {
                    DWORD   DataLength = (_tcslen(Buffer) + 1) * sizeof(TCHAR);

                    DataLength -= (((SectionNameEnd + 1) - Buffer) * sizeof(TCHAR));

                    //
                    // Init default object state
                    //
                    memset(This, 0, sizeof(BOI_SECTION));

                    //
                    // Get the name
                    //
                    _tcsncpy(This->Name, SectionNameStart + 1, 
                        SectionNameEnd - SectionNameStart - 1);


                    //
                    // Replicate the contents and keep it
                    //
                    This->Contents =  (PTSTR)SBE_MALLOC(DataLength);

                    if (This->Contents) {
                        _tcscpy(This->Contents, SectionNameEnd + 1);
                        Result = TRUE;
                    } else {
                        Result = FALSE;
                    }                    
                }

                if (!Result) {
                    BOISectionDelete(This);
                    This = NULL;
                }
            }

            SBE_FREE(Buffer);
        }            
    }

    return This;
}

VOID
BOISectionDelete(
    IN PBOI_SECTION This
    )
{
    if (This) {
        if (This->Contents) {
            SBE_FREE(This->Contents);
        }

        SBE_FREE(This);
    }
}

static
BOOLEAN
BOISectionWrite(
    IN PBOI_SECTION This,
    IN OUT PTSTR Buffer
    )
{
    BOOLEAN Result = FALSE;
    
    if (This && Buffer) {
        _tcscat(Buffer, BOIOS_SECTION_NAME_START_STR);
        _tcscat(Buffer, BOISectionGetName(This));
        _tcscat(Buffer, BOIOS_SECTION_NAME_END_STR);
        _tcscat(Buffer, TEXT("\r\n"));

        if (This->Contents) {
            _tcscat(Buffer, This->Contents);
        }            
    }

    return Result;
}


//
// BOI_OS_BOOT_ENTRY Methods
//

static
VOID
BOIOSBEInit(
    IN PBOI_OS_BOOT_ENTRY This
    )
{
    This->OsBootEntry.Delete = BOIOSBEDelete;
    This->OsBootEntry.Flush = BOIOSBEFlush;
}

PBOI_SECTION
BOIOSBOFindSection(
    IN PBOI_OS_BOOT_OPTIONS This,
    IN PTSTR SectionName
    )
{
    PBOI_SECTION Entry = NULL;

    for (Entry = This->Sections; Entry; Entry = Entry->Next) {
        if (!_tcsicmp(Entry->Name, SectionName)) {
            break;  // found the required section
        }
    }

    return Entry;
}

static
POS_BOOT_ENTRY
BOIOSBECreate(
    IN ULONG  Id,
    IN PCTSTR BootEntryLine,
    IN PBOI_OS_BOOT_OPTIONS Container
    )
{    
    POS_BOOT_ENTRY  Entry = NULL;

    if (BootEntryLine && Container) {
        BOOLEAN Result = FALSE;
        TCHAR   Buffer[MAX_PATH * 4];
        TCHAR   Token[MAX_PATH];
        PBOI_OS_BOOT_ENTRY  BootEntry = (PBOI_OS_BOOT_ENTRY)SBE_MALLOC(sizeof(BOI_OS_BOOT_ENTRY));
        POS_BOOT_ENTRY BaseBootEntry = (POS_BOOT_ENTRY)BootEntry;
                
        //
        // Replicate the input string
        //
        _tcsncpy(Buffer, BootEntryLine, sizeof(Buffer)/sizeof(TCHAR));

        //
        // Remove unwanted charcters in the string
        //
        if (BootEntry && BOIOSFixupString(Buffer, TEXT("\n\r"))) {
            PTSTR   EqualSign = _tcschr(Buffer, TEXT('='));

            //
            // Initialize object state
            //
            memset(BootEntry, 0, sizeof(BOI_OS_BOOT_ENTRY));
            BOIOSBEInit(BootEntry);            
            BaseBootEntry->Id = Id;
            BaseBootEntry->BootOptions = (POS_BOOT_OPTIONS)Container;

            if (EqualSign) {
                PTSTR Slash;
                
                *EqualSign = 0;                
                Slash = _tcschr(Buffer, TEXT('\\'));

                if (Slash) {
                    PTSTR   NameStart = NULL, NameEnd = NULL;
                    PTSTR   NextToken = NULL;

                    Result = TRUE;
                    *Slash = 0;

                    //
                    // Parse & set the boot device name
                    //
                    _tcscpy(Token, Buffer);
                    BOIOSFixupString(Token, TEXT("\n\r "));
                    _tcslwr(Token);
                    OSBESetBootVolumeName(BaseBootEntry, Token);

                    //
                    // if it starts with "C:" its either old OS,
                    // or CmdCons or WinPE or Setup entry
                    //
                    if (_tcschr(Token, TEXT(':'))) {
                        OSBE_SET_OLDOS(BaseBootEntry);
                    }

                    //
                    // Parse & set the boot path
                    //
                    _tcscpy(Token, Slash + 1);
                    BOIOSFixupString(Token, TEXT("\n\r "));
                    OSBESetBootPath(BaseBootEntry, Token);


                    //
                    // Parse & set the friendly name
                    //                    
                    NameStart = _tcschr(EqualSign + 1, TEXT('\"'));

                    //
                    // Set friendly name
                    //
                    if (NameStart) {                        
                        NameEnd = _tcschr(NameStart + 1, TEXT('\"'));
                    }                        

                    if (NameEnd) {  
                        _tcsncpy(Token, NameStart, NameEnd - NameStart);
                        Token[NameEnd - NameStart] = 0;
                        BOIOSFixupString(Token, TEXT("\r\n\""));
                        OSBESetFriendlyName(BaseBootEntry, Token);
                    } else {
                        Result = FALSE;
                    }                        

                    //
                    // Set osload options 
                    //                    
                    NextToken = _tcschr(EqualSign + 1, TEXT('/'));

                    if (NextToken) {  
                        _tcscpy(Token, NextToken);
                        BOIOSFixupString(Token, TEXT("\r\n"));
                        OSBESetOsLoadOptions(BaseBootEntry, Token);
                    }                        
                }                    
            }

            if (!Result) {
                SBE_FREE(BaseBootEntry);
                BaseBootEntry = NULL;
            } else {
                Entry = BaseBootEntry;
            }                
        }
    }

    return Entry;
}

static
VOID
BOIOSBEDelete(
    IN  POS_BOOT_ENTRY  Obj
    )
{
    PBOI_OS_BOOT_ENTRY  This = (PBOI_OS_BOOT_ENTRY)Obj;
    
    if (This) {
        SBE_FREE(This);
    }        
}

static
BOOLEAN
BOIOSBEWrite(
    IN POS_BOOT_ENTRY  This,
    IN OUT PTSTR Buffer
    )
{
    BOOLEAN Result = FALSE;

    if (This && Buffer && !OSBE_IS_DELETED(This)) {
        _tcscat(Buffer, OSBEGetBootVolumeName(This));
        _tcscat(Buffer, TEXT("\\"));
        _tcscat(Buffer, OSBEGetBootPath(This));
        _tcscat(Buffer, TEXT("="));
        _tcscat(Buffer, TEXT("\""));
        _tcscat(Buffer, OSBEGetFriendlyName(This));
        _tcscat(Buffer, TEXT("\""));
        _tcscat(Buffer, TEXT(" "));
        _tcscat(Buffer, OSBEGetOsLoadOptions(This));
        _tcscat(Buffer, TEXT("\r\n"));
        Result = TRUE;
    }

    return Result;
}


static
BOOLEAN
BOIOSBEFlush(
    IN  POS_BOOT_ENTRY  Obj
    )
{
    return TRUE;   // currently can't flush individual entries
}

//
// BOI_OS_BOOT_OPTIONS Methods
//
static
VOID
BOIOSBOInit(
    IN PBOI_OS_BOOT_OPTIONS  This
    )
{
    This->OsBootOptions.Delete = BOIOSBODelete;
    This->OsBootOptions.Flush = BOIOSBOFlush;
    This->OsBootOptions.AddNewBootEntry = BOIOSBOAddNewBootEntry;
    This->OsBootOptions.DeleteBootEntry = OSBODeleteBootEntry;
}

BOOLEAN
BOIOSBOParseAndCreateBootEntries(
    IN PBOI_OS_BOOT_OPTIONS This,
    IN PBOI_SECTION Section    
    )
{
    BOOLEAN Result = FALSE;

    if (This && Section) {
        Result = TRUE;
    
        if (Section->Contents) {
            PTSTR   NextLineStart = Section->Contents;
            PTSTR   NextLineEnd;
            TCHAR   OldChar;
            POS_BOOT_ENTRY  FirstBootEntry = NULL;
            POS_BOOT_ENTRY  BootEntry = NULL;
            POS_BOOT_ENTRY  LastBootEntry = NULL;
            ULONG BootEntryCount;

            while (NextLineStart) {
                NextLineEnd = _tcschr(NextLineStart, TEXT('\r'));

                if (NextLineEnd) {
                    if (*(NextLineEnd + 1) == TEXT('\n')) {
                        NextLineEnd++;
                    }

                    NextLineEnd++;
                    OldChar = *NextLineEnd;
                    *NextLineEnd = 0;
                }                    

                //
                // Each boot entry line needs to be more than 2 characters in 
                // length and contain an entry of "a=b" form
                //
                if ((!NextLineEnd || ((NextLineEnd - NextLineStart) > 2)) &&
                    (_tcschr(NextLineStart, TEXT('=')))) {
                    BootEntry = BOIOSBECreate(This->NextEntryId++, NextLineStart, This);

                    if (BootEntry) {
                        This->OsBootOptions.EntryCount++;

                        if (!FirstBootEntry) {
                            FirstBootEntry = LastBootEntry = BootEntry;
                        } else {
                            LastBootEntry->NextEntry = BootEntry;
                            LastBootEntry = BootEntry;
                        }                                            
                    } else {
                        Result = FALSE;

                        break;  // don't continue on
                    }                                                                            
                }                    

                if (NextLineEnd) {
                    *NextLineEnd = OldChar;
                }

                NextLineStart = NextLineEnd;
            }

            This->OsBootOptions.BootEntries = FirstBootEntry;
            
            //
            // Initialize the boot order array
            // NOTE : Doesn't make much sense with boot.ini currently
            //
            BootEntryCount = OSBOGetBootEntryCount((POS_BOOT_OPTIONS)This);

            if (BootEntryCount) {
                PULONG  BootOrder = (PULONG)SBE_MALLOC(BootEntryCount * sizeof(ULONG));

                if (BootOrder) {
                    ULONG Index = 0;
                    memset(BootOrder, 0, sizeof(ULONG) * BootEntryCount);

                    BootEntry = OSBOGetFirstBootEntry((POS_BOOT_OPTIONS)This);

                    while (BootEntry && (Index < BootEntryCount)) {
                        BootOrder[Index] = OSBEGetId(BootEntry);
                        BootEntry = OSBOGetNextBootEntry((POS_BOOT_OPTIONS)This, BootEntry);
                    }

                    This->OsBootOptions.BootOrder = BootOrder;
                    This->OsBootOptions.BootOrderCount = BootEntryCount;
                }
            }
        }
    }

    return Result;
}

BOOLEAN
BOIOSBOParseTimeoutAndActiveEntry(
    IN PBOI_OS_BOOT_OPTIONS This,
    IN PBOI_SECTION Section
    )
{
    BOOLEAN Result = FALSE;

    if (This && Section && !_tcsicmp(Section->Name, BOIOS_BOOTLOADER_SECTION)) {
        TCHAR   Buffer[MAX_PATH * 2];
        TCHAR   Timeout[MAX_PATH];
        TCHAR   Default[MAX_PATH];
        PTSTR   DefKey, TimeoutKey;
        PTSTR   DefValue;
        DWORD   TimeKeyLength = _tcslen(BOIOS_TIMEOUT_KEY);
        DWORD   DefKeyLength = _tcslen(BOIOS_DEFAULT_KEY);
        DWORD   CopyLength;

        Result = TRUE;
        
        _tcscpy(Buffer, Section->Contents);
        _tcslwr(Buffer);
        BOIOSFixupString(Buffer, TEXT("\r\n "));

        Timeout[0] = Default[0] = 0;
        
        DefKey = _tcsstr(Buffer, BOIOS_DEFAULT_KEY);
        TimeoutKey = _tcsstr(Buffer, BOIOS_TIMEOUT_KEY);
        
        if (DefKey && TimeoutKey) {
            if (DefKey > TimeoutKey) {        
                CopyLength = DefKey - TimeoutKey - TimeKeyLength;
                _tcsncpy(Timeout, TimeoutKey + TimeKeyLength, CopyLength);
                Timeout[CopyLength] = 0;                
                _tcscpy(Default, DefKey + DefKeyLength);
            } else {
                CopyLength = TimeoutKey - DefKey - DefKeyLength;
                _tcsncpy(Default, DefKey + DefKeyLength, CopyLength);
                Default[CopyLength] = 0;                
                _tcscpy(Timeout, TimeoutKey + TimeKeyLength);
            }
        } else if (DefKey) {
            _tcscpy(Default, DefKey + DefKeyLength);
        } else if (TimeoutKey) {
            _tcscpy(Timeout, TimeoutKey + TimeKeyLength);
        }                        

        if (TimeoutKey) {        
            ULONG TimeoutValue = _ttol(Timeout);

            OSBOSetTimeOut((POS_BOOT_OPTIONS)This, TimeoutValue);
        }

        if (DefKey) {
            PTSTR   BootPath = _tcschr(Default, TEXT('\\'));

            if (BootPath) {
                POS_BOOT_ENTRY CurrEntry;

                *BootPath = 0;                
                CurrEntry = OSBOGetFirstBootEntry((POS_BOOT_OPTIONS)This);                

                while (CurrEntry) {
                    if (_tcsstr(Default, OSBEGetBootVolumeName(CurrEntry)) &&
                        !_tcsicmp(OSBEGetBootPath(CurrEntry), BootPath + 1)) {
                        break;
                    }

                    CurrEntry = OSBOGetNextBootEntry((POS_BOOT_OPTIONS)This, CurrEntry);
                }

                if (CurrEntry) {
                    OSBOSetActiveBootEntry((POS_BOOT_OPTIONS)This, CurrEntry);
                }                        
            } else {
                Result = FALSE;
            }                    
        }            

        OSBO_RESET_DIRTY((POS_BOOT_OPTIONS)This);
    }

    return Result;
}

POS_BOOT_OPTIONS
BOIOSBOCreate(
    IN PCTSTR   BootIniPath,
    IN BOOLEAN  OpenExisting
    )
{
    POS_BOOT_OPTIONS This = NULL;

    if (BootIniPath) {
        BY_HANDLE_FILE_INFORMATION FileInfo = {0};
        PCHAR   FileContent = NULL;
        HANDLE  BootIniHandle;

        //
        // Open the file
        //
        BootIniHandle = CreateFile(BootIniPath,
                            GENERIC_READ,
                            FILE_SHARE_READ,
                            NULL, 
                            OPEN_EXISTING,                                    
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);

        if ((BootIniHandle != INVALID_HANDLE_VALUE) &&
            GetFileInformationByHandle(BootIniHandle,
                &FileInfo)){
            //
            // Map the file
            //
            HANDLE MapHandle = CreateFileMapping(BootIniHandle,
                                    NULL,
                                    PAGE_READONLY,
                                    FileInfo.nFileSizeHigh,
                                    FileInfo.nFileSizeLow,
                                    NULL);

            if (MapHandle) {
                //
                // Get hold of view for the file content
                //
                PVOID   FileView = MapViewOfFile(MapHandle,
                                        FILE_MAP_READ,
                                        0,
                                        0,
                                        0);

                if (FileView) {
                    DWORD BytesRead = 0;

                    //
                    // Allocate the buffer and read the file contents
                    //
                    FileContent = SBE_MALLOC(FileInfo.nFileSizeLow + 1);

                    if (FileContent) {
                        if (!ReadFile(BootIniHandle,
                                FileContent,
                                FileInfo.nFileSizeLow,
                                &BytesRead,
                                NULL)) {
                            SBE_FREE(FileContent);
                            FileContent = NULL;
                        } else {
                            FileContent[FileInfo.nFileSizeLow] = 0;
                        }                            
                    } 

                    UnmapViewOfFile(FileView);
                }

                CloseHandle(MapHandle);
            }
            
            CloseHandle(BootIniHandle);
        } else {
            //
            // Could be that user is creating boot options fresh
            //
            if (!OpenExisting) {        
                PBOI_OS_BOOT_OPTIONS Obj = (PBOI_OS_BOOT_OPTIONS)SBE_MALLOC(sizeof(BOI_OS_BOOT_OPTIONS));

                if (Obj) {
                    //
                    // Initialize object
                    //
                    memset(Obj, 0, sizeof(BOI_OS_BOOT_OPTIONS));
                    BOIOSBOInit(Obj);
                    _tcscpy(Obj->BootIniPath, BootIniPath);                    
                }                    

                This = (POS_BOOT_OPTIONS)Obj;
            }                    
        }

        //
        // If there is any file content then parse it
        //
        if (FileContent) {
#ifdef UNICODE
            PWSTR   Content = SBE_MALLOC((FileInfo.nFileSizeLow + 1) * sizeof(WCHAR));

            //
            // Convert the Ansi/OEM content to unicode content
            //
            if (Content) {
                if (MultiByteToWideChar(CP_OEMCP,
                        0,
                        FileContent,
                        FileInfo.nFileSizeLow,
                        Content,
                        FileInfo.nFileSizeLow + 1)) {
                    Content[FileInfo.nFileSizeLow ] = 0;                        
                } else {
                    SBE_FREE(Content);
                    Content = NULL;
                }                    
            } else {
                SBE_FREE(FileContent);
                FileContent = NULL;
            }   
            
#else
            PCHAR   Content = FileContent;
#endif

            if (Content && FileContent) {
                TCHAR   NextLine[MAX_PATH * 4];
                PTSTR   NextSectionStart = _tcschr(Content, BOIOS_SECTION_NAME_START);
                PTSTR   NextSectionEnd;
                PBOI_SECTION SectionList = NULL;
                PBOI_SECTION Section = NULL;
                PBOI_SECTION TailSection = NULL;
                BOOLEAN Result = TRUE;

                //
                // Prase the whole files and create section objects
                //
                while (NextSectionStart) {
                    TCHAR   OldChar;
                    
                    Section = NULL;
                    
                    NextSectionEnd = _tcschr(NextSectionStart + 1, BOIOS_SECTION_NAME_START);

                    if (NextSectionEnd) {                        
                        OldChar = *NextSectionEnd;
                        *NextSectionEnd = 0;    // null terminate                        
                    }                    

                    //
                    // Create the section object
                    //
                    Section = BOISectionCreate(NextSectionStart);

                    if (NextSectionEnd) {                        
                        *NextSectionEnd = OldChar; 
                    }                    
                    
                    if (Section) {
                        if (!SectionList) {
                            SectionList = Section;
                        } else {                            
                            TailSection->Next = Section;
                        }                            
                        
                        TailSection = Section;
                    } else {
                        Result = FALSE;
                        break;
                    }                       

                    NextSectionStart = NextSectionEnd;
                }                

                if (Result) {
                    PBOI_OS_BOOT_OPTIONS Obj = (PBOI_OS_BOOT_OPTIONS)SBE_MALLOC(sizeof(BOI_OS_BOOT_OPTIONS));

                    if (Obj) {
                        //
                        // Initialize object
                        //
                        memset(Obj, 0, sizeof(BOI_OS_BOOT_OPTIONS));
                        BOIOSBOInit(Obj);
                        _tcscpy(Obj->BootIniPath, BootIniPath);

                        Obj->Sections = SectionList;
                        SectionList = NULL;

                        //
                        // Get hold of [operating systems] section and
                        // parse its entries and create boot entries
                        //
                        Section = BOIOSBOFindSection(Obj, BOIOS_OS_SECTION);

                        if (Section) {
                            Result = BOIOSBOParseAndCreateBootEntries(Obj, Section);
                        }                                                        

                        //
                        // Get hold of [boot loader] section and prase its
                        // entries
                        //
                        if (Result) {
                            Section = BOIOSBOFindSection(Obj, BOIOS_BOOTLOADER_SECTION);

                            if (Section) {                                    
                                Result = BOIOSBOParseTimeoutAndActiveEntry(Obj, Section);
                            }
                        }

                        if (!Result) {
                            //
                            // Delete the object to free up all the sections
                            // and the entries
                            //
                            BOIOSBODelete((POS_BOOT_OPTIONS)Obj);
                            Obj = NULL;
                        } 

                        This = (POS_BOOT_OPTIONS)Obj;
                    } else {
                        Result = FALSE;
                    }                        
                }

                //
                // free up the allocated sections, in case of failure
                //
                if (!Result && SectionList) {
                    while (SectionList) {
                        Section = SectionList;
                        SectionList = SectionList->Next;
                        BOISectionDelete(Section);
                    }                                            
                }

                //
                // Free the content
                //
                if ((PVOID)Content != (PVOID)FileContent) {
                    SBE_FREE(Content);               
                }            
            }

            SBE_FREE(FileContent);
        }
    }
    
    return This;
}

static        
VOID
BOIOSBODelete(
    IN POS_BOOT_OPTIONS Obj
    )
{
    PBOI_OS_BOOT_OPTIONS This = (PBOI_OS_BOOT_OPTIONS)Obj;
    
    if (This) {
        PBOI_SECTION CurrSection, PrevSection;
        
        //
        // delete each boot entry 
        //
        POS_BOOT_ENTRY Entry = OSBOGetFirstBootEntry(Obj);
        POS_BOOT_ENTRY PrevEntry;

        while (Entry) {
            PrevEntry = Entry;
            Entry = OSBOGetNextBootEntry(Obj, Entry);
            OSBEDelete(PrevEntry);
        }

        //
        // delete all the sections
        //
        CurrSection = This->Sections;

        while (CurrSection) {
            PrevSection = CurrSection;
            CurrSection = CurrSection->Next;
            BOISectionDelete(PrevSection);
        }

        if (Obj->BootOrder) {
            SBE_FREE(Obj->BootOrder);
        }

        //
        // delete the main object
        //
        SBE_FREE(This);
    }        
}

static
POS_BOOT_ENTRY
BOIOSBOAddNewBootEntry(
    IN POS_BOOT_OPTIONS This,
    IN PCTSTR            FriendlyName,
    IN PCTSTR            OsLoaderVolumeName,
    IN PCTSTR            OsLoaderPath,
    IN PCTSTR            BootVolumeName,
    IN PCTSTR            BootPath,
    IN PCTSTR            OsLoadOptions
    )
{
    PBOI_OS_BOOT_ENTRY  Entry = NULL;

    if (This && FriendlyName && BootVolumeName && BootPath) {
        Entry = SBE_MALLOC(sizeof(BOI_OS_BOOT_ENTRY));

        if (Entry) {
            ULONG   OrderCount;
            PULONG  NewOrder;
            POS_BOOT_ENTRY BaseEntry = (POS_BOOT_ENTRY)Entry;
            PBOI_OS_BOOT_OPTIONS Obj = (PBOI_OS_BOOT_OPTIONS)This;
        
            //
            // init core fields
            //
            memset(Entry, 0, sizeof(BOI_OS_BOOT_ENTRY));
            BOIOSBEInit(Entry);            
            Entry->OsBootEntry.BootOptions = This;

            //
            // fill in the attributes
            //
            OSBESetFriendlyName((POS_BOOT_ENTRY)Entry, FriendlyName);
            OSBESetBootVolumeName((POS_BOOT_ENTRY)Entry, BootVolumeName);
            OSBESetBootPath((POS_BOOT_ENTRY)Entry, BootPath);            

            if (OsLoadOptions) {
                OSBESetOsLoadOptions((POS_BOOT_ENTRY)Entry, OsLoadOptions);
            }

            BaseEntry->Id = Obj->NextEntryId++;

            //
            // Flush the entry now to get a proper Id;
            //
                
            Entry->OsBootEntry.BootOptions = (POS_BOOT_OPTIONS)This;            
            Entry->OsBootEntry.NextEntry = This->BootEntries;
            This->BootEntries = (POS_BOOT_ENTRY)Entry;
            This->EntryCount++;

            //
            // Put the new entry at the end of the boot order
            //
            OrderCount = OSBOGetOrderedBootEntryCount(This);

            NewOrder = (PULONG)SBE_MALLOC((OrderCount + 1) * sizeof(ULONG));

            if (NewOrder) {
                memset(NewOrder, 0, sizeof(ULONG) * (OrderCount + 1));

                //
                // copy over the old ordered list
                //
                memcpy(NewOrder, This->BootOrder, sizeof(ULONG) * OrderCount);
                NewOrder[OrderCount] = OSBEGetId((POS_BOOT_ENTRY)Entry);
                SBE_FREE(This->BootOrder);
                This->BootOrder = NewOrder;
                This->BootOrderCount = OrderCount + 1;
            } else {
                OSBODeleteBootEntry(This, BaseEntry);
                Entry = NULL;
            }                    

            if (Entry) {
                //
                // mark it dirty and new for flushing
                //
                OSBE_SET_NEW(Entry);
                OSBE_SET_DIRTY(Entry);                                
            }                
        }
    }        
    
    return (POS_BOOT_ENTRY)Entry;
}

static
BOOLEAN
BOIOSBOWrite(
    IN PBOI_OS_BOOT_OPTIONS This,
    IN PCTSTR Buffer
    )
{
    BOOLEAN Result = FALSE;

    if (This && Buffer) {
        TCHAR   BackupFileName[MAX_PATH];
        PTSTR   Extension;
        HANDLE  FileHandle;

        //
        // Create a backup name
        //
        _tcscpy(BackupFileName, This->BootIniPath);
        Extension = _tcschr(BackupFileName, TEXT('.'));

        if (Extension) {
            _tcscpy(Extension, TEXT(".BAK"));
        } else {
            _tcscat(BackupFileName, TEXT(".BAK"));
        }            

        //
        // Delete the backup file if it exists
        //
        SetFileAttributes(BackupFileName, FILE_ATTRIBUTE_NORMAL);
        DeleteFile(BackupFileName);

        //
        // Copy the existing boot.ini as backup file
        //
        SetFileAttributes(This->BootIniPath, FILE_ATTRIBUTE_NORMAL);
        CopyFile(This->BootIniPath, BackupFileName, FALSE);

        //
        // Create new boot.ini file
        //
        FileHandle = CreateFile(This->BootIniPath,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);

        if (FileHandle && (FileHandle != INVALID_HANDLE_VALUE)) {
            PCHAR   AnsiBuffer;
            ULONG   BufferLength = _tcslen(Buffer);
            DWORD   BytesWritten = 0;

            Result = TRUE;
            
#ifdef UNICODE
            //
            // Convert the unicode buffer to ansi buffer
            //
            AnsiBuffer = (PCHAR)SBE_MALLOC(BufferLength + 1);

            if (AnsiBuffer) {
                memset(AnsiBuffer, 0, BufferLength);

                if (WideCharToMultiByte(CP_OEMCP,
                        0,
                        Buffer,
                        BufferLength,
                        AnsiBuffer,
                        BufferLength,
                        NULL,
                        NULL)) {
                    Result = TRUE;
                    AnsiBuffer[BufferLength] = 0;
                } else {
                    Result = FALSE;
                }                            
            }
#else   
            AnsiBuffer = Buffer;
#endif

            //
            // Write the buffer to the file
            //
            if (AnsiBuffer && 
                !WriteFile(FileHandle, 
                        AnsiBuffer,
                        BufferLength,
                        &BytesWritten,
                        NULL)) {
                Result = FALSE;                            
            }                  

            if ((PVOID)AnsiBuffer != (PVOID)Buffer) {
                SBE_FREE(AnsiBuffer);
                AnsiBuffer = NULL;
            }

            //
            // Done with the file handle
            //
            CloseHandle(FileHandle);

            SetFileAttributes(This->BootIniPath, 
                FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY |
                FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
        }            
    }        

    return Result;
}

static
BOOLEAN
BOIOSBOFlush(
    IN POS_BOOT_OPTIONS Obj
    )
{
    BOOLEAN Result = FALSE;
    PBOI_OS_BOOT_OPTIONS  This = (PBOI_OS_BOOT_OPTIONS)Obj;    

    if (This) { 
        PTSTR   Buffer = (PTSTR)SBE_MALLOC(MAX_BOOT_INI_SIZE * sizeof(TCHAR));

        if (Buffer) {
            TCHAR   ScratchBuffer[MAX_PATH * 2] = {0};
            POS_BOOT_ENTRY ActiveEntry = OSBOGetActiveBootEntry(Obj);
            POS_BOOT_ENTRY CurrentEntry;
            PBOI_SECTION   CurrentSection;

            Result = TRUE;
            
            memset(Buffer, 0, MAX_BOOT_INI_SIZE * sizeof(TCHAR));            

            //
            // first flush the boot options
            //
            _tcscat(Buffer, BOIOS_SECTION_NAME_START_STR);
            _tcscat(Buffer, BOIOS_BOOTLOADER_SECTION);
            _tcscat(Buffer, BOIOS_SECTION_NAME_END_STR);
            _tcscat(Buffer, TEXT("\r\n"));

            //
            // write time out
            //
            _tcscat(Buffer, BOIOS_TIMEOUT_KEY);
            _tcscat(Buffer, _ltot(Obj->Timeout, ScratchBuffer, 10));
            _tcscat(Buffer, TEXT("\r\n"));

            //
            // write active entry
            //
            if (ActiveEntry) {
                _tcscpy(ScratchBuffer, BOIOS_DEFAULT_KEY);
                _tcscat(ScratchBuffer, OSBEGetBootVolumeName(ActiveEntry));
                _tcscat(ScratchBuffer, TEXT("\\"));
                _tcscat(ScratchBuffer, OSBEGetBootPath(ActiveEntry));
                _tcscat(ScratchBuffer, TEXT("\r\n"));

                _tcscat(Buffer, ScratchBuffer);
            }                

            //
            // Write the boot entries section 
            //
            _tcscat(Buffer, BOIOS_SECTION_NAME_START_STR);
            _tcscat(Buffer, BOIOS_OS_SECTION);
            _tcscat(Buffer, BOIOS_SECTION_NAME_END_STR);
            _tcscat(Buffer, TEXT("\r\n"));

            //
            // write each boot entry now
            //

            //
            // First write the valid arc entries
            //
            CurrentEntry = OSBOGetFirstBootEntry(Obj);

            while (Result && CurrentEntry) {
                if (!OSBE_IS_DELETED(CurrentEntry) &&
                    !OSBE_IS_OLDOS(CurrentEntry)) {
                    Result = BOIOSBEWrite(CurrentEntry, Buffer);                    
                }
                
                CurrentEntry = OSBOGetNextBootEntry(Obj, CurrentEntry);
            }

            //
            // Now write the old OS entries
            // NOTE : We do this for backward compatabily reasons
            //
            CurrentEntry = OSBOGetFirstBootEntry(Obj);

            while (Result && CurrentEntry) {
                if (OSBE_IS_OLDOS(CurrentEntry)) {
                    Result = BOIOSBEWrite(CurrentEntry, Buffer);                    
                }
                
                CurrentEntry = OSBOGetNextBootEntry(Obj, CurrentEntry);
            }

            //
            // Write any additions sections which were present on the
            // 
            CurrentSection = BOIOSGetFirstSection(This);

            while (Result && CurrentSection) {
                //
                // Write all the other additional sections in boot.ini other
                // than [boot loader] and [operating systems]
                //
                if (_tcsicmp(BOISectionGetName(CurrentSection), BOIOS_BOOTLOADER_SECTION) &&
                    _tcsicmp(BOISectionGetName(CurrentSection), BOIOS_OS_SECTION)) {
                    Result = BOISectionWrite(CurrentSection, Buffer);
                }

                CurrentSection = BOIOSGetNextSection(This, CurrentSection);
            }         

            Result = BOIOSBOWrite(This, Buffer);

            //
            // Free the allocated buffer
            //
            SBE_FREE(Buffer);
        }
    }

    return Result;
}