NT4/private/utils/comp/comp.cxx
2020-09-30 17:12:29 +02:00

1245 lines
35 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1991 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.
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 "error.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>
}
ERRSTACK *perrstk;
STREAM_MESSAGE *psmsg; // Create a pointer to the stream message
// class for program output.
ULONG Errlev; // The current program error level
PSTREAM Stdout;
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::Destruct(
)
/*++
Routine Description:
Cleans up after finishing with an FC object.
Arguments:
None.
Return Value:
None.
--*/
{
DELETE( perrstk );
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;
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() ) {
DebugPrintf( "LexArray.Initialize() Failed!\n" );
Errlev = INTERNAL_ERROR;
return( FALSE );
}
if( !ArgLex.Initialize(&LexArray) ) {
DebugPrintf( "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() ) {
DebugPrintf( "ArgLex.PrepareToParse() Failed!\n" );
Errlev = INTERNAL_ERROR;
return( FALSE );
}
if( !ProgramName.Initialize("*") ||
!FlagDecimalFormat.Initialize("/D") ||
!FlagAsciiFormat.Initialize("/A") ||
!FlagLineNumbers.Initialize("/L") ||
!FlagCaseInsensitive.Initialize("/C") ||
!FlagWrongNumber.Initialize("/N") ||
!LongMatchLines.Initialize("/N=*") ||
!FlagRequestHelp.Initialize("/?") ||
!StringInvalidSwitch.Initialize("/*") ||
!InFile1.Initialize("*") ||
!InFile2.Initialize("*") ) {
DebugPrintf( "Unable to Initialize some or all of the Arguments!\n" );
Errlev = INTERNAL_ERROR;
return( FALSE );
}
if( !ArrayOfArg.Initialize() ) {
DebugPrintf( "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(&FlagWrongNumber) ||
!ArrayOfArg.Put(&LongMatchLines) ||
!ArrayOfArg.Put(&FlagRequestHelp) ||
!ArrayOfArg.Put(&StringInvalidSwitch) ||
!ArrayOfArg.Put(&InFile1) ||
!ArrayOfArg.Put(&InFile2) ) {
DebugPrintf( "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();
if( FlagDecimalFormat.IsValueSet() ||
FlagAsciiFormat.IsValueSet() ||
FlagLineNumbers.IsValueSet() ||
FlagCaseInsensitive.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 ) ) {
DebugPrintf( "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 ) ) {
DebugPrintf( "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 '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;
//
// 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 ) {
DebugPrintf( "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 {
//
// 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 );
DELETE( pExpanded );
} else {
psmsg->Set( MSG_COMP_COMPARE_FILES );
psmsg->Display( "%W%W", _InputPath1->GetPathString(),
_InputPath2->GetPathString()
);
_File2 = SYSTEM::QueryFile( &CanonPath2 );
}
if( _File2 == NULL ) {
Errlev = CANT_OPEN_FILE;
psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
psmsg->Display( "%W", _InputPath2->GetPathString() );
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 ) ) == 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 ) ) == 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 );
return;
}
#ifdef DBCS // 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 DBCS // 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 ) {
ErrType.Initialize( "OFFSET" );
Message[ 2 ] = '%';
Message[ 3 ] = 'X';
} else {
ErrType.Initialize( "LINE" );
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 DBCS // 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 // DBCS
Message[ 4 ] = '%';
Message[ 5 ] = 'c';
Message[ 6 ] = '%';
Message[ 7 ] = 'c';
#endif // DBCS
}
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 DBCS // 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 // DBCS
// 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 // DBCS
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 DBCS // v-junm - 08/30/93
if( *Byte1W == '\r' ) {
#else // DBCS
if( Byte1 == '\r' ) {
#endif // DBCS
LineCount++;
}
if( _Limited ) {
if( LineCount > (ULONG)_NumberOfLines ) {
break;
}
}
}
#ifdef DBCS // 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 // DBCS
//
// 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 _CRTAPI1
main(
)
{
DEFINE_CLASS_DESCRIPTOR( COMP );
{
COMP Comp;
perrstk = NEW ERRSTACK;
psmsg = NEW STREAM_MESSAGE;
Stdout = Get_Standard_Output_Stream();
// Initialize the stream message for standard input, stdout
psmsg->Initialize( Stdout,
Get_Standard_Input_Stream(),
Get_Standard_Error_Stream() );
if( !SYSTEM::IsCorrectVersion() ) {
DebugPrintf( "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 );
}
}
}