/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is msdump2symdb.c code, released
 * Jan 16, 2003.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Garrett Arch Blythe, 16-January-2003
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>

#define ERROR_REPORT(num, val, msg)   fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg));
#define CLEANUP(ptr)    do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0)


typedef struct __struct_Options
/*
**  Options to control how we perform.
**
**  mProgramName    Used in help text.
**  mInput          File to read for input.
**                  Default is stdin.
**  mInputName      Name of the file.
**  mOutput         Output file, append.
**                  Default is stdout.
**  mOutputName     Name of the file.
**  mHelp           Whether or not help should be shown.
*/
{
    const char* mProgramName;
    FILE* mInput;
    char* mInputName;
    FILE* mOutput;
    char* mOutputName;
    int mHelp;
}
Options;


typedef struct __struct_Switch
/*
**  Command line options.
*/
{
    const char* mLongName;
    const char* mShortName;
    int mHasValue;
    const char* mValue;
    const char* mDescription;
}
Switch;

#define DESC_NEWLINE "\n\t\t"

static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."};
static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."};
static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."};

static Switch* gSwitches[] = {
        &gInputSwitch,
        &gOutputSwitch,
        &gHelpSwitch
};


typedef struct __struct_MSDump_Symbol
/*
**  Struct to hold infomration on a symbol.
**
**  mSize               Size of the symbol once all work is complete.
**  mOffset             Offset of the symbol in the section.
**  mName               Symbolic name.
*/
{
    unsigned    mSize;
    unsigned    mOffset;
    char*       mName;
}
MSDump_Symbol;


typedef struct __struct_MSDump_Section
/*
**  Struct for holding information on a section.
**
**  mLength             Length of the section in bytes.
**  mUsed               Number of bytes used in the section thus far.
**                      Should eventually match mLength after work is done.
**  mType               Type of section, as string (.data, .text, et. al.)
**  mSymbols            Symbols found inside the section.
**  mSymbolCount        Number of symbols in array.
*/
{
    unsigned            mLength;
    unsigned            mUsed;
    char*               mType;

    MSDump_Symbol*      mSymbols;
    unsigned            mSymbolCount;
}
MSDump_Section;


typedef struct __struct_MSDump_Object
/*
**  Struct for holding object's data.
*/
{
    char*   mObject;

    MSDump_Section*     mSections;
    unsigned            mSectionCount;
}
MSDump_Object;


typedef struct __struct_MSDump_ReadState
/*
**  State flags while reading the input gives us hints on what to do.
**
**  mSkipLines                  Number of lines to skip without parsing.
**  mSectionDetails             Section information next, like line length.
**  mCurrentObject              Object file we are dealing with.
*/
{
    unsigned            mSkipLines;
    unsigned            mSectionDetails;
    MSDump_Object*      mCurrentObject;
}
MSDump_ReadState;


typedef struct __struct_MSDump_Container
/*
**  Umbrella container for all data encountered.
*/
{
    MSDump_ReadState    mReadState;

    MSDump_Object*      mObjects;
    unsigned            mObjectCount;
}
MSDump_Container;


void trimWhite(char* inString)
/*
**  Remove any whitespace from the end of the string.
*/
{
    int len = strlen(inString);

    while(len)
    {
        len--;

        if(isspace(*(inString + len)))
        {
            *(inString + len) = '\0';
        }
        else
        {
            break;
        }
    }
}


const char* skipWhite(const char* inString)
/*
**  Return pointer to first non white space character.
*/
{
    const char* retval = inString;

    while('\0' != *retval && isspace(*retval))
    {
        retval++;
    }

    return retval;
}


const char* skipNonWhite(const char* inString)
/*
**  Return pointer to first white space character.
*/
{
    const char* retval = inString;

    while('\0' != *retval && !isspace(*retval))
    {
        retval++;
    }

    return retval;
}


void slash2bs(char* inString)
/*
**  Change any forward slash to a backslash.
*/
{
    char* slash = inString;

    while(NULL != (slash = strchr(slash, '/')))
    {
        *slash = '\\';
        slash++;
    }
}


