mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1418 lines
41 KiB
1418 lines
41 KiB
/*++
|
|
|
|
Copyright (c) 1991-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Comp.cxx
|
|
|
|
Abstract:
|
|
|
|
Compares the contents of two files or sets of files.
|
|
|
|
COMP [data1] [data2] [/D] [/A] [/L] [/N=number] [/C]
|
|
|
|
data1 Specifies location and name(s) of first file(s) to compare.
|
|
data2 Specifies location and name(s) of second files to compare.
|
|
/D Displays differences in decimal format. This is the default
|
|
setting.
|
|
/A Displays differences in ASCII characters.
|
|
/L Displays line numbers for differences.
|
|
/N=number Compares only the first specified number of lines in each file.
|
|
/C Disregards case of ASCII letters when comparing files.
|
|
/OFFLINE Do not skip files with offline attribute set.
|
|
|
|
To compare sets of files, use wildcards in data1 and data2 parameters.
|
|
|
|
Author:
|
|
|
|
Barry J. Gilhuly *** W-Barry *** Jun 91
|
|
|
|
Environment:
|
|
|
|
ULIB, User Mode
|
|
|
|
--*/
|
|
|
|
#include "ulib.hxx"
|
|
#include "ulibcl.hxx"
|
|
#include "arg.hxx"
|
|
#include "array.hxx"
|
|
#include "bytestrm.hxx"
|
|
#include "dir.hxx"
|
|
#include "file.hxx"
|
|
#include "filestrm.hxx"
|
|
#include "filter.hxx"
|
|
#include "iterator.hxx"
|
|
#include "path.hxx"
|
|
#include "rtmsg.h"
|
|
#include "system.hxx"
|
|
#include "smsg.hxx"
|
|
#include "comp.hxx"
|
|
|
|
|
|
extern "C" {
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
}
|
|
|
|
|
|
STREAM_MESSAGE *psmsg = NULL; // Create a pointer to the stream message
|
|
// class for program output.
|
|
ULONG Errlev; // The current program error level
|
|
ULONG CompResult;
|
|
|
|
|
|
|
|
//
|
|
// Define a macro to deal with case insensitive comparisons
|
|
//
|
|
#define CASE_SENSITIVE( x ) ( ( _CaseInsensitive ) ? towupper( x ) : x )
|
|
|
|
VOID
|
|
StripQuotesFromString(
|
|
IN PWSTRING String
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine removes leading and trailing quote marks (if
|
|
present) from a quoted string. If the string is not a quoted
|
|
string, it is left unchanged.
|
|
|
|
--*/
|
|
{
|
|
if( String->QueryChCount() >= 2 &&
|
|
String->QueryChAt( 0 ) == '\"' &&
|
|
String->QueryChAt( String->QueryChCount() - 1 ) == '\"' ) {
|
|
|
|
String->DeleteChAt( String->QueryChCount() - 1 );
|
|
String->DeleteChAt( 0 );
|
|
}
|
|
}
|
|
|
|
DEFINE_CONSTRUCTOR( COMP, PROGRAM );
|
|
|
|
VOID
|
|
COMP::Construct(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the object.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
_InputPath1 = NULL;
|
|
_InputPath2 = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
COMP::Destruct(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up after finishing with an FC object.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
DELETE( psmsg );
|
|
if( _InputPath1 != NULL ) {
|
|
DELETE( _InputPath1 );
|
|
}
|
|
if( _InputPath2 != NULL ) {
|
|
DELETE( _InputPath2 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
BOOLEAN
|
|
COMP::Initialize(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes an FC object.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - Indicates if the initialization succeeded.
|
|
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
ARGUMENT_LEXEMIZER ArgLex;
|
|
ARRAY LexArray;
|
|
ARRAY ArrayOfArg;
|
|
PATH_ARGUMENT ProgramName;
|
|
FLAG_ARGUMENT FlagDecimalFormat;
|
|
FLAG_ARGUMENT FlagAsciiFormat;
|
|
FLAG_ARGUMENT FlagLineNumbers;
|
|
FLAG_ARGUMENT FlagCaseInsensitive;
|
|
FLAG_ARGUMENT FlagRequestHelp;
|
|
FLAG_ARGUMENT FlagWrongNumber;
|
|
FLAG_ARGUMENT FlagIncludeOffline;
|
|
FLAG_ARGUMENT FlagIncludeOffline2;
|
|
LONG_ARGUMENT LongMatchLines;
|
|
PATH_ARGUMENT InFile1;
|
|
PATH_ARGUMENT InFile2;
|
|
STRING_ARGUMENT StringInvalidSwitch;
|
|
WCHAR WChar;
|
|
DSTRING InvalidString;
|
|
|
|
_Numbered = FALSE;
|
|
_Limited = FALSE;
|
|
_InputPath1 = NULL;
|
|
_InputPath2 = NULL;
|
|
if( !LexArray.Initialize() ) {
|
|
DebugPrintTrace(( "LexArray.Initialize() Failed!\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
if( !ArgLex.Initialize(&LexArray) ) {
|
|
DebugPrintTrace(( "ArgLex.Initialize() Failed!\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
|
|
ArgLex.PutSwitches("/");
|
|
ArgLex.SetCaseSensitive( FALSE );
|
|
ArgLex.PutMultipleSwitch( "acdl" );
|
|
ArgLex.PutSeparators( " /\t" );
|
|
ArgLex.PutStartQuotes( "\"" );
|
|
ArgLex.PutEndQuotes( "\"" );
|
|
|
|
if( !ArgLex.PrepareToParse() ) {
|
|
DebugPrintTrace(( "ArgLex.PrepareToParse() Failed!\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
|
|
if( !ProgramName.Initialize("*") ||
|
|
!FlagDecimalFormat.Initialize("/D") ||
|
|
!FlagAsciiFormat.Initialize("/A") ||
|
|
!FlagLineNumbers.Initialize("/L") ||
|
|
!FlagCaseInsensitive.Initialize("/C") ||
|
|
!FlagIncludeOffline.Initialize("/OFFLINE") ||
|
|
!FlagIncludeOffline2.Initialize("/OFF") ||
|
|
!FlagWrongNumber.Initialize("/N") ||
|
|
!LongMatchLines.Initialize("/N=*") ||
|
|
!FlagRequestHelp.Initialize("/?") ||
|
|
!StringInvalidSwitch.Initialize("/*") ||
|
|
!InFile1.Initialize("*") ||
|
|
!InFile2.Initialize("*") ) {
|
|
|
|
DebugPrintTrace(( "Unable to Initialize some or all of the Arguments!\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
if( !ArrayOfArg.Initialize() ) {
|
|
DebugPrintTrace(( "ArrayOfArg.Initialize() Failed\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
|
|
if( !ArrayOfArg.Put(&ProgramName) ||
|
|
!ArrayOfArg.Put(&FlagDecimalFormat) ||
|
|
!ArrayOfArg.Put(&FlagAsciiFormat) ||
|
|
!ArrayOfArg.Put(&FlagLineNumbers) ||
|
|
!ArrayOfArg.Put(&FlagCaseInsensitive) ||
|
|
!ArrayOfArg.Put(&FlagIncludeOffline) ||
|
|
!ArrayOfArg.Put(&FlagIncludeOffline2) ||
|
|
!ArrayOfArg.Put(&FlagWrongNumber) ||
|
|
!ArrayOfArg.Put(&LongMatchLines) ||
|
|
!ArrayOfArg.Put(&FlagRequestHelp) ||
|
|
!ArrayOfArg.Put(&StringInvalidSwitch) ||
|
|
!ArrayOfArg.Put(&InFile1) ||
|
|
!ArrayOfArg.Put(&InFile2) ) {
|
|
|
|
DebugPrintTrace(( "ArrayOfArg.Put() Failed!\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
|
|
}
|
|
|
|
|
|
if( !ArgLex.DoParsing( &ArrayOfArg ) ||
|
|
StringInvalidSwitch.IsValueSet() ) {
|
|
|
|
if( StringInvalidSwitch.IsValueSet() ) {
|
|
//
|
|
// An invalid switch was found...
|
|
//
|
|
// InvalidString.Initialize( "/" );
|
|
// InvalidString.Strcat( StringInvalidSwitch.GetString() );
|
|
InvalidString.Initialize( "" );
|
|
InvalidString.Strcat( StringInvalidSwitch.GetLexeme() );
|
|
Errlev = INV_SWITCH;
|
|
psmsg->Set( MSG_COMP_INVALID_SWITCH );
|
|
psmsg->Display( "%W", &InvalidString );
|
|
} else {
|
|
psmsg->Set( MSG_COMP_BAD_COMMAND_LINE );
|
|
psmsg->Display( "" );
|
|
Errlev = SYNT_ERR;
|
|
}
|
|
return( FALSE );
|
|
}
|
|
|
|
if( FlagWrongNumber.IsValueSet() ) {
|
|
psmsg->Set( MSG_COMP_NUMERIC_FORMAT );
|
|
psmsg->Display( "" );
|
|
}
|
|
|
|
// It should now be safe to test the arguments for their values...
|
|
if( FlagRequestHelp.QueryFlag() ) {
|
|
|
|
// Send help message
|
|
psmsg->Set( MSG_COMP_HELP_MESSAGE );
|
|
psmsg->Display( "" );
|
|
return( FALSE );
|
|
}
|
|
|
|
if( InFile1.IsValueSet() ) {
|
|
StripQuotesFromString( (PWSTRING)InFile1.GetPath()->GetPathString() );
|
|
if( ( _InputPath1 = NEW PATH ) == NULL ) {
|
|
psmsg->Set( MSG_COMP_NO_MEMORY );
|
|
psmsg->Display( "" );
|
|
Errlev = NO_MEM_AVAIL;
|
|
return( FALSE );
|
|
}
|
|
if( !_InputPath1->Initialize( InFile1.GetPath(), FALSE ) ) {
|
|
DebugAbort( "Failed to initialize canonicolized version of the path 1\n" );
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
} else {
|
|
_InputPath1 = NULL;
|
|
}
|
|
|
|
if( InFile2.IsValueSet() ) {
|
|
StripQuotesFromString( (PWSTRING)InFile2.GetPath()->GetPathString() );
|
|
if( ( _InputPath2 = NEW PATH ) == NULL ) {
|
|
Errlev = NO_MEM_AVAIL;
|
|
psmsg->Set( MSG_COMP_NO_MEMORY );
|
|
psmsg->Display( "" );
|
|
return( FALSE );
|
|
}
|
|
if( !_InputPath2->Initialize( InFile2.GetPath(), FALSE ) ) {
|
|
DebugAbort( "Failed to initialize canonicolized version of the path 2\n" );
|
|
Errlev = INTERNAL_ERROR;
|
|
return( FALSE );
|
|
}
|
|
} else {
|
|
_InputPath2 = NULL;
|
|
}
|
|
|
|
//
|
|
// Set the output mode...
|
|
//
|
|
if( FlagAsciiFormat.QueryFlag() ) {
|
|
_Mode = OUTPUT_ASCII;
|
|
} else if( FlagDecimalFormat.QueryFlag() ) {
|
|
_Mode = OUTPUT_DECIMAL;
|
|
} else {
|
|
_Mode = OUTPUT_HEX;
|
|
}
|
|
|
|
//
|
|
// Set the remaining flags...
|
|
//
|
|
if( LongMatchLines.IsValueSet() ) {
|
|
if( ( ( WChar = ( LongMatchLines.GetLexeme()->QueryChAt( 3 ) ) ) == '+' ) ||
|
|
WChar == '-' ||
|
|
( _NumberOfLines = LongMatchLines.QueryLong() ) < 0 ) {
|
|
Errlev = INV_SWITCH;
|
|
psmsg->Set( MSG_COMP_BAD_NUMERIC_ARG );
|
|
psmsg->Display( "%W", LongMatchLines.GetLexeme() );
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( _NumberOfLines != 0 ) {
|
|
_Numbered = TRUE;
|
|
_Limited = TRUE;
|
|
}
|
|
|
|
} else {
|
|
_Numbered = FlagLineNumbers.QueryFlag();
|
|
_Limited = FALSE;
|
|
}
|
|
_CaseInsensitive = FlagCaseInsensitive.QueryFlag();
|
|
|
|
_SkipOffline = ( !FlagIncludeOffline.QueryFlag() ) &&
|
|
( !FlagIncludeOffline2.QueryFlag() );
|
|
|
|
if( FlagDecimalFormat.IsValueSet() ||
|
|
FlagAsciiFormat.IsValueSet() ||
|
|
FlagLineNumbers.IsValueSet() ||
|
|
FlagCaseInsensitive.IsValueSet() ||
|
|
FlagIncludeOffline.IsValueSet() ||
|
|
FlagIncludeOffline2.IsValueSet() ||
|
|
LongMatchLines.IsValueSet() ||
|
|
( InFile1.IsValueSet() &&
|
|
InFile2.IsValueSet() ) ) {
|
|
_OptionsFound = TRUE;
|
|
} else {
|
|
_OptionsFound = FALSE;
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
VOID
|
|
COMP::Start(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Query missing information from the user and start the comparison
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
DSTRING UserInput;
|
|
PWSTRING InvalidSwitch;
|
|
USHORT OptionCount;
|
|
LONG Number;
|
|
|
|
for( ;; ) {
|
|
|
|
if( _InputPath1 == NULL ) {
|
|
|
|
// Query a path for file 1...
|
|
psmsg->Set( MSG_COMP_QUERY_FILE1, ERROR_MESSAGE );
|
|
psmsg->Display( "" );
|
|
if( !psmsg->QueryStringInput( &UserInput ) ) {
|
|
psmsg->Set( MSG_COMP_UNEXPECTED_END );
|
|
psmsg->Display( "" );
|
|
Errlev = UNEXP_EOF;
|
|
return;
|
|
}
|
|
if( ( _InputPath1 = NEW PATH ) == NULL ) {
|
|
psmsg->Set( MSG_COMP_NO_MEMORY );
|
|
psmsg->Display( "" );
|
|
Errlev = NO_MEM_AVAIL;
|
|
return;
|
|
}
|
|
if( !_InputPath1->Initialize( &UserInput, FALSE ) ) {
|
|
DebugPrintTrace(( "Unable to initialize the path for file 1\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
if( _InputPath2 == NULL ) {
|
|
|
|
// Query a path for file 2...
|
|
psmsg->Set( MSG_COMP_QUERY_FILE2, ERROR_MESSAGE );
|
|
psmsg->Display( "" );
|
|
if( !psmsg->QueryStringInput( &UserInput ) ) {
|
|
psmsg->Set( MSG_COMP_UNEXPECTED_END );
|
|
psmsg->Display( "" );
|
|
Errlev = UNEXP_EOF;
|
|
return;
|
|
}
|
|
if( ( _InputPath2 = NEW PATH ) == NULL ) {
|
|
psmsg->Set( MSG_COMP_NO_MEMORY );
|
|
psmsg->Display( "" );
|
|
Errlev = NO_MEM_AVAIL;
|
|
return;
|
|
}
|
|
if( !_InputPath2->Initialize( &UserInput, FALSE ) ) {
|
|
DebugPrintTrace(( "Unable to initialize the path for file 2\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
if( !_OptionsFound ) {
|
|
|
|
//
|
|
// Query Options from the user...
|
|
//
|
|
DSTRING Options;
|
|
DSTRING Delim;
|
|
CHNUM CurSwitchStart, NextSwitchStart, Len;
|
|
|
|
Delim.Initialize( "/-" );
|
|
// Query a new list of options from the user
|
|
for( OptionCount = 0; OptionCount < 5; OptionCount++ ) {
|
|
psmsg->Set( MSG_COMP_OPTION, ERROR_MESSAGE );
|
|
psmsg->Display( "" );
|
|
if( !psmsg->QueryStringInput( &Options ) ) {
|
|
psmsg->Set( MSG_COMP_UNEXPECTED_END );
|
|
psmsg->Display( "" );
|
|
Errlev = UNEXP_EOF;
|
|
return;
|
|
}
|
|
if( Options.QueryChCount() == 0 ) {
|
|
break;
|
|
}
|
|
|
|
CurSwitchStart = Options.Strcspn( &Delim );
|
|
if( CurSwitchStart != 0 ) {
|
|
psmsg->Set( MSG_COMP_BAD_COMMAND_LINE );
|
|
psmsg->Display( "" );
|
|
Errlev = SYNT_ERR;
|
|
return;
|
|
}
|
|
for( ;; ) {
|
|
|
|
Len = 0;
|
|
CurSwitchStart++;
|
|
NextSwitchStart = Options.Strcspn( &Delim, CurSwitchStart );
|
|
|
|
switch( towupper( Options.QueryChAt( CurSwitchStart ) ) ) {
|
|
case 'A':
|
|
_Mode = OUTPUT_ASCII;
|
|
CurSwitchStart++;
|
|
break;
|
|
case 'D':
|
|
_Mode = OUTPUT_DECIMAL;
|
|
CurSwitchStart++;
|
|
break;
|
|
case 'C':
|
|
_CaseInsensitive = TRUE;
|
|
CurSwitchStart++;
|
|
break;
|
|
case 'L':
|
|
_Numbered = TRUE;
|
|
CurSwitchStart++;
|
|
break;
|
|
case 'O':
|
|
PWSTRING pArg;
|
|
if( NextSwitchStart == INVALID_CHNUM ) {
|
|
pArg = Options.QueryString(CurSwitchStart);
|
|
} else {
|
|
pArg = Options.QueryString(CurSwitchStart, NextSwitchStart-CurSwitchStart);
|
|
}
|
|
if (pArg &&
|
|
(0 == _wcsicmp(pArg->GetWSTR(), L"OFFLINE")) ) {
|
|
_SkipOffline = FALSE;
|
|
CurSwitchStart += wcslen( L"OFFLINE" );
|
|
DELETE( pArg );
|
|
} else if (pArg &&
|
|
(0 == _wcsicmp(pArg->GetWSTR(), L"OFF")) ) {
|
|
_SkipOffline = FALSE;
|
|
CurSwitchStart += wcslen( L"OFF" );
|
|
DELETE( pArg );
|
|
} else {
|
|
InvalidSwitch = Options.QueryString( CurSwitchStart );
|
|
psmsg->Set( MSG_COMP_INVALID_SWITCH );
|
|
psmsg->Display( "%W", InvalidSwitch );
|
|
Errlev = INV_SWITCH;
|
|
DELETE( InvalidSwitch );
|
|
DELETE( pArg );
|
|
|
|
return;
|
|
}
|
|
break;
|
|
case 'N':
|
|
++CurSwitchStart;
|
|
if( Options.QueryChAt( CurSwitchStart ) != '=' ) {
|
|
psmsg->Set( MSG_COMP_NUMERIC_FORMAT );
|
|
psmsg->Display( "" );
|
|
break;
|
|
}
|
|
++CurSwitchStart;
|
|
if( CurSwitchStart == NextSwitchStart ) {
|
|
break;
|
|
}
|
|
if( NextSwitchStart == INVALID_CHNUM ) {
|
|
Len = INVALID_CHNUM;
|
|
} else {
|
|
Len = NextSwitchStart - CurSwitchStart;
|
|
}
|
|
if( !Options.QueryNumber( &Number, CurSwitchStart, Len ) ) {
|
|
InvalidSwitch = Options.QueryString( CurSwitchStart );
|
|
psmsg->Set( MSG_COMP_BAD_NUMERIC_ARG );
|
|
psmsg->Display( "%W", InvalidSwitch );
|
|
Errlev = BAD_NUMERIC_ARG;
|
|
DELETE( InvalidSwitch );
|
|
|
|
return;
|
|
}
|
|
if (Options.QueryNumber( &_NumberOfLines, CurSwitchStart, Len ) ) {
|
|
_Numbered = TRUE;
|
|
_Limited = TRUE;
|
|
}
|
|
CurSwitchStart += Len;
|
|
break;
|
|
default:
|
|
InvalidSwitch = Options.QueryString( CurSwitchStart - 1 );
|
|
psmsg->Set( MSG_COMP_INVALID_SWITCH );
|
|
psmsg->Display( "%W", InvalidSwitch );
|
|
Errlev = INV_SWITCH;
|
|
DELETE( InvalidSwitch );
|
|
|
|
return;
|
|
}
|
|
if( ( CurSwitchStart != NextSwitchStart ) ||
|
|
( Len == INVALID_CHNUM ) ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DoCompare();
|
|
|
|
//
|
|
// Check if there are more files to be compared...
|
|
//
|
|
psmsg->Set( MSG_COMP_MORE, ERROR_MESSAGE );
|
|
psmsg->Display( "" );
|
|
if( !psmsg->IsYesResponse() ) {
|
|
break;
|
|
}
|
|
DELETE( _InputPath1 );
|
|
DELETE( _InputPath2 );
|
|
_InputPath1 = NULL;
|
|
_InputPath2 = NULL;
|
|
_OptionsFound = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
COMP::DoCompare(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Perform the comparison of the files.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
FSN_FILTER Filter;
|
|
PARRAY pNodeArray;
|
|
PITERATOR pIterator;
|
|
PWSTRING pTmp;
|
|
PATH File1Path;
|
|
PFSN_DIRECTORY pDirectory = NULL;
|
|
PATH CanonPath1; // Canonicolized versions of the user paths
|
|
PATH CanonPath2;
|
|
DSTRING WildCardString;
|
|
BOOLEAN PrintSkipWarning = FALSE;
|
|
BOOLEAN OfflineSkipped;
|
|
|
|
//
|
|
// Initialize the wildcard string..
|
|
//
|
|
WildCardString.Initialize( "" );
|
|
SYSTEM::QueryResourceString( &WildCardString, MSG_COMP_WILDCARD_STRING, "" );
|
|
|
|
// Check to see if the input paths are empty.
|
|
|
|
if (_InputPath1->GetPathString()->QueryChCount() == 0) {
|
|
Errlev = CANT_OPEN_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
|
|
psmsg->Display( "%W", _InputPath1->GetPathString() );
|
|
return;
|
|
}
|
|
|
|
if (_InputPath2->GetPathString()->QueryChCount() == 0) {
|
|
Errlev = CANT_OPEN_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
|
|
psmsg->Display( "%W", _InputPath2->GetPathString() );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Test if the input paths contain only a directory name. If it
|
|
// does, append '*.*' to the path so all files in that directory
|
|
// may be compared.
|
|
//
|
|
if( _InputPath1->IsDrive() ||
|
|
( !_InputPath1->HasWildCard() &&
|
|
( pDirectory = SYSTEM::QueryDirectory( _InputPath1 ) ) != NULL ) ) {
|
|
|
|
// The input path corresponds to a directory...
|
|
_InputPath1->AppendBase( &WildCardString );
|
|
|
|
}
|
|
DELETE( pDirectory );
|
|
if( _InputPath2->IsDrive() ||
|
|
( !_InputPath2->HasWildCard() &&
|
|
( pDirectory = SYSTEM::QueryDirectory( _InputPath2 ) ) != NULL ) ) {
|
|
|
|
// The input path corresponds to a directory...
|
|
_InputPath2->AppendBase( &WildCardString );
|
|
|
|
}
|
|
DELETE( pDirectory );
|
|
|
|
//
|
|
// Canonicolize the input paths...
|
|
//
|
|
CanonPath1.Initialize( _InputPath1, TRUE );
|
|
CanonPath2.Initialize( _InputPath2, TRUE );
|
|
|
|
//
|
|
// Test if the first path name contains any wildcards. If it does,
|
|
// the program must initialize an array of FSN_NODES (for multiple
|
|
// files...
|
|
//
|
|
if( CanonPath1.HasWildCard() ) {
|
|
PPATH pTmpPath;
|
|
//
|
|
// Get a directory based on what the user specified for File 1
|
|
//
|
|
if( ( pTmpPath = CanonPath1.QueryFullPath() ) == NULL ) {
|
|
DebugPrintTrace(( "Unable to grab the Prefix from the input path...\n" ));
|
|
Errlev = INTERNAL_ERROR;
|
|
return;
|
|
}
|
|
pTmpPath->TruncateBase();
|
|
if( ( pDirectory = SYSTEM::QueryDirectory( pTmpPath, FALSE ) ) != NULL ) {
|
|
//
|
|
// Create an FSN_FILTER so we can use the directory to create an
|
|
// array of FSN_NODES
|
|
Filter.Initialize();
|
|
|
|
pTmp = CanonPath1.QueryName();
|
|
Filter.SetFileName( pTmp );
|
|
DELETE( pTmp );
|
|
Filter.SetAttributes( (FSN_ATTRIBUTE)0, // ALL
|
|
FSN_ATTRIBUTE_FILES, // ANY
|
|
FSN_ATTRIBUTE_DIRECTORY ); // NONE
|
|
pNodeArray = pDirectory->QueryFsnodeArray( &Filter );
|
|
pIterator = pNodeArray->QueryIterator();
|
|
DELETE( pDirectory );
|
|
|
|
_File1 = (FSN_FILE *)pIterator->GetNext();
|
|
} else {
|
|
_File1 = NULL;
|
|
}
|
|
DELETE( pTmpPath );
|
|
} else {
|
|
_File1 = SYSTEM::QueryFile( &CanonPath1 );
|
|
}
|
|
|
|
if( _File1 == NULL ) {
|
|
Errlev = CANT_OPEN_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
|
|
psmsg->Display( "%W", _InputPath1->GetPathString() );
|
|
return;
|
|
}
|
|
|
|
do {
|
|
|
|
//
|
|
// Explicitly find if File1 has offline attributes set (This is required
|
|
// because SYSTEM::QueryFile is not used for getting the FSN_FILE object)
|
|
//
|
|
if (_SkipOffline && IsOffline(_File1)) {
|
|
PrintSkipWarning = TRUE;
|
|
Errlev = FILES_SKIPPED;
|
|
DELETE( _File1 );
|
|
if( !CanonPath1.HasWildCard() ) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Replace the input path filename with what is to be opened...
|
|
//
|
|
pTmp = _File1->GetPath()->QueryName();
|
|
_InputPath1->SetName( pTmp );
|
|
DELETE( pTmp );
|
|
|
|
|
|
// Determine if filename 2 contains any wildcards...
|
|
if( CanonPath2.HasWildCard() ) {
|
|
// ...if it does, expand them...
|
|
PPATH pExpanded;
|
|
|
|
pExpanded = CanonPath2.QueryWCExpansion( (PATH *)_File1->GetPath() );
|
|
if( pExpanded == NULL ) {
|
|
Errlev = COULD_NOT_EXP;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_EXPAND );
|
|
psmsg->Display( "%W%W", _InputPath1->GetPathString(), _InputPath2->GetPathString() );
|
|
DELETE( _File1 );
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Place the expanded name in the input path...
|
|
//
|
|
pTmp = pExpanded->QueryName();
|
|
_InputPath2->SetName( pTmp );
|
|
DELETE( pTmp );
|
|
|
|
psmsg->Set( MSG_COMP_COMPARE_FILES );
|
|
psmsg->Display( "%W%W", _InputPath1->GetPathString(),
|
|
_InputPath2->GetPathString()
|
|
);
|
|
_File2 = SYSTEM::QueryFile( pExpanded, _SkipOffline, &OfflineSkipped );
|
|
DELETE( pExpanded );
|
|
|
|
} else {
|
|
|
|
psmsg->Set( MSG_COMP_COMPARE_FILES );
|
|
psmsg->Display( "%W%W", _InputPath1->GetPathString(),
|
|
_InputPath2->GetPathString()
|
|
);
|
|
_File2 = SYSTEM::QueryFile( &CanonPath2, _SkipOffline, &OfflineSkipped );
|
|
|
|
}
|
|
|
|
if( _File2 == NULL ) {
|
|
if (OfflineSkipped) {
|
|
// Skipping offline files is not an error, just track this happened
|
|
PrintSkipWarning = TRUE;
|
|
Errlev = FILES_SKIPPED;
|
|
} else {
|
|
// Display error message
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
|
|
psmsg->Display( "%W", _InputPath2->GetPathString() );
|
|
Errlev = CANT_OPEN_FILE;
|
|
}
|
|
DELETE( _File1 );
|
|
if( !CanonPath1.HasWildCard() ) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the streams...
|
|
// Initialize _ByteStream1 and _ByteStream2 with BufferSize = 1024, to
|
|
// improve performance
|
|
//
|
|
if( (( _FileStream1 = (FILE_STREAM *)_File1->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ) ) == NULL) ||
|
|
!_ByteStream1.Initialize( _FileStream1, 1024 )
|
|
) {
|
|
Errlev = CANT_READ_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_READ );
|
|
psmsg->Display( "%W", _File1->GetPath()->GetPathString() );
|
|
DELETE( _File1 );
|
|
DELETE( _File2 );
|
|
if( !CanonPath1.HasWildCard() ) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if( (( _FileStream2 = (FILE_STREAM *)_File2->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ) ) == NULL ) ||
|
|
!_ByteStream2.Initialize( _FileStream2, 1024 )
|
|
) {
|
|
Errlev = CANT_READ_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_READ );
|
|
psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
|
|
DELETE( _FileStream1 );
|
|
DELETE( _File1 );
|
|
DELETE( _File2 );
|
|
if( !CanonPath1.HasWildCard() ) {
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
BinaryCompare();
|
|
|
|
// Close both streams now, since we are done with them...
|
|
DELETE( _FileStream1 );
|
|
DELETE( _FileStream2 );
|
|
DELETE( _File1 );
|
|
DELETE( _File2 );
|
|
|
|
if( !CanonPath1.HasWildCard() ) {
|
|
break;
|
|
}
|
|
|
|
} while( ( _File1 = (FSN_FILE *)pIterator->GetNext() ) != NULL );
|
|
|
|
//
|
|
// Print warning message if offline files were skipped
|
|
//
|
|
if(PrintSkipWarning) {
|
|
psmsg->Set( MSG_COMP_OFFLINE_FILES_SKIPPED );
|
|
psmsg->Display( "" );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
BOOLEAN
|
|
COMP::IsOffline(
|
|
PFSN_FILE pFile
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if a file object represents an offline file
|
|
|
|
Arguments:
|
|
|
|
pFile - The file to check
|
|
|
|
Return Value:
|
|
|
|
TRUE - if offline
|
|
|
|
Notes:
|
|
|
|
This routine assumes a valid object that represnts a valid file
|
|
On error, it returns FALSE since a none offline file is the default.
|
|
|
|
--*/
|
|
{
|
|
PWSTRING FullPath = NULL;
|
|
BOOLEAN fRet = FALSE;
|
|
PCWSTR FileName;
|
|
DWORD dwAttributes;
|
|
|
|
DebugAssert( pFile );
|
|
|
|
if ( pFile &&
|
|
((FullPath = pFile->GetPath()->QueryFullPathString()) != NULL ) &&
|
|
((FileName = FullPath->GetWSTR()) != NULL ) &&
|
|
( FileName[0] != (WCHAR)'\0' ) &&
|
|
((dwAttributes = GetFileAttributes( FileName )) != -1) ) {
|
|
|
|
if (dwAttributes & FILE_ATTRIBUTE_OFFLINE) {
|
|
fRet = TRUE;
|
|
}
|
|
}
|
|
|
|
DELETE( FullPath );
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
|
|
BOOLEAN
|
|
COMP::CharEqual(
|
|
PUCHAR c1,
|
|
PUCHAR c2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks to see if a PCHAR DBCS or SBCS char is equal or not. For SBCS
|
|
chars, if the CaseInsensitive flag is set, the characters are converted
|
|
to uppercase and checked for equality.
|
|
|
|
Arguments:
|
|
|
|
c1 - NULL terminating DBCS/SBCS char *.
|
|
c2 - NULL terminating DBCS/SBCS char *.
|
|
|
|
Return Value:
|
|
|
|
TRUE - if equal.
|
|
|
|
Notes:
|
|
|
|
The char string sequence is:
|
|
|
|
SBCS:
|
|
c1[0] - char code.
|
|
c1[1] - 0.
|
|
DBCS:
|
|
c1[0] - leadbyte.
|
|
c1[1] - tailbyte.
|
|
c1[2] - 0.
|
|
|
|
--*/
|
|
{
|
|
if ( (*(c1+1) == 0) && (*(c2+1) == 0 ) )
|
|
return( CASE_SENSITIVE( *c1 ) == CASE_SENSITIVE( *c2 ) );
|
|
else
|
|
return( (*c1 == *c2) && (*(c1+1) == *(c2+1)) );
|
|
}
|
|
|
|
#endif
|
|
|
|
VOID
|
|
COMP::BinaryCompare(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Does the actual binary compare between the two streams
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Notes:
|
|
|
|
The binary compare simply does a byte by byte comparison of the two
|
|
files and reports all differences, as well as the offset into the
|
|
file... ...no line buffer is required for this comparision...
|
|
|
|
--*/
|
|
{
|
|
ULONG FileOffset = 0;
|
|
ULONG LineCount = 1; // Start the line count at 1...
|
|
USHORT Differences = 0;
|
|
BYTE Byte1, Byte2;
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
BOOLEAN Lead = 0; // Set when leadbyte is read.
|
|
UCHAR Byte1W[3], Byte2W[3]; // PCHAR to contain DBCS/SBCS char.
|
|
ULONG DBCSFileOffset = 0; // When ASCII output and DBCS char,
|
|
// the offset is where the lead
|
|
// byte is, not the tail byte.
|
|
#endif
|
|
STR Message[ 9 ];
|
|
DSTRING ErrType;
|
|
|
|
//
|
|
// Set up the message string...
|
|
//
|
|
Message[ 0 ] = '%';
|
|
Message[ 1 ] = 'W';
|
|
if( !_Numbered ) {
|
|
if (!SYSTEM::QueryResourceString(&ErrType, MSG_COMP_OFFSET_STRING, "")) {
|
|
DebugPrintTrace(("COMP: Unable to read resource string %d\n", MSG_COMP_OFFSET_STRING));
|
|
Errlev = INTERNAL_ERROR;
|
|
return;
|
|
}
|
|
Message[ 2 ] = '%';
|
|
Message[ 3 ] = 'X';
|
|
} else {
|
|
if (!SYSTEM::QueryResourceString(&ErrType, MSG_COMP_LINE_STRING, "")) {
|
|
DebugPrintTrace(("COMP: Unable to read resource string %d\n", MSG_COMP_LINE_STRING));
|
|
Errlev = INTERNAL_ERROR;
|
|
return;
|
|
}
|
|
Message[ 2 ] = '%';
|
|
Message[ 3 ] = 'd';
|
|
}
|
|
if( _Mode == OUTPUT_HEX ) {
|
|
Message[ 4 ] = '%';
|
|
Message[ 5 ] = 'X';
|
|
Message[ 6 ] = '%';
|
|
Message[ 7 ] = 'X';
|
|
} else if( _Mode == OUTPUT_DECIMAL ) {
|
|
Message[ 4 ] = '%';
|
|
Message[ 5 ] = 'd';
|
|
Message[ 6 ] = '%';
|
|
Message[ 7 ] = 'd';
|
|
} else {
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
// This is needed to display DBCS chars. DBCS chars will be handed over
|
|
// as a pointer of chars. In which turns out to go to a call to swprintf in
|
|
// basesys.cxx. You may wonder why a DBCS char is stored as a string, but
|
|
// it works because before the call to swprintf is made, a 'h' is placed before
|
|
// the '%s' and makes a conversion to unicode.
|
|
|
|
Message[ 4 ] = '%';
|
|
Message[ 5 ] = 's';
|
|
Message[ 6 ] = '%';
|
|
Message[ 7 ] = 's';
|
|
#else // FE_SB
|
|
Message[ 4 ] = '%';
|
|
Message[ 5 ] = 'c';
|
|
Message[ 6 ] = '%';
|
|
Message[ 7 ] = 'c';
|
|
#endif // FE_SB
|
|
}
|
|
Message[ 8 ] = 0;
|
|
|
|
// Compare the lengths of the files - if they aren't the same and
|
|
// the number of lines to match hasn't been specified, then return
|
|
// 'Files are different sizes'.
|
|
if( !_Limited ) {
|
|
if( _File1->QuerySize() != _File2->QuerySize() ) {
|
|
Errlev = DIFFERENT_SIZES;
|
|
CompResult = FILES_ARE_DIFFERENT;
|
|
psmsg->Set( MSG_COMP_DIFFERENT_SIZES );
|
|
psmsg->Display( "" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
for( ;; FileOffset++ ) {
|
|
//if( !_FileStream1->ReadByte( &Byte1 ) ) {
|
|
if( !_ByteStream1.ReadByte( &Byte1 ) ) {
|
|
if( !_ByteStream1.IsAtEnd() ) {
|
|
Errlev = CANT_READ_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_READ );
|
|
psmsg->Display( "%W", _File1->GetPath()->GetPathString() );
|
|
return;
|
|
}
|
|
//if( !_FileStream2->ReadByte( &Byte2 ) ) {
|
|
if( !_ByteStream2.ReadByte( &Byte2 ) ) {
|
|
if( !_ByteStream2.IsAtEnd() ) {
|
|
Errlev = CANT_READ_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_READ );
|
|
psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
|
|
return;
|
|
}
|
|
break;
|
|
} else {
|
|
Errlev = FILE1_LINES;
|
|
psmsg->Set( MSG_COMP_FILE1_TOO_SHORT );
|
|
psmsg->Display( "%d", LineCount-1 );
|
|
return;
|
|
}
|
|
} else {
|
|
//if( !_FileStream2->ReadByte( &Byte2 ) ) {
|
|
if( !_ByteStream2.ReadByte( &Byte2 ) ) {
|
|
if( !_ByteStream2.IsAtEnd() ) {
|
|
Errlev = CANT_READ_FILE;
|
|
psmsg->Set( MSG_COMP_UNABLE_TO_READ );
|
|
psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
|
|
return;
|
|
}
|
|
Errlev = FILE2_LINES;
|
|
psmsg->Set( MSG_COMP_FILE2_TOO_SHORT );
|
|
psmsg->Display( "%d", LineCount-1 );
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
// For hex and decimal display, we don't want to worry about DBCS chars. This
|
|
// is a different spec than DOS/V (Japanese DOS), but it's much cleaner this
|
|
// way. So, we will only worry about DBCS chars when the user asks us to
|
|
// display the difference in characters (/A option). The file offset displayed
|
|
// for DBCS characters is always where the leadbyte is in the file even though
|
|
// only the tailbyte is different.
|
|
|
|
DBCSFileOffset = FileOffset;
|
|
|
|
//
|
|
// Only going to worry about DBCS when user is comparing with
|
|
// ASCII output.
|
|
//
|
|
//if ( _Mode == OUTPUT_ASCII ) {
|
|
|
|
|
|
//kksuzuka: #133
|
|
//We have to worry about DBCS with 'c' option also.
|
|
if ( (_Mode==OUTPUT_ASCII) || ( _CaseInsensitive ) ) {
|
|
|
|
if ( Lead ) {
|
|
|
|
//
|
|
// DBCS leadbyte already found. Setup variables and
|
|
// fill in tailbyte and null.
|
|
//
|
|
|
|
DBCSFileOffset--;
|
|
Lead = FALSE;
|
|
*(Byte1W+1) = Byte1;
|
|
*(Byte2W+1) = Byte2;
|
|
*(Byte1W+2) = *(Byte2W+2) = 0;
|
|
|
|
}
|
|
else if ( IsDBCSLeadByte( Byte1 ) || IsDBCSLeadByte( Byte2 ) ) {
|
|
|
|
//
|
|
// Found leadbyte. Set lead flag telling the next time
|
|
// around that the character is a tailbyte.
|
|
//
|
|
|
|
//
|
|
// Save the leadbyte. Tailbyte will be filled next time
|
|
// around(above).
|
|
//
|
|
|
|
*Byte1W = Byte1;
|
|
*Byte2W = Byte2;
|
|
Lead = TRUE;
|
|
continue;
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// SBCS char.
|
|
//
|
|
|
|
*Byte1W = Byte1;
|
|
*Byte2W = Byte2;
|
|
*(Byte1W+1) = *(Byte2W+1) = 0;
|
|
Lead = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Not ASCII output (/a option). Perform original routines.
|
|
//
|
|
|
|
*Byte1W = Byte1;
|
|
*Byte2W = Byte2;
|
|
*(Byte1W+1) = *(Byte2W+1) = 0;
|
|
}
|
|
|
|
//
|
|
// Check to see if chars are equal. If not, display difference.
|
|
//
|
|
|
|
if ( CharEqual( Byte1W, Byte2W ) == FALSE ) {
|
|
|
|
psmsg->Set( MSG_COMP_COMPARE_ERROR );
|
|
|
|
if ( _Mode == OUTPUT_ASCII ) {
|
|
|
|
if ( _Numbered )
|
|
psmsg->Display( Message, &ErrType,
|
|
LineCount, Byte1W, Byte2W );
|
|
else
|
|
psmsg->Display( Message, &ErrType,
|
|
DBCSFileOffset, Byte1W, Byte2W );
|
|
|
|
}
|
|
else {
|
|
if ( _Numbered )
|
|
psmsg->Display( Message, &ErrType,
|
|
LineCount, *Byte1W, *Byte2W );
|
|
//kksuzuka: #133
|
|
//We have to worry about DBCS with c option also.
|
|
//else
|
|
else {
|
|
if( *Byte1W != *Byte2W ) {
|
|
psmsg->Display( Message, &ErrType,
|
|
FileOffset, *Byte1W, *Byte2W );
|
|
}
|
|
else {
|
|
psmsg->Display( Message, &ErrType,
|
|
FileOffset, *(Byte1W+1), *(Byte2W+1) );
|
|
}
|
|
}
|
|
}
|
|
#else // FE_SB
|
|
// Now compare the bytes...if they are different, report the
|
|
// difference...
|
|
if( CASE_SENSITIVE( Byte1 ) != CASE_SENSITIVE( Byte2 ) ) {
|
|
if( _Numbered ) {
|
|
psmsg->Set( MSG_COMP_COMPARE_ERROR );
|
|
psmsg->Display( Message, &ErrType, LineCount, Byte1, Byte2 );
|
|
} else {
|
|
psmsg->Set( MSG_COMP_COMPARE_ERROR );
|
|
psmsg->Display( Message, &ErrType, FileOffset, Byte1, Byte2 );
|
|
}
|
|
|
|
#endif // FE_SB
|
|
|
|
if( ++Differences == MAX_DIFF ) {
|
|
psmsg->Set( MSG_COMP_TOO_MANY_ERRORS );
|
|
psmsg->Display( "" );
|
|
Errlev = TEN_MISM;
|
|
CompResult = FILES_ARE_DIFFERENT;
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// Use <CR>'s imbedded in File1 to determine the line count. This is
|
|
// an inexact method (the differing byte may be '/r') but it is good
|
|
// enough for the purposes of this program.
|
|
//
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
if( *Byte1W == '\r' ) {
|
|
#else // FE_SB
|
|
if( Byte1 == '\r' ) {
|
|
#endif // FE_SB
|
|
LineCount++;
|
|
}
|
|
if( _Limited ) {
|
|
if( LineCount > (ULONG)_NumberOfLines ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef FE_SB // v-junm - 08/30/93
|
|
// There may be a leadbyte without a tailbyte at the end of the file. Check
|
|
// for it, and process accordingly.
|
|
|
|
if ( _Mode == OUTPUT_ASCII && Lead ) {
|
|
|
|
//
|
|
// There is a leadbyte left. Check to see if they are equal and
|
|
// print difference if not.
|
|
//
|
|
|
|
if ( *Byte2W != *Byte1W ) {
|
|
|
|
*(Byte1W+1) = *(Byte2W+1) = 0;
|
|
Differences++;
|
|
|
|
psmsg->Set( MSG_COMP_COMPARE_ERROR );
|
|
if ( _Numbered )
|
|
psmsg->Display(Message, &ErrType, LineCount, Byte1W, Byte2W);
|
|
else
|
|
psmsg->Display(Message, &ErrType, FileOffset-1, Byte1W, Byte2W);
|
|
}
|
|
}
|
|
|
|
#endif // FE_SB
|
|
|
|
//
|
|
// Check if any differences were found in the files
|
|
//
|
|
if( !Differences ) {
|
|
psmsg->Set( MSG_COMP_FILES_OK );
|
|
psmsg->Display( " " );
|
|
} else {
|
|
CompResult = FILES_ARE_DIFFERENT;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
int __cdecl
|
|
main(
|
|
)
|
|
{
|
|
|
|
|
|
DEFINE_CLASS_DESCRIPTOR( COMP );
|
|
|
|
|
|
__try {
|
|
|
|
COMP Comp;
|
|
|
|
psmsg = NEW STREAM_MESSAGE;
|
|
|
|
if (psmsg == NULL) {
|
|
DebugPrint("COMP: Out of memory\n");
|
|
Errlev = NO_MEM_AVAIL;
|
|
return( CANNOT_COMPARE_FILES );
|
|
}
|
|
// Initialize the stream message for standard input, stdout
|
|
if (!Get_Standard_Output_Stream() ||
|
|
!Get_Standard_Input_Stream() ||
|
|
!psmsg->Initialize( Get_Standard_Output_Stream(),
|
|
Get_Standard_Input_Stream(),
|
|
Get_Standard_Error_Stream() )) {
|
|
|
|
if (!Get_Standard_Output_Stream()) {
|
|
DebugPrintTrace(("COMP: Output stream is NULL\n"));
|
|
} else if (!Get_Standard_Input_Stream()) {
|
|
DebugPrintTrace(("COMP: Input stream is NULL\n"));
|
|
} else {
|
|
DebugPrintTrace(("COMP: Unable to initialize message stream\n"));
|
|
}
|
|
|
|
Comp.Destruct();
|
|
return( CANNOT_COMPARE_FILES );
|
|
}
|
|
|
|
if( !SYSTEM::IsCorrectVersion() ) {
|
|
DebugPrintTrace(( "COMP: Incorrect Version Number...\n" ));
|
|
psmsg->Set( MSG_COMP_INCORRECT_VERSION );
|
|
psmsg->Display( "" );
|
|
Comp.Destruct();
|
|
return( CANNOT_COMPARE_FILES );
|
|
// return( INCORRECT_DOS_VER );
|
|
}
|
|
|
|
// Set the Error level to Zero - No error...
|
|
Errlev = NO_ERRORS;
|
|
CompResult = FILES_ARE_EQUAL;
|
|
|
|
if( !( Comp.Initialize() ) ) {
|
|
//
|
|
// The Command line didn't initialize properly, die nicely
|
|
// without printing any error messages - Main doesn't know
|
|
// why the Initialization failed...
|
|
//
|
|
// What has to be deleted by hand, or can everything be removed
|
|
// by the destructor for the FC class?
|
|
//
|
|
Comp.Destruct();
|
|
return( CANNOT_COMPARE_FILES );
|
|
// return( Errlev );
|
|
}
|
|
|
|
|
|
// Do file comparison stuff...
|
|
Comp.Start();
|
|
Comp.Destruct();
|
|
// return( Errlev );
|
|
if( ( Errlev == NO_ERRORS ) || ( Errlev == TEN_MISM ) || ( Errlev == DIFFERENT_SIZES ) ) {
|
|
return( CompResult );
|
|
} else {
|
|
return( CANNOT_COMPARE_FILES );
|
|
}
|
|
|
|
} __except ((_exception_code() == STATUS_STACK_OVERFLOW) ?
|
|
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
|
|
|
// may not be able to display anything if initialization failed
|
|
// in additional to out of stack space
|
|
// so just send a message to the debug port
|
|
|
|
DebugPrint("COMP: Out of stack space\n");
|
|
Errlev = NO_MEM_AVAIL;
|
|
return CANNOT_COMPARE_FILES;
|
|
}
|
|
}
|