Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

1492 lines
35 KiB

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
fc.cxx
Abstract:
Compares two files or sets of files and displays the differences between
them.
FC [/A] [/C] [/L] [/LBn] [/N] [/T] [/W] [/nnnn] [drive1:][path1]filename1
[drive2:][path2]filename2
FC /B [drive1:][path1]filename1 [drive2:][path2]filename2
/A Displays only first and last lines for each set of differences.
/B Performs a binary comparison.
/C Disregards the case of letters.
/L Compares files as ASCII text.
/LBn Sets the maximum consecutive mismatches to the specified number of
lines.
/N Displays the line numbers on an ASCII comparison.
/T Does not expand tabs to spaces.
/W Compresses white space (tabs and spaces) for comparison.
/nnnn Specifies the number of consecutive lines that must match after a
mismatch.
Author:
Barry J. Gilhuly *** W-Barry *** May 91
Environment:
ULIB, User Mode
--*/
#include "ulib.hxx"
#include "ulibcl.hxx"
#include "error.hxx"
#include "arg.hxx"
#include "array.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 "fc.hxx"
extern "C" {
#include <stdio.h>
#include <string.h>
}
ERRSTACK *perrstk;
STREAM_MESSAGE *psmsg; // Create a pointer to the stream message
// class for program output.
DEFINE_CONSTRUCTOR( FC, PROGRAM );
VOID
FC::Destruct(
)
/*++
Routine Description:
Cleans up after finishing with an FC object.
Arguments:
None.
Return Value:
None.
--*/
{
DELETE( perrstk );
DELETE( psmsg );
DELETE( _InputPath1 );
DELETE( _InputPath2 );
return;
}
BOOLEAN
FC::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 FlagAbbreviate;
FLAG_ARGUMENT FlagAsciiCompare;
FLAG_ARGUMENT FlagBinaryCompare;
FLAG_ARGUMENT FlagCaseInsensitive;
FLAG_ARGUMENT FlagCompression;
FLAG_ARGUMENT FlagExpansion;
FLAG_ARGUMENT FlagLineNumber;
FLAG_ARGUMENT FlagRequestHelp;
PATH_ARGUMENT InFile1;
PATH_ARGUMENT InFile2;
PCPATH TmpPath;
WSTRING TmpString1;
WSTRING TmpString2;
PCWSTRING AuxString;
_InputPath1 = NULL;
_InputPath2 = NULL;
//
// Set the default mode to ASCII
//
_Mode = FALSE;
if( !LexArray.Initialize() ) {
DbgAbort( "LexArray.Initialize() Failed!\n" );
return( FALSE );
}
if( !ArgLex.Initialize(&LexArray) ) {
DbgAbort( "ArgLex.Initialize() Failed!\n" );
return( FALSE );
}
// Allow only the '/' as a valid switch
ArgLex.PutSwitches("/");
ArgLex.SetCaseSensitive( FALSE );
if( !ArgLex.PrepareToParse() ) {
DbgAbort( "ArgLex.PrepareToParse() Failed!\n" );
return( FALSE );
}
if( !ProgramName.Initialize("*") ||
!FlagAbbreviate.Initialize("/A") ||
!FlagAsciiCompare.Initialize("/L") ||
!FlagBinaryCompare.Initialize("/B") ||
!FlagCaseInsensitive.Initialize("/C") ||
!FlagCompression.Initialize("/W") ||
!FlagExpansion.Initialize("/T") ||
!FlagLineNumber.Initialize("/N") ||
!FlagRequestHelp.Initialize("/?") ||
!_LongBufferSize.Initialize("/LB#") ||
!_LongMatch.Initialize("/*") ||
!InFile1.Initialize("*") ||
!InFile2.Initialize("*") ) {
DbgAbort( "Unable to Initialize some or all of the Arguments!\n" );
return( FALSE );
}
if( !ArrayOfArg.Initialize() ) {
DbgAbort( "ArrayOfArg.Initialize() Failed\n" );
return( FALSE );
}
if( !ArrayOfArg.Put(&ProgramName) ||
!ArrayOfArg.Put(&FlagAbbreviate) ||
!ArrayOfArg.Put(&FlagAsciiCompare) ||
!ArrayOfArg.Put(&FlagBinaryCompare) ||
!ArrayOfArg.Put(&FlagCaseInsensitive) ||
!ArrayOfArg.Put(&FlagCompression) ||
!ArrayOfArg.Put(&FlagExpansion) ||
!ArrayOfArg.Put(&FlagLineNumber) ||
!ArrayOfArg.Put(&FlagRequestHelp) ||
!ArrayOfArg.Put(&_LongBufferSize) ||
!ArrayOfArg.Put(&_LongMatch) ||
!ArrayOfArg.Put(&InFile1) ||
!ArrayOfArg.Put(&InFile2) ) {
DbgAbort( "ArrayOfArg.Put() Failed!\n" );
return( FALSE );
}
if( !( ArgLex.DoParsing( &ArrayOfArg ) ) ) {
// For each incorrect command line parameter, FC displays the
// following message:
//
// FC: Invalid Switch
//
// It does *not* die if a parameter is unrecognized...(Dos does...)
//
psmsg->Set( MSG_FC_INVALID_SWITCH );
psmsg->Display( "" );
return( FALSE );
}
// It should now be safe to test the arguments for their values...
if( FlagRequestHelp.QueryFlag() ) {
// Send help message
psmsg->Set( MSG_FC_HELP_MESSAGE );
psmsg->Display( "" );
return( FALSE );
}
if( FlagBinaryCompare.QueryFlag() &&
( FlagAsciiCompare.QueryFlag() || FlagLineNumber.QueryFlag() ) ) {
psmsg->Set( MSG_FC_INCOMPATIBLE_SWITCHES );
psmsg->Display( "" );
return( FALSE );
}
if( !InFile1.IsValueSet() ||
!InFile2.IsValueSet() ) {
psmsg->Set( MSG_FC_INSUFFICIENT_FILES );
psmsg->Display( "" );
return( FALSE );
}
//
// Convert paths to upper case
//
TmpPath = InFile1.GetPath();
DbgPtrAssert( TmpPath );
AuxString = TmpPath->GetPathString();
DbgPtrAssert( AuxString );
if( !TmpString1.Initialize( AuxString ) ){
DbgAbort( "TmpString1.Initialize() failed \n" );
return( FALSE );
}
TmpString1.Strupr();
TmpPath = InFile2.GetPath();
DbgPtrAssert( TmpPath );
AuxString = TmpPath->GetPathString();
DbgPtrAssert( AuxString );
if( !TmpString2.Initialize( AuxString ) ) {
DbgAbort( "TmpString2.Initialize() failed \n" );
return( FALSE );
}
TmpString2.Strupr();
//
// Initialize _InputPath1 and _InputPAth2
//
if( ( _InputPath1 = NEW PATH ) == NULL ) {
DbgAbort( "Unable to allocate memory for string storage\n" );
return( FALSE );
}
/*
if( !_InputPath1->Initialize( InFile1.GetPath(), FALSE ) ) {
DbgAbort( "Failed to initialize canonicolized version of the path 1\n" );
return( FALSE );
}
*/
if( !_InputPath1->Initialize( &TmpString1, FALSE ) ) {
DbgAbort( "Failed to initialize canonicolized version of the path 1\n" );
return( FALSE );
}
if( ( _InputPath2 = NEW PATH ) == NULL ) {
DbgAbort( "Unable to allocate memory for string storage\n" );
return( FALSE );
}
/*
if( !_InputPath2->Initialize( InFile2.GetPath(), FALSE ) ) {
DbgAbort( "Failed to initialize canonicolized version of the path 1\n" );
return( FALSE );
}
*/
if( !_InputPath2->Initialize( &TmpString2, FALSE ) ) {
DbgAbort( "Failed to initialize canonicolized version of the path 1\n" );
return( FALSE );
}
if( !FlagBinaryCompare.QueryFlag() &&
!FlagAsciiCompare.QueryFlag() ) {
PWSTRING pString;
PSUB_STRING pExtention;
USHORT idx;
if( ( pExtention = _InputPath1->QueryExt() ) != NULL ) {
pString = NEW WSTRING;
idx = 0;
while( Extentions[ idx ] != NULL ) {
pString->Initialize( Extentions[ idx ] );
if( !pString->Stricmp( pExtention ) ) {
_Mode = TRUE;
break;
}
idx++;
}
DELETE( pString );
DELETE( pExtention );
}
} else if( FlagBinaryCompare.QueryFlag() ) {
_Mode = TRUE;
}
//
// Retrieve the values of the rest of the flags...
//
_Abbreviate = FlagAbbreviate.QueryFlag();
_CaseInsensitive = FlagCaseInsensitive.QueryFlag();
_Compression = FlagCompression.QueryFlag();
_Expansion = FlagExpansion.QueryFlag();
_LineNumber = FlagLineNumber.QueryFlag();
return( TRUE );
}
VOID
FC::DoCompare(
)
/*++
Routine Description:
Perform the comparison of the files.
Arguments:
None.
Return Value:
None.
--*/
{
FSN_FILTER Filter;
PARRAY pNodeArray = NULL;
PITERATOR pIterator = NULL;
PDYNAMIC_SUB_STRING pTmp = NULL;
PFSN_DIRECTORY pDirectory = NULL;
PATH CanonPath1;
PATH CanonPath2;
PPATH AuxPath1;
PPATH AuxPath2;
PDYNAMIC_SUB_STRING Name;
PDYNAMIC_SUB_STRING Prefix;
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 ) {
DbgAbort( "Unable to grab the Prefix from the input path...\n" );
return;
}
pTmpPath->TruncateBase();
if( ( pDirectory = SYSTEM::QueryDirectory( pTmpPath ) ) != 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 ) {
psmsg->Set( MSG_FC_FILES_NOT_FOUND );
psmsg->Display( "%W", _InputPath1->GetPathString() );
DELETE( pIterator );
DELETE( pNodeArray);
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 ) {
psmsg->Set( MSG_FC_CANT_EXPAND_TO_MATCH );
psmsg->Display( "%W%W", _InputPath1->GetPathString(), _InputPath2->GetPathString() );
DELETE( _File1 );
DELETE( pIterator );
DELETE( pNodeArray);
return;
}
//
// Place the expanded name in the input path...
//
pTmp = pExpanded->QueryName();
_InputPath2->SetName( pTmp );
DELETE( pTmp );
psmsg->Set( MSG_FC_COMPARING_FILES );
psmsg->Display( "%W%W", _InputPath1->GetPathString(),
_InputPath2->GetPathString() );
_File2 = SYSTEM::QueryFile( pExpanded );
DELETE( pExpanded );
} else {
psmsg->Set( MSG_FC_COMPARING_FILES );
psmsg->Display( "%W%W", _InputPath1->GetPathString(),
_InputPath2->GetPathString()
);
_File2 = SYSTEM::QueryFile( &CanonPath2 );
}
if( _File2 == NULL ) {
psmsg->Set( MSG_FC_UNABLE_TO_OPEN );
psmsg->Display( "%W", _InputPath2->GetPathString() );
DELETE( _File1 );
if( !CanonPath1.HasWildCard() ) {
break;
}
continue;
}
//
// Open the streams...
//
if( ( _FileStream1 = (FILE_STREAM *)_File1->QueryStream( READ_ACCESS ) ) == NULL ) {
psmsg->Set( MSG_FC_CANT_CREATE_STREAM );
psmsg->Display( "%W", _InputPath1->GetPathString() );
DELETE( _File1 );
DELETE( _File2 );
if( !CanonPath1.HasWildCard() ) {
break;
}
continue;
}
if( ( _FileStream2 = (FILE_STREAM *)_File2->QueryStream( READ_ACCESS ) ) == NULL ) {
psmsg->Set( MSG_FC_CANT_CREATE_STREAM );
psmsg->Display( "%W", _InputPath2->GetPathString() );
DELETE( _FileStream1 );
DELETE( _File1 );
DELETE( _File2 );
if( !CanonPath1.HasWildCard() ) {
break;
}
continue;
}
//
// Prepare the strings that contain the file names.
// These strings will be used by Dump()
//
AuxPath1 = NEW( PATH );
DbgPtrAssert( AuxPath1 );
Prefix = _InputPath1->QueryPrefix();
if( Prefix != NULL ) {
if( !AuxPath1->Initialize( Prefix ) ) {
DbgAbort( "AuxPath1->Initialize( Prefix ) failed \n" );
DELETE( AuxPath1 );
DELETE( Prefix );
return;
}
Name = _File1->QueryName();
DbgPtrAssert( Name );
if( !AuxPath1->AppendBase( Name ) ) {
DbgAbort( "AuxPath1->AppendBase() failed \n" );
DELETE( AuxPath1 );
DELETE( Name );
return;
}
} else {
Name = _File1->QueryName();
DbgPtrAssert( Name );
if( !AuxPath1->Initialize( Name ) ) {
DbgAbort( "AuxPath1->Initialize( Name ) failed \n" );
DELETE( AuxPath1 );
DELETE( Name );
return;
}
}
DELETE( Name );
DELETE( Prefix );
_FileName1 = AuxPath1->GetPathString();
DbgPtrAssert( _FileName1 );
AuxPath2 = NEW( PATH );
DbgPtrAssert( AuxPath2 );
Prefix = _InputPath2->QueryPrefix();
if( Prefix != NULL ) {
if( !AuxPath2->Initialize( Prefix ) ) {
DbgAbort( "AuxPath2->Initialize( Prefix ) failed \n" );
DELETE( AuxPath2 );
DELETE( Prefix );
return;
}
Name = _File2->QueryName();
DbgPtrAssert( Name );
if( !AuxPath2->AppendBase( Name ) ) {
DbgAbort( "AuxPath2->AppendBase() failed \n" );
DELETE( AuxPath2 );
DELETE( Name );
return;
}
} else {
Name = _File2->QueryName();
DbgPtrAssert( Name );
if( !AuxPath2->Initialize( Name ) ) {
DbgAbort( "AuxPath2->Initialize( Name ) failed \n" );
DELETE( AuxPath2 );
DELETE( Name );
return;
}
}
DELETE( Name );
DELETE( Prefix );
_FileName2 = AuxPath2->GetPathString();
DbgPtrAssert( _FileName2 );
// Do comparison of data...
if( !_Mode ) {
DoAsciiCompare();
} else {
DoBinaryCompare();
}
DELETE( AuxPath1 );
DELETE( AuxPath2 );
// 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 );
DELETE( pIterator );
DELETE( pNodeArray);
return;
}
VOID
FC::DoAsciiCompare(
)
/*++
Routine Description:
Does the actual ascii based comparison between the ascii based files
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
ARRAY LineBuffer1, LineBuffer2;
ULONG LineNum1, LineNum2;
ULONG SyncLen, BufSize;
ULONG Src, Dest;
BOOLEAN fBuffDiff, fSync, fSame;
ULONG Index;
ULONG Count;
ULONG xc, yc, xp, yp;
BOOLEAN EndBuffer1;
BOOLEAN EndBuffer2;
ULONG Count1;
ULONG Count2;
ULONG RealSyncLen;
ARRAY EmptyStringArray1;
ARRAY EmptyStringArray2;
//
// Set the current line number of each buffer to 0
//
LineNum1 = LineNum2 = 0;
//
// Set the value for the number of lines required to consider the
// files back in sync...
//
SyncLen =
_LongMatch.IsValueSet() ? _LongMatch.QueryLong() : DEFAULT_MATCH;
//
// Initialize the buffer size
//
BufSize =
_LongBufferSize.IsValueSet() ? _LongBufferSize.QueryLong() : DEFAULT_LINE_BUFFER;
//
// For compatibility purpose, display that no differences were found
//
if( BufSize == 0 ) {
psmsg->Set( MSG_FC_NO_DIFFERENCES );
psmsg->Display( " " );
return;
}
//
// Initialize arrays of empty strings
//
if( !EmptyStringArray1.Initialize( BufSize, 0 ) ||
!EmptyStringArray2.Initialize( BufSize, 0 )) {
DbgAbort( "Unable to initialize EmptyStringArrays" );
return;
}
if( !FillEmptyStringArray( &EmptyStringArray1 ) ) {
DbgAbort( "Unable to fill EmptyStringArray1" );
return;
}
if( !FillEmptyStringArray( &EmptyStringArray2 ) ) {
DbgAbort( "Unable to fill EmptyStringArray2" );
return;
}
//
// Initialize the array of lines
//
if( !LineBuffer1.Initialize( BufSize, 0 ) ||
!LineBuffer2.Initialize( BufSize, 0 ) ) {
DbgAbort( "Unable to initialize line buffers!" );
return;
}
//
// Assume initially that the files are equal
//
fSame = TRUE;
for(;;) {
//
// Fill the buffers
//
LineNum1 += FillBuf( &LineBuffer1, _FileStream1, &EmptyStringArray1 );
LineNum2 += FillBuf( &LineBuffer2, _FileStream2, &EmptyStringArray2 );
if( ( LineBuffer1.QueryMemberCount() == 0 ) &&
( LineBuffer2.QueryMemberCount() == 0 ) ) {
//
// Buffers are empty and there are no more lines to read
// from the files.
//
if( fSame ){
//
// If no difference was found between the two files,
// display message indicating that the files are equal
//
psmsg->Set( MSG_FC_NO_DIFFERENCES );
psmsg->Display( " " );
}
return;
}
//
// At least one of the buffers is not empty.
// Assume that the contents of the two buffers are equal, and find
// out the position in the buffer where the difference start.
//
fBuffDiff = FALSE;
Count = min( LineBuffer1.QueryMemberCount(), LineBuffer2.QueryMemberCount() );
for( Index = 0; Index < Count; Index++ ) {
if( !CompareArraySeg( &LineBuffer1, Index, &LineBuffer2, Index, 1 ) ) {
fSame = FALSE; // Files are not equal
fBuffDiff = TRUE; // Buffers are not equal
break;
}
}
if( fBuffDiff ) {
//
// if a difference was found, adjust 'Count', so that the last
// line that matches is kept in the buffer.
// No adjustment is made if the buffers are the same, so that
// all lines are removed from the buffer
// (Is this correct? Dos has the same behavior)
//
//
Index = ( Index )? ( Index-1 ) : 0;
}
//
// Remove the first (index+1) lines from the buffers
//
ShiftArray( &LineBuffer1, Index, &EmptyStringArray1 );
ShiftArray( &LineBuffer2, Index, &EmptyStringArray2 );
//
// If both buffers are empty, try to refill them
//
if( ( LineBuffer1.QueryMemberCount() == 0 ) &&
( LineBuffer2.QueryMemberCount() == 0 ) ) {
continue;
}
//
// If at least one of the buffers is not empty try to refill
// fill the buffers
//
LineNum1 += FillBuf( &LineBuffer1, _FileStream1, &EmptyStringArray1 );
LineNum2 += FillBuf( &LineBuffer2, _FileStream2, &EmptyStringArray2 );
//
// Attempt to synchronize the two buffers
//
EndBuffer1 = FALSE;
EndBuffer2 = FALSE;
xc = 1; // The first element in each buffer are either
xp = 1; // different (and don't need to be compared ), or
yc = 1; // are the last elements that match before the
yp = 1; // differeces (and don't need to be compared either
// For this reason we start the indeces with '1'
// instead of '0'
fSync = FALSE;
Count1 = LineBuffer1.QueryMemberCount();
Count2 = LineBuffer2.QueryMemberCount();
while( !fSync ) {
RealSyncLen = min( Count1 - xc, Count2 - yp );
RealSyncLen = min( RealSyncLen, SyncLen );
if( CompareArraySeg( &LineBuffer1, xc, &LineBuffer2, yp, RealSyncLen ) ){
//
// Dump differences
//
Dump( &LineBuffer1, xc + 1, LineNum1, TRUE );
Dump( &LineBuffer2, yp + 1, LineNum2, FALSE );
psmsg->Set( MSG_FC_DUMP_END );
psmsg->Display( " " );
//
// Remove elements
//
ShiftArray( &LineBuffer1, xc, &EmptyStringArray1 );
ShiftArray( &LineBuffer2, yp, &EmptyStringArray2 );
fSync = TRUE;
}
if( !fSync ) {
RealSyncLen = min( Count1 - xp, Count2 - yc );
RealSyncLen = min( RealSyncLen, SyncLen );
if( CompareArraySeg( &LineBuffer1, xp, &LineBuffer2, yc, RealSyncLen ) ){
//
// Dump differences
//
Dump( &LineBuffer1, xp + 1, LineNum1, TRUE );
Dump( &LineBuffer2, yc + 1, LineNum2, FALSE );
psmsg->Set( MSG_FC_DUMP_END );
psmsg->Display( " " );
//
// Remove elements
//
ShiftArray( &LineBuffer1, xp, &EmptyStringArray1 );
ShiftArray( &LineBuffer2, yc, &EmptyStringArray2 );
fSync = TRUE;
}
}
if( !fSync ) {
//
// Adjust indexes
//
if( ++xp > xc ) {
xp = 1;
if( ++xc >= Count1 ) {
xc = Count1;
EndBuffer1 = TRUE;
}
}
if( ++yp > yc ) {
yp = 1;
if( ++yc >= Count2 ) {
yc = Count2;
EndBuffer2 = TRUE;
}
}
if( EndBuffer1 && EndBuffer2 ) {
if( ( LineBuffer1.QueryMemberCount() >= LineBuffer1.QueryCapacity() ) ||
( LineBuffer2.QueryMemberCount() >= LineBuffer2.QueryCapacity() ) ) {
psmsg->Set( MSG_FC_RESYNC_FAILED );
psmsg->Display( " " );
}
//
// Dump buffers
//
Dump( &LineBuffer1, Count1, LineNum1, TRUE );
Dump( &LineBuffer2, Count2, LineNum2, FALSE );
psmsg->Set( MSG_FC_DUMP_END );
psmsg->Display( " " );
//
// Remove elements from buffer
//
ShiftArray( &LineBuffer1, Count1, &EmptyStringArray1 );
ShiftArray( &LineBuffer2, Count2, &EmptyStringArray2 );
return;
}
}
} // while( !fSync ) loop
} // for(;;) loop
}
VOID
FC::DoBinaryCompare(
)
/*++
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;
BYTE Byte1, Byte2;
STR Buffer[OFFSET_WIDTH+1]; // A buffer to store the characters from the converted offset...
WSTRING ZeroString;
BOOLEAN fDiff;
fDiff = FALSE;
for( ;; FileOffset++ ) {
if( !_FileStream1->ReadByte( &Byte1 ) ) {
// Assume EOF of File 1
if( !_FileStream2->ReadByte( &Byte2 ) ) {
// Assume EOF of File 2
break;
} else {
fDiff = TRUE;
psmsg->Set( MSG_FC_FILES_DIFFERENT_LENGTH );
psmsg->Display( "%W%W", ( _File2->GetPath() )->GetPathString(),
( _File1->GetPath() )->GetPathString() );
break;
}
} else {
if( !_FileStream2->ReadByte( &Byte2 ) ) {
// Assume EOF of File 2
fDiff = TRUE;
psmsg->Set( MSG_FC_FILES_DIFFERENT_LENGTH );
psmsg->Display( "%W%W", ( _File1->GetPath() )->GetPathString(),
( _File2->GetPath() )->GetPathString() );
break;
}
}
// Now compare the bytes...if they are different, report the
// difference...
if( Byte1 != Byte2 ) {
fDiff = TRUE;
// Convert the current offset to a hex string...max length will be 8 chars...
sprintf( Buffer, "%08lx %02x %02x", FileOffset, Byte1, Byte2 );
ZeroString.Initialize( Buffer );
psmsg->Set( MSG_FC_DATA );
psmsg->Display( "%W", &ZeroString );
}
}
//
// Check if any differences were found in the files
//
if( !fDiff ) {
psmsg->Set( MSG_FC_NO_DIFFERENCES );
psmsg->Display( " " );
}
return;
}
ULONG
FC::FillBuf(
PARRAY pArray,
PFILE_STREAM pStream,
PARRAY EmptyStringArray
)
/*++
Routine Description:
Attempts to fill an array of strings to capacity (therefore, a
non-growing array) from the given stream.
Arguments:
pArray - A pointer to the array to be filled.
pStream - A pointer to the stream which is to be read.
EmptyStringArray - Pointer to the an array that contains empty strings.
Return Value:
The number of lines read from the stream.
Notes:
--*/
{
WSTRING Delim;
PWSTRING String;
WSTRING Spaces;
WCHAR Wchar;
ULONG Count = 0;
CHNUM idx;
Delim.Initialize( "\n\r" );
Spaces.Initialize( " " ); // BUGBUG - If TABSTOP changes,
// the length of this string must
// change as well
while( pArray->QueryMemberCount() < pArray->QueryCapacity() ) {
if( pStream->IsAtEnd() ) {
break;
}
/*
if( ( String = NEW WSTRING ) == NULL ) {
DbgAbort( "String = NEW WSTRING failed!\n" );
}
if( !String->Initialize( "" ) ) {
DbgAbort( "String->Initialize() failed!" );
}
*/
String = ( PWSTRING )EmptyStringArray->RemoveAt( 0 );
DbgPtrAssert( String );
if( !pStream->ReadLine( String ) ) {
DbgAbort( "Unable to read line but file isn't empty...\n" );
}
if( !_Expansion ) {
//
// Expand tabs to space characters...
//
Wchar = '\t'; // the tab character...
idx = 0;
while( ( idx = String->Strchr( Wchar, idx ) ) != INVALID_CHNUM ) {
String->Replace( &Spaces, idx, 1, 0, TABSTOP - ( idx % TABSTOP ) );
}
}
pArray->Put( String );
Count++;
}
return( Count );
}
BOOLEAN
FC::CompareArraySeg(
PARRAY pArrayX,
ULONG idxX,
PARRAY pArrayY,
ULONG idxY,
ULONG Len
)
/*++
Routine Description:
Compares two arrays of strings from a specified index for a specified
number of elements.
Arguments:
pArrayX - the first array to compare.
idxX - the index into array X where the compare starts.
pArrayY - the second array to compare.
idxY - the index into array Y where the compare starts.
Len - the number of elements of the arrays to compare.
Return Value:
TRUE if the array segments are the same.
Notes:
This compare function uses flags from the FC:: class to qualify which
method it uses to do the actual comparison.
--*/
{
PWSTRING StrX;
PWSTRING StrY;
WSTRING Delim;
CHNUM BeginX, EndX, BeginY, EndY;
if( ( Len == 0 ) ||
( ( idxX + Len ) > pArrayX->QueryMemberCount() ) ||
( ( idxY + Len ) > pArrayY->QueryMemberCount() ) ) {
return( FALSE );
}
while( Len > 0 ) {
StrX = (WSTRING *)pArrayX->GetAt( idxX );
StrY = (WSTRING *)pArrayY->GetAt( idxY );
if( !_Compression ) {
// If whitespace isn't being ignored, then the length of the
// strings will instantly tell whether they are different...
if( StrX->QueryChCount() != StrY->QueryChCount() ) {
return( FALSE );
}
if( _CaseInsensitive ) {
if( StrX->StringCompare( StrY, CF_IGNORECASE ) ) {
return( FALSE );
}
} else {
if( StrX->StringCompare( StrY, 0 ) ) {
return( FALSE );
}
}
} else {
Delim.Initialize( " \n\r\t" );
PSUB_STRING SubStrX;
PSUB_STRING SubStrY;
EndX = EndY = 0;
for(;;) {
if( EndX != INVALID_CHNUM ) {
BeginX = StrX->Strspn( &Delim, EndX );
} else {
BeginX = INVALID_CHNUM;
}
if( EndY != INVALID_CHNUM ) {
BeginY = StrY->Strspn( &Delim, EndY );
} else {
BeginY = INVALID_CHNUM;
}
if( BeginX == INVALID_CHNUM ) {
if( BeginY == INVALID_CHNUM ) {
break;
} else {
return( FALSE );
}
} else {
if( BeginY == INVALID_CHNUM ) {
return( FALSE );
}
}
EndX = StrX->Strcspn( &Delim, BeginX );
EndY = StrY->Strcspn( &Delim, BeginY );
if( EndX == INVALID_CHNUM ) {
SubStrX = StrX->QuerySubString( BeginX );
} else {
SubStrX = StrX->QuerySubString( BeginX, EndX - BeginX );
}
if( EndY == INVALID_CHNUM ) {
SubStrY = StrY->QuerySubString( BeginY );
} else {
SubStrY = StrY->QuerySubString( BeginY, EndY - BeginY );
}
if( SubStrX->QueryChCount() != SubStrY->QueryChCount() ) {
DELETE( SubStrX );
DELETE( SubStrY );
return( FALSE );
}
if( _CaseInsensitive ) {
if( SubStrX->Stricmp( SubStrY ) ) {
DELETE( SubStrX );
DELETE( SubStrY );
return( FALSE );
}
} else {
if( SubStrX->Strcmp( SubStrY ) ) {
DELETE( SubStrX );
DELETE( SubStrY );
return( FALSE );
}
}
DELETE( SubStrX );
DELETE( SubStrY );
}
}
Len--;
idxX++;
idxY++;
}
return( TRUE );
}
BOOLEAN
FC::ShiftArray(
PARRAY pArray,
ULONG idx,
PARRAY EmptyStringArray
)
/*++
Routine Description:
Remove idx elements from the array, starting with the the first
element. The remaining elements in the array are moved to the
begining of the array.
Arguments:
pArray - the array on which the ripple copy is carried out.
idx - number of elements to remove
EmptyStringArray - Pointer to the array that will store the strings
removed from pArray.
Return Value:
TRUE if the passed index is less or equal than the number of members
in the array.
Notes:
--*/
{
PWSTRING pTmp;
ULONG Count;
//
// Check the bounds on the index...
//
if( idx > pArray->QueryMemberCount() ) {
DbgAbort( "Invalid index idx \n" );
return( FALSE );
}
if( idx == 0) {
return( TRUE );
}
//
// Remove the first 'idx' elements from the array.
// After each element is removed, the remaining ones are automatically
// shifted to the top (RemoveAt() does it).
//
for( Count = 0; Count < idx; Count++ ) {
pTmp = (WSTRING *)pArray->RemoveAt( 0 );
DbgPtrAssert( pTmp );
EmptyStringArray->Put( pTmp );
// DELETE( pTmp );
}
return( TRUE );
}
VOID
FC::Dump(
PARRAY pArray,
ULONG idx,
ULONG LineCount,
BOOLEAN fFileIndicator
)
/*++
Routine Description:
Display the contents of an array to the screen using the message
class (and the global pointer to the message class for stdin/stdout).
It displays 'idx' elements of the array, starting at position 0.
Arguments:
pArray - A pointer to the array that is to be dumped.
idx - Numbers of elements to be displayed.
LineCount - The current line count.
fFileIndicator - Indicates which file owns the array - TRUE is File 1.
Return Value:
None.
Notes:
--*/
{
USHORT i;
// Correct the line count for output...
LineCount -= pArray->QueryMemberCount();
psmsg->Set( MSG_FC_OUTPUT_FILENAME );
if( fFileIndicator ) {
// psmsg->Display( "%W", ( _File1->GetPath() )->GetPathString() );
psmsg->Display( "%W", _FileName1 );
} else {
// psmsg->Display( "%W", ( _File2->GetPath() )->GetPathString() );
psmsg->Display( "%W", _FileName2 );
}
if( idx == 0 ) {
return;
}
if( _Abbreviate && ( idx > 2 ) ) {
//
// The output is abbreviated and there are more than two elements
// in the array.
//
// Print the first element
//
PrintSequenceOfLines( pArray, 0, 0, LineCount );
//
// Print the '...'
//
if( _LineNumber ) {
//
// print the shifted ...
//
psmsg->Set( MSG_FC_ABBREVIATE_SYMBOL_SHIFTED );
psmsg->Display( " " );
} else {
//
// print the non-shifted ...
//
psmsg->Set( MSG_FC_ABBREVIATE_SYMBOL );
psmsg->Display( " " );
}
LineCount += idx - 1; // Correct the linecount for the skipped lines
// Print the last element
//
PrintSequenceOfLines( pArray, idx, idx, LineCount );
} else {
//
// Print the first 'idx' elements in the array
//
PrintSequenceOfLines( pArray, 0, idx-1, LineCount );
}
}
VOID
FC::PrintSequenceOfLines(
PARRAY pArray,
ULONG Start,
ULONG End,
ULONG LineCount
)
/*++
Routine Description:
Display the contents of an array to the screen using the message
class (and the global pointer to the message class for stdin/stdout).
Arguments:
pArray - A pointer to the array that is to be dumped.
Start - Index of the first element to be displayed.
End - Index of the last element to be displayed.
LineCount - The line count of the first element to be displayed.
Return Value:
None.
Notes:
--*/
{
ULONG i;
if( Start > End ) {
DbgAbort( "Invalid values for Start and End \n" );
return;
}
for( i = Start; i <= End; i++ ) {
if( _LineNumber ) {
LineCount;
psmsg->Set( MSG_FC_NUMBERED_DATA );
psmsg->Display( "%5d %W", LineCount, (PWSTRING)pArray->GetAt( i ) );
} else {
psmsg->Set( MSG_FC_DATA );
psmsg->Display( "%W", (PWSTRING)pArray->GetAt( i ) );
}
}
}
BOOLEAN
FC::FillEmptyStringArray(
PARRAY Array
)
/*++
Routine Description:
Fill an array with WSTRINGS initialized with "".
It assumes that the array is empty and that it was previously initialized.
Arguments:
Array - A pointer to an empty and initialized array.
Return Value:
BOOLEAN - Returns TRUE if the operation succeeds.
Notes:
--*/
{
PWSTRING String;
while( Array->QueryMemberCount() < Array->QueryCapacity() ) {
if( ( String = NEW WSTRING ) == NULL ) {
DbgAbort( "String = NEW WSTRING failed!\n" );
}
if( !String->Initialize( "" ) ) {
DbgAbort( "String->Initialize() failed!" );
return( FALSE );
}
Array->Put( String );
}
return( TRUE );
}
main(
)
{
DEFINE_CLASS_DESCRIPTOR( FC );
{
FC Fc;
perrstk = NEW ERRSTACK;
psmsg = NEW STREAM_MESSAGE;
// Initialize the stream message for standard input, stdout
psmsg->Initialize( Get_Standard_Output_Stream(),
Get_Standard_Input_Stream() );
if( !SYSTEM::IsCorrectVersion() ) {
psmsg->Set( MSG_FC_INCORRECT_VERSION );
psmsg->Display( "" );
Fc.Destruct();
return( 0 );
}
if( !( Fc.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?
//
Fc.Destruct();
return( 1 );
}
// Do file comparison stuff...
Fc.DoCompare();
Fc.Destruct();
return( 0 );
}
}