const char* skipToArg(const char* inString, unsigned inArgIndex)
/*
**  Return pointer either to the arg or NULL.
**  1 indexed.
*/
{
    const char* retval = NULL;

    while(0 != inArgIndex && '\0' != *inString)
    {
        inArgIndex--;

        inString = skipWhite(inString);
        if(0 != inArgIndex)
        {
            inString = skipNonWhite(inString);
        }
    }

    if('\0' != *inString)
    {
        retval = inString;
    }

    return retval;
}


const char* getLastArg(const char* inString)
/*
**  Return pointer to last arg in string.
*/
{
    const char* retval = NULL;
    int length = 0;
    int sawString = 0;

    length = strlen(inString);
    while(0 != length)
    {
        length--;

        if(0 == sawString)
        {
            if(0 == isspace(inString[length]))
            {
                sawString = __LINE__;
            }
        }
        else
        {
            if(0 != isspace(inString[length]))
            {
                retval = inString + length + 1;
            }
        }
    }

    return retval;
}


int processLine(Options* inOptions, MSDump_Container* inContainer, const char* inLine)
/*
**  Handle one line at a time.
**  Looking for several different types of lines.
**  Ignore all other lines.
**  The container is the state machine.
**  returns 0 on no error.
*/
{
    int retval = 0;

    /*
    **  Check to see if we were expecting section details.
    */
    if(0 != inContainer->mReadState.mSectionDetails)
    {
        const char* length = NULL;
        unsigned sectionIndex = 0;

        /*
        **  Detail is a 1 based index....
        **  Reset.
        */
        sectionIndex = inContainer->mReadState.mSectionDetails - 1;
        inContainer->mReadState.mSectionDetails = 0;

        if(0 == strncmp("    Section length", inLine, 18))
        {
            const char* sectionLength = NULL;
            unsigned numericLength = 0;
            char* endScan = NULL;

            sectionLength = skipWhite(inLine + 18);

            errno = 0;
            numericLength = strtoul(sectionLength, &endScan, 16);
            if(0 == errno && endScan != sectionLength)
            {
                inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mLength = numericLength;
            }
            else
            {
                retval = __LINE__;
                ERROR_REPORT(retval, inLine, "Cannot scan for section length.");
            }
        }
        else
        {
            retval = __LINE__;
            ERROR_REPORT(retval, inLine, "Cannot parse section line.");
        }
    }
    /*
    **  Check for switching object file symbols.
    */
    else if(0 == strncmp("Dump of file ", inLine, 13))
    {
        const char* dupMe = inLine + 13;
        char* dup = NULL;
        
        dup = strdup(dupMe);
        if(NULL != dup)
        {
            void* growth = NULL;
            
            trimWhite(dup);
            slash2bs(dup);
            
            
            growth = realloc(inContainer->mObjects, (inContainer->mObjectCount + 1) * sizeof(MSDump_Object));
            if(NULL != growth)
            {
                unsigned int index = inContainer->mObjectCount;
                
                inContainer->mObjectCount++;
                inContainer->mObjects = growth;
                memset(inContainer->mObjects + index, 0, sizeof(MSDump_Object));
                
                inContainer->mObjects[index].mObject = dup;

                /*
                **  Reset the read state for this new object.
                */
                memset(&inContainer->mReadState, 0, sizeof(MSDump_ReadState));

                /*
                **  Record our current object file.
                */
                inContainer->mReadState.mCurrentObject = inContainer->mObjects + index;

                /*
                **  We can skip a few lines.
                */
                inContainer->mReadState.mSkipLines = 4;
            }
            else
            {
                retval = __LINE__;
                ERROR_REPORT(retval, dup, "Unable to grow object array.");
                free(dup);
            }
        }
        else
        {
            retval = __LINE__;
            ERROR_REPORT(retval, dupMe, "Unable to copy string.");
            
        }
    }
    /*
    **  Check for a symbol dump or a section header.
    */
    else if(isxdigit(*inLine) && isxdigit(*(inLine + 1)) && isxdigit(*(inLine + 2)))
    {
        const char* sectionString = NULL;

        /*
        **  Determine the section for this line.
        **  Ignore DEBUG sections.
        */
        sectionString = skipToArg(inLine, 3);
        if(NULL != sectionString)
        {
            if(0 != strncmp(sectionString, "DEBUG", 5) && 0 != strncmp(sectionString, "ABS", 3) && 0 != strncmp(sectionString, "UNDEF", 5))
            {
                /*
                **  MUST start with "SECT"
                */
                if(0 == strncmp(sectionString, "SECT", 4))
                {
                    unsigned sectionIndex1 = 0;

                    char *endScan = NULL;

                    sectionString += 4;

                    /*
                    **  Convert the remaining string to an index.
                    **  It will be 1 based.
                    */
                    errno = 0;
                    sectionIndex1 = strtoul(sectionString, &endScan, 16);
                    if(0 == errno && endScan != sectionString && 0 != sectionIndex1)
                    {
                        unsigned sectionIndex = sectionIndex1 - 1;

                        /*
                        **  Is this a new section? Assumed to be ascending.
                        **  Or is this a symbol in the section?
                        */
                        if(sectionIndex1 > inContainer->mReadState.mCurrentObject->mSectionCount)
                        {
                            const char* typeArg = NULL;

                            /*
                            **  New Section, figure out the type.
                            */
                            typeArg = skipToArg(sectionString, 5);
                            if(NULL != typeArg)
                            {
                                char* typeDup = NULL;

                                /*
                                **  Skip the leading period before duping.
                                */
                                if('.' == *typeArg)
                                {
                                    typeArg++;
                                }
                                typeDup = strdup(typeArg);

                                if(NULL != typeDup)
                                {
                                    void* moved = NULL;
                                    char* nonWhite = NULL;

                                    /*
                                    **  Terminate the duplicate after the section type.
                                    */
                                    nonWhite = (char*)skipNonWhite(typeDup);
                                    if(NULL != nonWhite)
                                    {
                                        *nonWhite = '\0';
                                    }

                                    /*
                                    **  Create more space for the section in the object...
                                    */
                                    moved = realloc(inContainer->mReadState.mCurrentObject->mSections, sizeof(MSDump_Section) * sectionIndex1);
                                    if(NULL != moved)
                                    {
                                        unsigned oldCount = inContainer->mReadState.mCurrentObject->mSectionCount;

                                        inContainer->mReadState.mCurrentObject->mSections = (MSDump_Section*)moved;
                                        inContainer->mReadState.mCurrentObject->mSectionCount = sectionIndex1;
                                        memset(&inContainer->mReadState.mCurrentObject->mSections[oldCount], 0, sizeof(MSDump_Section) * (sectionIndex1 - oldCount));
                                        
                                        /*
                                        **  Other section details.
                                        */
                                        inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mType = typeDup;
                                            
                                            
                                        /*
                                        **  Mark it so that we look for the length on the next line.
                                        **  This happens on next entry into the read state.
                                        */
                                        inContainer->mReadState.mSectionDetails = sectionIndex1;
                                    }
                                    else
                                    {
                                        retval = __LINE__;
                                        ERROR_REPORT(retval, inLine, "Unable to grow for new section.");
                                        free(typeDup);
                                    }
                                }
                                else
                                {
                                    retval = __LINE__;
                                    ERROR_REPORT(retval, typeArg, "Unable to duplicate type.");
                                }
                            }
                            else
                            {
                                retval = __LINE__;
                                ERROR_REPORT(retval, inLine, "Unable to determine section type.");
                            }

                        }
                        else
                        {
                            const char* offsetArg = NULL;
                            const char* classArg = NULL;
                            unsigned classWords = 1;
                            const char* symbolArg = NULL;

                            /*
                            **  This is an section we've seen before, and must list a symbol.
                            **  Figure out the things we want to know about the symbol, e.g. size.
                            **  We will ignore particular classes of symbols.
                            */

                            offsetArg = skipToArg(inLine, 2);

                            classArg = skipToArg(offsetArg, 4);
                            if(0 == strncmp(classArg, "()", 2))
                            {
                                classArg = skipToArg(classArg, 2);
                            }
                            if(0 == strncmp(classArg, ".bf or.ef", 9))
                            {
                                classWords = 2;
                            }

                            symbolArg = skipToArg(classArg, 3 + (classWords - 1));

                            /*
                            **  Skip particular lines/items.
                            */
                            if(
                                0 != strncmp(classArg, "Label", 5) &&
                                0 != strncmp(symbolArg, ".bf", 3) &&
                                0 != strncmp(symbolArg, ".lf", 3) &&
                                0 != strncmp(symbolArg, ".ef", 3)
                                )
                            {
                                char* endOffsetArg = NULL;
                                unsigned offset = 0;
                                
                                /*
                                ** Convert the offset to something meaninful (size).
                                */
                                errno = 0;
                                offset = strtoul(offsetArg, &endOffsetArg, 16);
                                if(0 == errno && endOffsetArg != offsetArg)
                                {
                                    void* moved = NULL;
                                    
                                    /*
                                    **  Increase the size of the symbol array in the section.
                                    **  Assumed symbols are unique within each section.
                                    */
                                    moved = realloc(inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols, sizeof(MSDump_Symbol) * (inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbolCount + 1));
                                    if(NULL != moved)
                                    {
                                        unsigned symIndex = 0;

                                        /*
                                        **  Record symbol details.
                                        **  Assumed symbols are encountered in order for their section (size calc depends on it).
                                        */
                                        symIndex = inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbolCount;
                                        inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbolCount++;
                                        inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols = (MSDump_Symbol*)moved;
                                        memset(&inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols[symIndex], 0, sizeof(MSDump_Symbol));
                                        
                                        inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols[symIndex].mOffset = offset;
                                        
                                        /*
                                        **  We could allocate smarter here if it ever mattered.
                                        */
                                        inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols[symIndex].mName = strdup(symbolArg);
                                        if(NULL != inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols[symIndex].mName)
                                        {
                                            char* trim = NULL;

                                            trim = (char*)skipNonWhite(inContainer->mReadState.mCurrentObject->mSections[sectionIndex].mSymbols[symIndex].mName);
                                            if(NULL != trim)
                                            {
                                                *trim = '\0';
                                            }
                                        }
                                        else
                                        {
                                            retval = __LINE__;
                                            ERROR_REPORT(retval, inLine, "Unable to duplicate symbol name.");
                                        }
                                    }
                                    else
                                    {
                                        retval = __LINE__;
                                        ERROR_REPORT(retval, inLine, "Unable to grow symbol array for section.");
                                    }
                                }
                                else
                                {
                                    retval = __LINE__;
                                    ERROR_REPORT(retval, inLine, "Unable to convert offset to a number.");
                                }
                            }
                        }
                    }
                    else
                    {
                        retval = __LINE__;
                        ERROR_REPORT(retval, inLine, "Unable to determine section index.");
                    }
                }
                else
                {
                    retval = __LINE__;
                    ERROR_REPORT(retval, inLine, "No match for section prefix.");
                }
            }
        }
        else
        {
            retval = __LINE__;
            ERROR_REPORT(retval, inLine, "Unable to scan for section.");
        }
    }

    return retval;
}


