/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * 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 maptsvdifftool.c code, released * Oct 3, 2002. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 2002 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Garrett Arch Blythe, 03-October-2002 * * Alternatively, the contents of this file may be used under the * terms of the GNU Public License (the "GPL"), in which case the * provisions of the GPL are applicable instead of those above. * If you wish to allow use of your version of this file only * under the terms of the GPL and not to allow others to use your * version of this file under the MPL, indicate your decision by * deleting the provisions above and replace them with the notice * and other provisions required by the GPL. If you do not delete * the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. */ #include #include #include #include #include #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 Wether or not help should be shown. ** mSummaryOnly Only output a signle line. ** mZeroDrift Output zero drift data. */ { const char* mProgramName; FILE* mInput; char* mInputName; FILE* mOutput; char* mOutputName; int mHelp; int mSummaryOnly; int mZeroDrift; } 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 gSummarySwitch = {"--summary", "-s", 0, NULL, "Only output a single line." DESC_NEWLINE "The cumulative size changes." DESC_NEWLINE "Overrides all other output options."}; static Switch gZeroDriftSwitch = {"--zerodrift", "-z", 0, NULL, "Output zero drift data." DESC_NEWLINE "Zero drift data includes all changes, even if they cancel out."}; static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."}; static Switch* gSwitches[] = { &gInputSwitch, &gOutputSwitch, &gSummarySwitch, &gZeroDriftSwitch, &gHelpSwitch }; typedef struct __struct_SizeComposition /* ** Used to keep which parts positive and negative resulted in the total. */ { int mPositive; int mNegative; } SizeComposition; typedef struct __struct_SizeStats /* ** Keep track of sizes. ** Use signed integers so that negatives are valid, in which case we shrunk. */ { int mCode; SizeComposition mCodeComposition; int mData; SizeComposition mDataComposition; } SizeStats; typedef enum __enum_SegmentClass /* ** What type of data a segment holds. */ { CODE, DATA } SegmentClass; typedef struct __struct_SymbolStats /* ** Symbol level stats. */ { char* mSymbol; int mSize; } SymbolStats; typedef struct __struct_ObjectStats /* ** Object level stats. */ { char* mObject; int mSize; SizeComposition mComposition; SymbolStats* mSymbols; unsigned mSymbolCount; } ObjectStats; typedef struct __struct_SegmentStats /* ** Segment level stats. */ { char* mSegment; SegmentClass mClass; int mSize; SizeComposition mComposition; ObjectStats* mObjects; unsigned mObjectCount; } SegmentStats; typedef struct __struct_ModuleStats /* ** Module level stats. */ { char* mModule; SizeStats mSize; SegmentStats* mSegments; unsigned mSegmentCount; } ModuleStats; static int moduleCompare(const void* in1, const void* in2) /* ** qsort helper. */ { int retval = 0; ModuleStats* one = (ModuleStats*)in1; ModuleStats* two = (ModuleStats*)in2; int oneSize = (one->mSize.mCode + one->mSize.mData); int twoSize = (two->mSize.mCode + two->mSize.mData); if(oneSize < twoSize) { retval = 1; } else if(oneSize > twoSize) { retval = -1; } return retval; } static int segmentCompare(const void* in1, const void* in2) /* ** qsort helper. */ { int retval = 0; SegmentStats* one = (SegmentStats*)in1; SegmentStats* two = (SegmentStats*)in2; if(one->mSize < two->mSize) { retval = 1; } else if(one->mSize > two->mSize) { retval = -1; } return retval; } static int objectCompare(const void* in1, const void* in2) /* ** qsort helper. */ { int retval = 0; ObjectStats* one = (ObjectStats*)in1; ObjectStats* two = (ObjectStats*)in2; if(one->mSize < two->mSize) { retval = 1; } else if(one->mSize > two->mSize) { retval = -1; } return retval; } static int symbolCompare(const void* in1, const void* in2) /* ** qsort helper. */ { int retval = 0; SymbolStats* one = (SymbolStats*)in1; SymbolStats* two = (SymbolStats*)in2; if(one->mSize < two->mSize) { retval = 1; } else if(one->mSize > two->mSize) { retval = -1; } return retval; } 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; } } } int difftool(Options* inOptions) /* ** Read a diff file and spit out relevant information. */ { int retval = 0; char lineBuffer[0x500]; SizeStats overall; ModuleStats* modules = NULL; unsigned moduleCount = 0; unsigned moduleLoop = 0; ModuleStats* theModule = NULL; unsigned segmentLoop = 0; SegmentStats* theSegment = NULL; unsigned objectLoop = 0; ObjectStats* theObject = NULL; unsigned symbolLoop = 0; SymbolStats* theSymbol = NULL; memset(&overall, 0, sizeof(overall)); /* ** Read the entire diff file. ** We're only interested in lines beginning with < or > */ while(0 == retval && NULL != fgets(lineBuffer, sizeof(lineBuffer), inOptions->mInput)) { trimWhite(lineBuffer); if(('<' == lineBuffer[0] || '>' == lineBuffer[0]) && ' ' == lineBuffer[1]) { int additive = 0; char* theLine = &lineBuffer[2]; int scanRes = 0; int size; char segClass[0x10]; char scope[0x10]; char module[0x100]; char segment[0x40]; char object[0x100]; char* symbol = NULL; /* ** Figure out if the line adds or subtracts from something. */ if('>' == lineBuffer[0]) { additive = __LINE__; } /* ** Scan the line for information. */ scanRes = sscanf(theLine, "%x\t%s\t%s\t%s\t%s\t%s\t", (unsigned*)&size, segClass, scope, module, segment, object); if(6 == scanRes) { SegmentClass segmentClass = DATA; symbol = strrchr(theLine, '\t') + 1; if(0 == strcmp(segClass, "CODE")) { segmentClass = CODE; } else if(0 == strcmp(segClass, "DATA")) { segmentClass = DATA; } else { retval = __LINE__; ERROR_REPORT(retval, segClass, "Unable to determine segment class."); } if(0 == retval) { unsigned moduleIndex = 0; /* ** Find, in succession, the following things: ** the module ** the segment ** the object ** the symbol ** Failure to find any one of these means to create it. */ for(moduleIndex = 0; moduleIndex < moduleCount; moduleIndex++) { if(0 == strcmp(modules[moduleIndex].mModule, module)) { break; } } if(moduleIndex == moduleCount) { void* moved = NULL; moved = realloc(modules, sizeof(ModuleStats) * (1 + moduleCount)); if(NULL != moved) { modules = (ModuleStats*)moved; moduleCount++; memset(modules + moduleIndex, 0, sizeof(ModuleStats)); modules[moduleIndex].mModule = strdup(module); if(NULL == modules[moduleIndex].mModule) { retval = __LINE__; ERROR_REPORT(retval, module, "Unable to duplicate string."); } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase module array."); } } if(0 == retval) { unsigned segmentIndex = 0; theModule = (modules + moduleIndex); for(segmentIndex = 0; segmentIndex < theModule->mSegmentCount; segmentIndex++) { if(0 == strcmp(segment, theModule->mSegments[segmentIndex].mSegment)) { break; } } if(segmentIndex == theModule->mSegmentCount) { void* moved = NULL; moved = realloc(theModule->mSegments, sizeof(SegmentStats) * (theModule->mSegmentCount + 1)); if(NULL != moved) { theModule->mSegments = (SegmentStats*)moved; theModule->mSegmentCount++; memset(theModule->mSegments + segmentIndex, 0, sizeof(SegmentStats)); theModule->mSegments[segmentIndex].mClass = segmentClass; theModule->mSegments[segmentIndex].mSegment = strdup(segment); if(NULL == theModule->mSegments[segmentIndex].mSegment) { retval = __LINE__; ERROR_REPORT(retval, segment, "Unable to duplicate string."); } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase segment array."); } } if(0 == retval) { unsigned objectIndex = 0; theSegment = (theModule->mSegments + segmentIndex); for(objectIndex = 0; objectIndex < theSegment->mObjectCount; objectIndex++) { if(0 == strcmp(object, theSegment->mObjects[objectIndex].mObject)) { break; } } if(objectIndex == theSegment->mObjectCount) { void* moved = NULL; moved = realloc(theSegment->mObjects, sizeof(ObjectStats) * (1 + theSegment->mObjectCount)); if(NULL != moved) { theSegment->mObjects = (ObjectStats*)moved; theSegment->mObjectCount++; memset(theSegment->mObjects + objectIndex, 0, sizeof(ObjectStats)); theSegment->mObjects[objectIndex].mObject = strdup(object); if(NULL == theSegment->mObjects[objectIndex].mObject) { retval = __LINE__; ERROR_REPORT(retval, object, "Unable to duplicate string."); } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase object array."); } } if(0 == retval) { unsigned symbolIndex = 0; theObject = (theSegment->mObjects + objectIndex); for(symbolIndex = 0; symbolIndex < theObject->mSymbolCount; symbolIndex++) { if(0 == strcmp(symbol, theObject->mSymbols[symbolIndex].mSymbol)) { break; } } if(symbolIndex == theObject->mSymbolCount) { void* moved = NULL; moved = realloc(theObject->mSymbols, sizeof(SymbolStats) * (1 + theObject->mSymbolCount)); if(NULL != moved) { theObject->mSymbols = (SymbolStats*)moved; theObject->mSymbolCount++; memset(theObject->mSymbols + symbolIndex, 0, sizeof(SymbolStats)); theObject->mSymbols[symbolIndex].mSymbol = strdup(symbol); if(NULL == theObject->mSymbols[symbolIndex].mSymbol) { retval = __LINE__; ERROR_REPORT(retval, symbol, "Unable to duplicate string."); } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase symbol array."); } } if(0 == retval) { theSymbol = (theObject->mSymbols + symbolIndex); /* ** Update our various totals. */ if(additive) { if(CODE == segmentClass) { overall.mCode += size; theModule->mSize.mCode += size; } else if(DATA == segmentClass) { overall.mData += size; theModule->mSize.mData += size; } theSegment->mSize += size; theObject->mSize += size; theSymbol->mSize += size; } else { if(CODE == segmentClass) { overall.mCode -= size; theModule->mSize.mCode -= size; } else if(DATA == segmentClass) { overall.mData -= size; theModule->mSize.mData -= size; } theSegment->mSize -= size; theObject->mSize -= size; theSymbol->mSize -= size; } } } } } } } else { retval = __LINE__; ERROR_REPORT(retval, inOptions->mInputName, "Unable to scan line data."); } } } if(0 == retval && 0 != ferror(inOptions->mInput)) { retval = __LINE__; ERROR_REPORT(retval, inOptions->mInputName, "Unable to read file."); } /* ** If all went well, time to report. */ if(0 == retval) { /* ** Loop through our data once more, so that the symbols can ** propigate their changes upwards in a positive/negative ** fashion. ** This will help give the composite change more meaning. */ for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) { theModule = modules + moduleLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == (theModule->mSize.mCode + theModule->mSize.mData)) { continue; } for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) { theSegment = theModule->mSegments + segmentLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == theSegment->mSize) { continue; } for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) { theObject = theSegment->mObjects + objectLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == theObject->mSize) { continue; } for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) { theSymbol = theObject->mSymbols + symbolLoop; /* ** Propogate the composition all the way to the top. ** Sizes of zero change are skipped. */ if(0 < theSymbol->mSize) { theObject->mComposition.mPositive += theSymbol->mSize; theSegment->mComposition.mPositive += theSymbol->mSize; if(CODE == theSegment->mClass) { overall.mCodeComposition.mPositive += theSymbol->mSize; theModule->mSize.mCodeComposition.mPositive += theSymbol->mSize; } else if(DATA == theSegment->mClass) { overall.mDataComposition.mPositive += theSymbol->mSize; theModule->mSize.mDataComposition.mPositive += theSymbol->mSize; } } else if(0 > theSymbol->mSize) { theObject->mComposition.mNegative += theSymbol->mSize; theSegment->mComposition.mNegative += theSymbol->mSize; if(CODE == theSegment->mClass) { overall.mCodeComposition.mNegative += theSymbol->mSize; theModule->mSize.mCodeComposition.mNegative += theSymbol->mSize; } else if(DATA == theSegment->mClass) { overall.mDataComposition.mNegative += theSymbol->mSize; theModule->mSize.mDataComposition.mNegative += theSymbol->mSize; } } } } } } if(inOptions->mSummaryOnly) { fprintf(inOptions->mOutput, "%+d (%+d/%+d)\n", overall.mCode + overall.mData, overall.mCodeComposition.mPositive + overall.mDataComposition.mPositive, overall.mCodeComposition.mNegative + overall.mDataComposition.mNegative); } else { fprintf(inOptions->mOutput, "Overall Change in Size\n"); fprintf(inOptions->mOutput, "\tTotal:\t%+11d (%+d/%+d)\n", overall.mCode + overall.mData, overall.mCodeComposition.mPositive + overall.mDataComposition.mPositive, overall.mCodeComposition.mNegative + overall.mDataComposition.mNegative); fprintf(inOptions->mOutput, "\tCode:\t%+11d (%+d/%+d)\n", overall.mCode, overall.mCodeComposition.mPositive, overall.mCodeComposition.mNegative); fprintf(inOptions->mOutput, "\tData:\t%+11d (%+d/%+d)\n", overall.mData, overall.mDataComposition.mPositive, overall.mDataComposition.mNegative); } /* ** Check what else we should output. */ if(0 == inOptions->mSummaryOnly && NULL != modules && moduleCount) { const char* segmentType = NULL; /* ** We're going to sort everything. */ qsort(modules, moduleCount, sizeof(ModuleStats), moduleCompare); for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) { theModule = modules + moduleLoop; qsort(theModule->mSegments, theModule->mSegmentCount, sizeof(SegmentStats), segmentCompare); for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) { theSegment = theModule->mSegments + segmentLoop; qsort(theSegment->mObjects, theSegment->mObjectCount, sizeof(ObjectStats), objectCompare); for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) { theObject = theSegment->mObjects + objectLoop; qsort(theObject->mSymbols, theObject->mSymbolCount, sizeof(SymbolStats), symbolCompare); } } } /* ** Loop through for output. */ for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) { theModule = modules + moduleLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == (theModule->mSize.mCode + theModule->mSize.mData)) { continue; } fprintf(inOptions->mOutput, "\n"); fprintf(inOptions->mOutput, "%s\n", theModule->mModule); fprintf(inOptions->mOutput, "\tTotal:\t%+11d (%+d/%+d)\n", theModule->mSize.mCode + theModule->mSize.mData, theModule->mSize.mCodeComposition.mPositive + theModule->mSize.mDataComposition.mPositive, theModule->mSize.mCodeComposition.mNegative + theModule->mSize.mDataComposition.mNegative); fprintf(inOptions->mOutput, "\tCode:\t%+11d (%+d/%+d)\n", theModule->mSize.mCode, theModule->mSize.mCodeComposition.mPositive, theModule->mSize.mCodeComposition.mNegative); fprintf(inOptions->mOutput, "\tData:\t%+11d (%+d/%+d)\n", theModule->mSize.mData, theModule->mSize.mDataComposition.mPositive, theModule->mSize.mDataComposition.mNegative); for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) { theSegment = theModule->mSegments + segmentLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == theSegment->mSize) { continue; } if(CODE == theSegment->mClass) { segmentType = "CODE"; } else if(DATA == theSegment->mClass) { segmentType = "DATA"; } fprintf(inOptions->mOutput, "\t%+11d (%+d/%+d)\t%s (%s)\n", theSegment->mSize, theSegment->mComposition.mPositive, theSegment->mComposition.mNegative, theSegment->mSegment, segmentType); for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) { theObject = theSegment->mObjects + objectLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == theObject->mSize) { continue; } fprintf(inOptions->mOutput, "\t\t%+11d (%+d/%+d)\t%s\n", theObject->mSize, theObject->mComposition.mPositive, theObject->mComposition.mNegative, theObject->mObject); for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) { theSymbol = theObject->mSymbols + symbolLoop; /* ** Skip if there is zero drift, or no net change. */ if(0 == inOptions->mZeroDrift && 0 == theSymbol->mSize) { continue; } fprintf(inOptions->mOutput, "\t\t\t%+11d\t%s\n", theSymbol->mSize, theSymbol->mSymbol); } } } } } } /* ** Cleanup time. */ for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) { theModule = modules + moduleLoop; for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) { theSegment = theModule->mSegments + segmentLoop; for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) { theObject = theSegment->mObjects + objectLoop; for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) { theSymbol = theObject->mSymbols + symbolLoop; CLEANUP(theSymbol->mSymbol); } CLEANUP(theObject->mSymbols); CLEANUP(theObject->mObject); } CLEANUP(theSegment->mObjects); CLEANUP(theSegment->mSegment); } CLEANUP(theModule->mSegments); CLEANUP(theModule->mModule); } CLEANUP(modules); 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 if(current == &gSummarySwitch) { outOptions->mSummaryOnly = __LINE__; } else if(current == &gZeroDriftSwitch) { outOptions->mZeroDrift = __LINE__; } else { retval = __LINE__; ERROR_REPORT(retval, current->mLongName, "No hanlder 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 = " "; } 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 diff of two sorted tsv files to form a summary report\n"); printf("of code and data size changes which is hoped to be human readable.\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 = difftool(&options); } cleanOptions(&options); return retval; }