void dumpCleanup(MSDump_Container* inContainer)
/*
**  Attempt to be nice and free up what we have allocated.
*/
{
    unsigned objectLoop = 0;
    unsigned sectionLoop = 0;
    unsigned symbolLoop = 0;

    for(objectLoop = 0; objectLoop < inContainer->mObjectCount; objectLoop++)
    {
        for(sectionLoop = 0; sectionLoop < inContainer->mObjects[objectLoop].mSectionCount; sectionLoop++)
        {
            for(symbolLoop = 0; symbolLoop < inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbolCount; symbolLoop++)
            {
                CLEANUP(inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mName);
            }
            inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbolCount = 0;
            CLEANUP(inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols);
            CLEANUP(inContainer->mObjects[objectLoop].mSections[sectionLoop].mType);
        }
        inContainer->mObjects[objectLoop].mSectionCount = 0;
        CLEANUP(inContainer->mObjects[objectLoop].mSections);
    }
    CLEANUP(inContainer->mObjects);
    inContainer->mObjectCount = 0;
}


int qsortSymOffset(const void* in1, const void* in2)
/*
**  qsort callback to sort the symbols by their offset.
*/
{
    MSDump_Symbol* sym1 = (MSDump_Symbol*)in1;
    MSDump_Symbol* sym2 = (MSDump_Symbol*)in2;
    int retval = 0;

    if(sym1->mOffset < sym2->mOffset)
    {
        retval = 1;
    }
    else if(sym1->mOffset > sym2->mOffset)
    {
        retval = -1;
    }

    return retval;
}


int calcContainer(Options* inOptions, MSDump_Container* inContainer)
/*
**  Resposible for doing any size calculations based on the offsets known.
**  After this calculation, each sections mUsed will match mSize.
**  After this calculation, all symbols should know how big they are.
*/
{
    int retval = 0;
    unsigned objectLoop = 0;
    unsigned sectionLoop = 0;
    unsigned symbolLoop = 0;


    /*
    **  Need to sort all symbols by their offsets.
    */
    for(objectLoop = 0; 0 == retval && objectLoop < inContainer->mObjectCount; objectLoop++)
    {
        for(sectionLoop = 0; 0 == retval && sectionLoop < inContainer->mObjects[objectLoop].mSectionCount; sectionLoop++)
        {
            qsort(
                inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols,
                inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbolCount,
                sizeof(MSDump_Symbol),
                qsortSymOffset
                );
        }
    }


    /*
    **  Need to go through all symbols and calculate their size.
    */
    for(objectLoop = 0; 0 == retval && objectLoop < inContainer->mObjectCount; objectLoop++)
    {
        for(sectionLoop = 0; 0 == retval && sectionLoop < inContainer->mObjects[objectLoop].mSectionCount; sectionLoop++)
        {
            for(symbolLoop = 0; 0 == retval && symbolLoop < inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbolCount; symbolLoop++)
            {
                inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mSize =
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mLength -
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mUsed -
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mOffset;

                inContainer->mObjects[objectLoop].mSections[sectionLoop].mUsed += 
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mSize;
            }
        }
    }


    return retval;
}


int reportContainer(Options* inOptions, MSDump_Container* inContainer)
/*
**  Display all symbols and their data.
**  We'll use a tsv format.
*/
{
    int retval = 0;
    unsigned objectLoop = 0;
    unsigned sectionLoop = 0;
    unsigned symbolLoop = 0;
    int printRes = 0;

    for(objectLoop = 0; 0 == retval && objectLoop < inContainer->mObjectCount; objectLoop++)
    {
        for(sectionLoop = 0; 0 == retval && sectionLoop < inContainer->mObjects[objectLoop].mSectionCount; sectionLoop++)
        {
            for(symbolLoop = 0; 0 == retval && symbolLoop < inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbolCount; symbolLoop++)
            {
                printRes = fprintf(inOptions->mOutput, "%s\t%s\t%.8X\t%s\n",
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mName,
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mType,
                    inContainer->mObjects[objectLoop].mSections[sectionLoop].mSymbols[symbolLoop].mSize,
                    inContainer->mObjects[objectLoop].mObject
                    );

                if(0 > printRes)
                {
                    retval = __LINE__;
                    ERROR_REPORT(retval, inOptions->mOutputName, "Unable to write to file.");
                }
            }
        }
    }

    return retval;
}


int dump2symdb(Options* inOptions)
/*
**  Convert the input into the output, respecting the options.
**  Returns 0 on success.
*/
{
    int retval = 0;
    char lineBuffer[0x800];
    MSDump_Container container;

    memset(&container, 0, sizeof(container));

    /*
    **  Read the file line by line.
    */
    while(0 == retval && NULL != fgets(lineBuffer, sizeof(lineBuffer), inOptions->mInput))
    {
        if(0 != container.mReadState.mSkipLines)
        {
            container.mReadState.mSkipLines--;
            continue;
        }
        retval = processLine(inOptions, &container, lineBuffer);
    }

    /*
    **  Perform whatever calculations desired.
    */
    if(0 == retval)
    {
        retval = calcContainer(inOptions, &container);
    }

    /*
    **  Output what we know.
    */
    if(0 == retval)
    {
        retval = reportContainer(inOptions, &container);
    }

    /*
    **  Cleanup what we've done.
    */
    dumpCleanup(&container);

    return retval;
}


int initOptions(Options* outOptions, int inArgc, char** inArgv)
/*
**  returns int     0 if successful.
*/
{
    int retval = 0;
    int loop = 0;
    int switchLoop = 0;
    int match = 0;
    const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
    Switch* current = NULL;

    /*
    **  Set any defaults.
    */
    memset(outOptions, 0, sizeof(Options));
    outOptions->mProgramName = inArgv[0];
    outOptions->mInput = stdin;
    outOptions->mInputName = strdup("stdin");
    outOptions->mOutput = stdout;
    outOptions->mOutputName = strdup("stdout");

    if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName)
    {
        retval = __LINE__;
        ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup.");
    }

    /*
    **  Go through and attempt to do the right thing.
    */
    for(loop = 1; loop < inArgc && 0 == retval; loop++)
    {
        match = 0;
        current = NULL;

        for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++)
        {
            if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop]))
            {
                match = __LINE__;
            }
            else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop]))
            {
                match = __LINE__;
            }

            if(match)
            {
                if(gSwitches[switchLoop]->mHasValue)
                {
                    /*
                    **  Attempt to absorb next option to fullfill value.
                    */
                    if(loop + 1 < inArgc)
                    {
                        loop++;

                        current = gSwitches[switchLoop];
                        current->mValue = inArgv[loop];
                    }
                }
                else
                {
                    current = gSwitches[switchLoop];
                }

                break;
            }
        }

        if(0 == match)
        {
            outOptions->mHelp = __LINE__;
            retval = __LINE__;
            ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch.");
        }
        else if(NULL == current)
        {
            outOptions->mHelp = __LINE__;
            retval = __LINE__;
            ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value.");
        }
        else
        {
            /*
            ** Do something based on address/swtich.
            */
            if(current == &gInputSwitch)
            {
                CLEANUP(outOptions->mInputName);
                if(NULL != outOptions->mInput && stdin != outOptions->mInput)
                {
                    fclose(outOptions->mInput);
                    outOptions->mInput = NULL;
                }

                outOptions->mInput = fopen(current->mValue, "r");
                if(NULL == outOptions->mInput)
                {
                    retval = __LINE__;
                    ERROR_REPORT(retval, current->mValue, "Unable to open input file.");
                }
                else
                {
                    outOptions->mInputName = strdup(current->mValue);
                    if(NULL == outOptions->mInputName)
                    {
                        retval = __LINE__;
                        ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
                    }
                }
            }
            else if(current == &gOutputSwitch)
            {
                CLEANUP(outOptions->mOutputName);
                if(NULL != outOptions->mOutput && stdout != outOptions->mOutput)
                {
                    fclose(outOptions->mOutput);
                    outOptions->mOutput = NULL;
                }

                outOptions->mOutput = fopen(current->mValue, "a");
                if(NULL == outOptions->mOutput)
                {
                    retval = __LINE__;
                    ERROR_REPORT(retval, current->mValue, "Unable to open output file.");
                }
                else
                {
                    outOptions->mOutputName = strdup(current->mValue);
                    if(NULL == outOptions->mOutputName)
                    {
                        retval = __LINE__;
                        ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
                    }
                }
            }
            else if(current == &gHelpSwitch)
            {
                outOptions->mHelp = __LINE__;
            }
            else
            {
                retval = __LINE__;
                ERROR_REPORT(retval, current->mLongName, "No handler for command line switch.");
            }
        }
    }

    return retval;
}


void cleanOptions(Options* inOptions)
/*
**  Clean up any open handles.
*/
{
    CLEANUP(inOptions->mInputName);
    if(NULL != inOptions->mInput && stdin != inOptions->mInput)
    {
        fclose(inOptions->mInput);
    }
    CLEANUP(inOptions->mOutputName);
    if(NULL != inOptions->mOutput && stdout != inOptions->mOutput)
    {
        fclose(inOptions->mOutput);
    }

    memset(inOptions, 0, sizeof(Options));
}


void showHelp(Options* inOptions)
/*
**  Show some simple help text on usage.
*/
{
    int loop = 0;
    const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
    const char* valueText = NULL;

    printf("usage:\t%s [arguments]\n", inOptions->mProgramName);
    printf("\n");
    printf("arguments:\n");

    for(loop = 0; loop < switchCount; loop++)
    {
        if(gSwitches[loop]->mHasValue)
        {
            valueText = " <value>";
        }
        else
        {
            valueText = "";
        }

        printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText);
        printf("\t %s%s", gSwitches[loop]->mShortName, valueText);
        printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription);
    }

    printf("This tool takes the output of \"dumpbin /symbols\" to produce a simple\n");
    printf("tsv db file of symbols and their respective attributes, like size.\n");
}


int main(int inArgc, char** inArgv)
{
    int retval = 0;
    Options options;

    retval = initOptions(&options, inArgc, inArgv);
    if(options.mHelp)
    {
        showHelp(&options);
    }
    else if(0 == retval)
    {
        retval = dump2symdb(&options);
    }

    cleanOptions(&options);
    return retval;
}