1320 lines
36 KiB
C++
1320 lines
36 KiB
C++
/*++
|
||
|
||
Copyright (c) 1990 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
FC
|
||
|
||
Abstract:
|
||
|
||
FC is a DOS-5 compatible file comparison utility
|
||
|
||
Author:
|
||
|
||
Ramon Juan San Andres (ramonsa) 01-May-1991
|
||
|
||
Notes:
|
||
|
||
This FC is a port of the DOS5 FC code. It has been slightly modified
|
||
to use some of the ULIB functionality (e.g. argument parsing), however
|
||
it does not make full use of the ULIB functionality (e.g. it uses
|
||
stdio.h functions for file handling).
|
||
|
||
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
|
||
/****************************************************************************
|
||
File Compare
|
||
|
||
Fcom compares two files in either a line-by-line mode or in a strict
|
||
BYTE-by-BYTE mode.
|
||
|
||
The BYTE-by-BYTE mode is simple; merely read both files and print the
|
||
offsets where they differ and the contents.
|
||
|
||
The line compare mode attempts to isolate differences in ranges of lines.
|
||
Two buffers of lines are read and compared. No hashing of lines needs
|
||
to be done; hashing only speedily tells you when things are different,
|
||
not the same. Most files run through this are expected to be largely
|
||
the same. Thus, hashing buys nothing.
|
||
|
||
|
||
***********************************************************************
|
||
The algorithm that immediately follows does not work. There is an error
|
||
somewhere in the range of lines 11 on. An alternative explanation follows.
|
||
KGS
|
||
************************************************************************
|
||
|
||
[0] Fill buffers
|
||
[1] If both buffers are empty then
|
||
[1.1] Done
|
||
[2] Adjust buffers so 1st differing lines are at top.
|
||
[3] If buffers are empty then
|
||
[3.1] Goto [0]
|
||
|
||
This is the difficult part. We assume that there is a sequence of inserts,
|
||
deletes and replacements that will bring the buffers back into alignment.
|
||
|
||
[4] xd = yd = FALSE
|
||
[5] xc = yc = 1
|
||
[6] xp = yp = 1
|
||
[7] If buffer1[xc] and buffer2[yp] begin a "sync" range then
|
||
[7.1] Output lines 1 through xc-1 in buffer 1
|
||
[7.2] Output lines 1 through yp-1 in buffer 2
|
||
[7.3] Adjust buffer 1 so line xc is at beginning
|
||
[7.4] Adjust buffer 2 so line yp is at beginning
|
||
[7.5] Goto [0]
|
||
[8] If buffer1[xp] and buffer2[yc] begin a "sync" range then
|
||
[8.1] Output lines 1 through xp-1 in buffer 1
|
||
[8.2] Output lines 1 through yc-1 in buffer 2
|
||
[8.3] Adjust buffer 1 so line xp is at beginning
|
||
[8.4] Adjust buffer 2 so line yc is at beginning
|
||
[8.5] Goto [0]
|
||
[9] xp = xp + 1
|
||
[10] if xp > xc then
|
||
[10.1] xp = 1
|
||
[10.2] xc = xc + 1
|
||
[10.3] if xc > number of lines in buffer 1 then
|
||
[10.4] xc = number of lines
|
||
[10.5] xd = TRUE
|
||
[11] if yp > yc then
|
||
[11.1] yp = 1
|
||
[11.2] yc = yc + 1
|
||
[11.3] if yc > number of lines in buffer 2 then
|
||
[11.4] yc = number of lines
|
||
[11.5] yd = TRUE
|
||
[12] if not xd or not yd then
|
||
[12.1] goto [6]
|
||
|
||
At this point there is no possible match between the buffers. For
|
||
simplicity, we punt.
|
||
|
||
[13] Display error message.
|
||
|
||
EXPLANATION 2
|
||
|
||
This is a variation of the Largest Common Subsequence problem. A
|
||
detailed explanation of this can be found on p 189 of Data Structures
|
||
and Algorithms by Aho Hopcroft and Ulman.
|
||
|
||
FC maintains two buffers within which it tries to find the Largest Common
|
||
Subsequence (The largest common subsequence is simply the pattern in
|
||
buffer1 that yields the most matches with the pattern in buffer2, or the
|
||
pattern in buffer2 that yields the most matches with the pattern in buffer1)
|
||
|
||
FC makes a simplifying assumption that the contents of one buffer can be
|
||
converted to the contents of the other buffer by deleting the lines that are
|
||
different between the two buffers.
|
||
|
||
Two indices into each buffer are maintained:
|
||
|
||
xc, yc == point to the last line that has been scanned up to now
|
||
|
||
xp, yp == point to the first line that has not been exhaustively
|
||
compared to lines 0 - #c in the other buffer.
|
||
|
||
FC now makes a second simplifying assumption:
|
||
It is unnecessary to do any calculations on lines that are equal.
|
||
|
||
Hence FC scans File1 and File two line by line until a difference is
|
||
encountered.
|
||
|
||
When a difference is encountered the two buffers are filled such that
|
||
the line containing the first difference heads the buffer. The following
|
||
exhaustive search algorithm is applied to find the first "sync" occurance.
|
||
(The below is simplified to use == for comparison. In practice more than
|
||
one line needs to match for a "sync" to be established).
|
||
|
||
FOR xc,yc = 1; xc,yx <= sizeof( BUFFERS ); xc++, yc++
|
||
|
||
FOR xp,yp = 1; xp,yp <= xc,yc; xp++, yp++
|
||
|
||
IF ( BUFFER1[xp] == BUFFER2[yc] )
|
||
|
||
Then the range of lines BUFFER1[ 1 ... xp ] and
|
||
BUFFER2[ 1 ... yc ] need to be deleted for the
|
||
two files to be equal. Therefore DISPLAY these
|
||
ranges, and begin scanning both files starting at
|
||
the matching lines.
|
||
FI
|
||
|
||
IF ( BUFFER1[yp] == BUFFER2[xc] )
|
||
|
||
Then the range of lines BUFFER2[ 1 ... yp ] and
|
||
BUFFER1[ 1 ... xc ] need to be deleted for the
|
||
two files to be equal. Therefore DISPLAY these
|
||
ranges, and begin scanning both files starting at
|
||
the matching lines.
|
||
FI
|
||
FOREND
|
||
FOREND
|
||
|
||
If a match is not found within the buffers, the message "RESYNC FAILED"
|
||
is issued and further comparison is aborted since there is no valid way
|
||
to find further matching lines.
|
||
|
||
END EXPLANATION 2
|
||
|
||
Certain flags may be set to modify the behavior of the comparison:
|
||
|
||
-a abbreviated output. Rather than displaying all of the modified
|
||
ranges, just display the beginning, ... and the ending difference
|
||
-b compare the files in binary (or BYTE-by-BYTE) mode. This mode is
|
||
default on .EXE, .OBJ, .LIB, .COM, .BIN, and .SYS files
|
||
-c ignore case on compare (cmp = strcmpi instead of strcmp)
|
||
-l compare files in line-by-line mode
|
||
-lb n set the size of the internal line buffer to n lines from default
|
||
of 100
|
||
-u Files to be compared are UNICODE text files
|
||
-w ignore blank lines and white space (ignore len 0, use strcmps)
|
||
-t do not untabify (use fgets instead of fgetl)
|
||
-n output the line number also
|
||
-NNNN set the number of lines to resynchronize to n which defaults
|
||
to 2. Failure to have this value set correctly can result in
|
||
odd output:
|
||
file1: file2:
|
||
abcdefg abcdefg
|
||
aaaaaaa aaaaaab
|
||
aaaaaaa aaaaaaa
|
||
aaaaaaa aaaaaaa
|
||
abcdefg abcdefg
|
||
|
||
with default sync of 2 yields: with sync => 3 yields:
|
||
|
||
*****f1 *****f1
|
||
abcdefg abcdefg
|
||
aaaaaaa aaaaaaa
|
||
*****f2 aaaaaaa
|
||
abcdefg *****f2
|
||
aaaaaab abcdefg
|
||
aaaaaaa aaaaaab
|
||
aaaaaaa
|
||
*****f1
|
||
aaaaaaa
|
||
aaaaaaa
|
||
abcdefg
|
||
*****f2
|
||
aaaaaaa
|
||
abcdefg
|
||
|
||
WARNING:
|
||
This program makes use of GOTO's and hence is not as straightforward
|
||
as it could be! CAVEAT PROGRAMMER.
|
||
****************************************************************************/
|
||
|
||
|
||
#include "ulib.hxx"
|
||
#include "fc.hxx"
|
||
#include "arg.hxx"
|
||
#include "array.hxx"
|
||
#include "arrayit.hxx"
|
||
#include "bytestrm.hxx"
|
||
#include "dir.hxx"
|
||
#include "file.hxx"
|
||
#include "filestrm.hxx"
|
||
#include "filter.hxx"
|
||
#include "mbstr.hxx"
|
||
#include "system.hxx"
|
||
#include "wstring.hxx"
|
||
|
||
#include <malloc.h>
|
||
#include <process.h>
|
||
#include <stdlib.h>
|
||
#include <math.h>
|
||
|
||
|
||
/**************************************************************************/
|
||
/* main */
|
||
/**************************************************************************/
|
||
|
||
INT _CRTAPI1
|
||
main (
|
||
)
|
||
{
|
||
DEFINE_CLASS_DESCRIPTOR( FC );
|
||
|
||
{
|
||
FC Fc;
|
||
|
||
if ( Fc.Initialize() ) {
|
||
|
||
return Fc.Fcmain();
|
||
}
|
||
}
|
||
|
||
return FAILURE;
|
||
}
|
||
|
||
|
||
|
||
|
||
CHAR *ExtBin[] = { "EXE", "OBJ", "LIB",
|
||
"COM", "BIN", "SYS", NULL };
|
||
|
||
|
||
|
||
|
||
DEFINE_CONSTRUCTOR( FC, PROGRAM );
|
||
|
||
|
||
FC::~FC () {
|
||
}
|
||
|
||
|
||
|
||
BOOLEAN FC::Initialize() {
|
||
|
||
|
||
if ( PROGRAM::Initialize() ) {
|
||
|
||
ValidateVersion();
|
||
|
||
ctSync = -1; // number of lines required to sync
|
||
cLine = -1; // number of lines in internal buffs
|
||
|
||
fAbbrev = FALSE; // abbreviated output
|
||
fBinary = FALSE; // binary comparison
|
||
fLine = FALSE; // line comparison
|
||
fNumb = FALSE; // display line numbers
|
||
fCase = TRUE; // case is significant
|
||
fIgnore = FALSE; // ignore spaces and blank lines
|
||
|
||
#ifdef DEBUG
|
||
fDebug = FALSE;
|
||
#endif
|
||
|
||
fExpandTabs = TRUE;
|
||
// funcRead = (int (*)(char *,int,FILE *))fgetl;
|
||
|
||
extBin = (CHAR **)ExtBin;
|
||
|
||
return ParseArguments();
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
BOOLEAN
|
||
FC::ParseArguments(
|
||
)
|
||
{
|
||
|
||
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;
|
||
FLAG_ARGUMENT FlagUnicode;
|
||
LONG_ARGUMENT LongBufferSize;
|
||
#ifdef DEBUG
|
||
FLAG_ARGUMENT FlagDebug;
|
||
#endif
|
||
STRING_ARGUMENT LongMatch;
|
||
PATH_ARGUMENT InFile1;
|
||
PATH_ARGUMENT InFile2;
|
||
|
||
LONG Long;
|
||
INT i;
|
||
|
||
|
||
if( !LexArray.Initialize() ) {
|
||
DebugAbort( "LexArray.Initialize() Failed!\n" );
|
||
return( FALSE );
|
||
}
|
||
if( !ArgLex.Initialize(&LexArray) ) {
|
||
DebugAbort( "ArgLex.Initialize() Failed!\n" );
|
||
return( FALSE );
|
||
}
|
||
|
||
// Allow only the '/' as a valid switch
|
||
ArgLex.PutSwitches("/");
|
||
ArgLex.SetCaseSensitive( FALSE );
|
||
|
||
ArgLex.PutStartQuotes("\"");
|
||
ArgLex.PutEndQuotes("\"");
|
||
ArgLex.PutSeparators(" \t");
|
||
|
||
if( !ArgLex.PrepareToParse() ) {
|
||
DebugAbort( "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("/?") ||
|
||
!FlagUnicode.Initialize("/U") ||
|
||
#ifdef DEBUG
|
||
!FlagDebug.Initialize("/D") ||
|
||
#endif
|
||
!LongBufferSize.Initialize("/LB#") ||
|
||
!LongMatch.Initialize("/*") ||
|
||
!InFile1.Initialize("*") ||
|
||
!InFile2.Initialize("*") ) {
|
||
|
||
DebugAbort( "Unable to Initialize some or all of the Arguments!\n" );
|
||
return( FALSE );
|
||
}
|
||
|
||
|
||
if( !ArrayOfArg.Initialize() ) {
|
||
DebugAbort( "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(&FlagUnicode) ||
|
||
#ifdef DEBUG
|
||
!ArrayOfArg.Put(&FlagDebug) ||
|
||
#endif
|
||
!ArrayOfArg.Put(&LongBufferSize) ||
|
||
!ArrayOfArg.Put(&LongMatch) ||
|
||
!ArrayOfArg.Put(&InFile1) ||
|
||
!ArrayOfArg.Put(&InFile2) ) {
|
||
|
||
DebugAbort( "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...)
|
||
//
|
||
DisplayMessage( MSG_FC_INVALID_SWITCH, ERROR_MESSAGE, "" );
|
||
// return( FALSE );
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// It should now be safe to test the arguments for their values...
|
||
//
|
||
if( FlagRequestHelp.QueryFlag() ) {
|
||
DisplayMessage( MSG_FC_HELP_MESSAGE, NORMAL_MESSAGE, "" );
|
||
return( FALSE );
|
||
}
|
||
|
||
if( FlagBinaryCompare.QueryFlag() &&
|
||
( FlagAsciiCompare.QueryFlag() || FlagLineNumber.QueryFlag() ) ) {
|
||
|
||
DisplayMessage( MSG_FC_INCOMPATIBLE_SWITCHES, ERROR_MESSAGE, "" );
|
||
return( FALSE );
|
||
}
|
||
|
||
if( !InFile1.IsValueSet() ||
|
||
!InFile2.IsValueSet() ) {
|
||
DisplayMessage( MSG_FC_INSUFFICIENT_FILES, ERROR_MESSAGE, "" );
|
||
return( FALSE );
|
||
}
|
||
|
||
|
||
//
|
||
// Convert filenames to upper case
|
||
//
|
||
_File1.Initialize( InFile1.GetPath() );
|
||
_File2.Initialize( InFile2.GetPath() );
|
||
|
||
((PWSTRING)_File1.GetPathString())->Strupr();
|
||
((PWSTRING)_File2.GetPathString())->Strupr();
|
||
|
||
|
||
fUnicode = FlagUnicode.QueryFlag();
|
||
fAbbrev = FlagAbbreviate.QueryFlag();
|
||
fCase = !FlagCaseInsensitive.QueryFlag();
|
||
fIgnore = FlagCompression.QueryFlag();
|
||
fNumb = FlagLineNumber.QueryFlag();
|
||
fBinary = FlagBinaryCompare.QueryFlag();
|
||
|
||
if ( FlagExpansion.QueryFlag() ) {
|
||
fExpandTabs = FALSE;
|
||
//funcRead = (int (*)(char *,int,FILE *))fgets;
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
fDebug = FlagDebug.QueryFlag();
|
||
#endif
|
||
|
||
|
||
if ( LongBufferSize.IsValueSet() ) {
|
||
cLine = (INT)LongBufferSize.QueryLong();
|
||
fLine = TRUE;
|
||
} else {
|
||
cLine = 100;
|
||
}
|
||
|
||
if ( LongMatch.IsValueSet() ) {
|
||
if ( LongMatch.GetString()->QueryNumber( &Long ) ) {
|
||
ctSync = (INT)Long;
|
||
fLine = TRUE;
|
||
} else {
|
||
|
||
DisplayMessage( MSG_FC_INVALID_SWITCH, ERROR_MESSAGE, "" );
|
||
ctSync = 2;
|
||
}
|
||
} else {
|
||
ctSync = 2;
|
||
}
|
||
|
||
if (!fBinary && !fLine) {
|
||
|
||
DSTRING ExtBin;
|
||
PWSTRING Ext = _File1.QueryExt();
|
||
|
||
if ( Ext ) {
|
||
for (i=0; extBin[i]; i++) {
|
||
ExtBin.Initialize( extBin[i] );
|
||
if ( !ExtBin.Stricmp( Ext ) ) {
|
||
fBinary = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
DELETE( Ext );
|
||
}
|
||
|
||
if (!fBinary) {
|
||
fLine = TRUE;
|
||
}
|
||
}
|
||
|
||
if (!fUnicode) {
|
||
if (fIgnore) {
|
||
if (fCase) {
|
||
fCmp = MBSTR::Strcmps;
|
||
} else {
|
||
fCmp = MBSTR::Strcmpis;
|
||
}
|
||
} else {
|
||
|
||
if (fCase) {
|
||
fCmp = MBSTR::Strcmp;
|
||
} else {
|
||
fCmp = MBSTR::Stricmp;
|
||
}
|
||
}
|
||
} else {
|
||
if (fIgnore) {
|
||
if (fCase) {
|
||
fCmp_U = WSTRING::Strcmps;
|
||
} else {
|
||
fCmp_U = WSTRING::Strcmpis;
|
||
}
|
||
} else {
|
||
|
||
if (fCase) {
|
||
fCmp_U = WSTRING::Strcmp;
|
||
} else {
|
||
fCmp_U = WSTRING::Stricmp;
|
||
}
|
||
}
|
||
}
|
||
|
||
return( TRUE );
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
INT
|
||
FC::Fcmain
|
||
(
|
||
)
|
||
{
|
||
ParseFileNames();
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
|
||
/**************************************************************************/
|
||
/* BinaryCompare */
|
||
/**************************************************************************/
|
||
|
||
int FC::BinaryCompare (char *f1, char *f2)
|
||
{
|
||
|
||
DSTRING FileStr;
|
||
PATH FileName;
|
||
PFSN_FILE File1 = NULL;
|
||
PFSN_FILE File2 = NULL;
|
||
PFILE_STREAM Stream1 = NULL;
|
||
PFILE_STREAM Stream2 = NULL;;
|
||
BYTE_STREAM Bs1;
|
||
BYTE_STREAM Bs2;
|
||
BYTE c1, c2;
|
||
DWORD pos;
|
||
BOOLEAN fSame;
|
||
char buffer[128];
|
||
int Result = 0;
|
||
|
||
|
||
if ( !FileStr.Initialize( f1 ) ||
|
||
!FileName.Initialize( &FileStr ) ||
|
||
!(File1 = SYSTEM::QueryFile( &FileName )) ||
|
||
!(Stream1 = File1->QueryStream( READ_ACCESS )) ||
|
||
!Bs1.Initialize( Stream1 )
|
||
) {
|
||
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%s", f1 );
|
||
Result = 1;
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
// goto Exit;
|
||
}
|
||
|
||
if ( !FileStr.Initialize( f2 ) ||
|
||
!FileName.Initialize( &FileStr ) ||
|
||
!(File2 = SYSTEM::QueryFile( &FileName )) ||
|
||
!(Stream2 = File2->QueryStream( READ_ACCESS )) ||
|
||
!Bs2.Initialize( Stream2 )
|
||
) {
|
||
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%s", f2 );
|
||
Result = 1;
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
// goto Exit;
|
||
}
|
||
|
||
fSame = TRUE;
|
||
pos = 0L;
|
||
|
||
while ( TRUE ) {
|
||
|
||
if ( Bs1.ReadByte( &c1 ) ) {
|
||
|
||
if ( Bs2.ReadByte( &c2 ) ) {
|
||
|
||
if (c1 != c2) {
|
||
|
||
fSame = FALSE;
|
||
sprintf( buffer, "%08lX: %02X %02X", pos, c1, c2 );
|
||
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%s", buffer );
|
||
// DisplayMessage( MSG_FC_HEX_OUT, NORMAL_MESSAGE, "%08lX %02X %02X", pos, c1, c2 );
|
||
}
|
||
|
||
} else {
|
||
|
||
DisplayMessage( MSG_FC_FILES_DIFFERENT_LENGTH, NORMAL_MESSAGE, "%s%s", f1, f2 );
|
||
Result = 1;
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
// goto Exit;
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
if ( Bs2.ReadByte( &c2 ) ) {
|
||
|
||
DisplayMessage( MSG_FC_FILES_DIFFERENT_LENGTH, NORMAL_MESSAGE, "%s%s", f2, f1 );
|
||
Result = 1;
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
//goto Exit;
|
||
|
||
} else {
|
||
|
||
if (fSame) {
|
||
DisplayMessage( MSG_FC_NO_DIFFERENCES, NORMAL_MESSAGE );
|
||
}
|
||
|
||
Result = 0;
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
// goto Exit;
|
||
|
||
}
|
||
}
|
||
|
||
pos++;
|
||
}
|
||
|
||
// Exit:
|
||
|
||
DELETE( Stream2 );
|
||
DELETE( File2 );
|
||
DELETE( Stream1 );
|
||
DELETE( File1 );
|
||
return Result;
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* Compare a range of lines. */
|
||
/**************************************************************************/
|
||
|
||
BOOLEAN FC::compare (int l1, register int s1, int l2, register int s2, int ct)
|
||
{
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("compare (%d, %d, %d, %d, %d)\n", l1, s1, l2, s2, ct);
|
||
#endif
|
||
|
||
if (ct <= 0 || s1+ct > l1 || s2+ct > l2)
|
||
return (FALSE);
|
||
|
||
while (ct--)
|
||
{
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("'%s' == '%s'? ", buffer1[s1].text, buffer2[s2].text);
|
||
#endif
|
||
if(!fUnicode) {
|
||
if ((*fCmp)(buffer1[s1++].text, buffer2[s2++].text)) {
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("No\n");
|
||
#endif
|
||
return (FALSE);
|
||
}
|
||
} else {
|
||
if ((*fCmp_U)(buffer1[s1++].wtext, buffer2[s2++].wtext)) {
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("No\n");
|
||
#endif
|
||
return (FALSE);
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("Yes\n");
|
||
#endif
|
||
|
||
return (TRUE);
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* LineCompare */
|
||
/**************************************************************************/
|
||
|
||
|
||
void FC::LineCompare (char *f1, char *f2)
|
||
{
|
||
|
||
DSTRING FileStr;
|
||
PATH FileName;
|
||
PFSN_FILE File1 = NULL;
|
||
PFSN_FILE File2 = NULL;
|
||
PFILE_STREAM Stream1 = NULL;
|
||
PFILE_STREAM Stream2 = NULL;;
|
||
|
||
|
||
buffer1 = buffer2 = NULL;
|
||
|
||
if ( !FileStr.Initialize( f1 ) ||
|
||
!FileName.Initialize( &FileStr ) ||
|
||
!(File1 = SYSTEM::QueryFile( &FileName )) ||
|
||
!(Stream1 = File1->QueryStream( READ_ACCESS ))
|
||
) {
|
||
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%s", f1 );
|
||
FREE(buffer1);
|
||
FREE(buffer2);
|
||
DELETE(Stream2);
|
||
DELETE(File2);
|
||
DELETE(Stream1);
|
||
DELETE(File1);
|
||
return;
|
||
//goto Exit;
|
||
}
|
||
|
||
if ( !FileStr.Initialize( f2 ) ||
|
||
!FileName.Initialize( &FileStr ) ||
|
||
!(File2 = SYSTEM::QueryFile( &FileName )) ||
|
||
!(Stream2 = File2->QueryStream( READ_ACCESS ))
|
||
) {
|
||
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%s", f2 );
|
||
FREE(buffer1);
|
||
FREE(buffer2);
|
||
DELETE(Stream2);
|
||
DELETE(File2);
|
||
DELETE(Stream1);
|
||
DELETE(File1);
|
||
return;
|
||
//goto Exit;
|
||
}
|
||
|
||
|
||
if ( (buffer1 = (struct lineType *)MALLOC(cLine * (sizeof *buffer1))) == NULL ||
|
||
(buffer2 = (struct lineType *)MALLOC(cLine * (sizeof *buffer1))) == NULL) {
|
||
|
||
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
||
FREE(buffer1);
|
||
FREE(buffer2);
|
||
DELETE(Stream2);
|
||
DELETE(File2);
|
||
DELETE(Stream1);
|
||
DELETE(File1);
|
||
return;
|
||
//goto Exit;
|
||
}
|
||
|
||
RealLineCompare( f1, f2, Stream1, Stream2 );
|
||
|
||
// Exit:
|
||
FREE(buffer1);
|
||
FREE(buffer2);
|
||
DELETE(Stream2);
|
||
DELETE(File2);
|
||
DELETE(Stream1);
|
||
DELETE(File1);
|
||
|
||
|
||
}
|
||
|
||
void FC::RealLineCompare (char *f1, char *f2, PSTREAM Stream1, PSTREAM Stream2 )
|
||
{
|
||
|
||
int l1, l2, i, xp, yp, xc, yc;
|
||
BOOLEAN xd, yd, fSame;
|
||
int line1, line2;
|
||
|
||
|
||
fSame = TRUE;
|
||
l1 = l2 = 0;
|
||
line1 = line2 = 0;
|
||
|
||
l0:
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug) {
|
||
DebugPrintf ("At scan beginning\n");
|
||
}
|
||
#endif
|
||
|
||
l1 += xfill (buffer1+l1, Stream1, cLine-l1, &line1);
|
||
l2 += xfill (buffer2+l2, Stream2, cLine-l2, &line2);
|
||
|
||
if (l1 == 0 && l2 == 0) {
|
||
|
||
if (fSame) {
|
||
DisplayMessage( MSG_FC_NO_DIFFERENCES, NORMAL_MESSAGE );
|
||
}
|
||
return;
|
||
}
|
||
|
||
xc = min (l1, l2);
|
||
|
||
for (i=0; i < xc; i++) {
|
||
|
||
if (!compare (l1, i, l2, i, 1)) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (i != xc) {
|
||
// i = max (i-1, 0);
|
||
i = ( i-1 > 0 )? ( i-1 ) : 0;
|
||
}
|
||
|
||
l1 = adjust (buffer1, l1, i);
|
||
l2 = adjust (buffer2, l2, i);
|
||
|
||
if (l1 == 0 && l2 == 0) {
|
||
goto l0;
|
||
}
|
||
|
||
l1 += xfill (buffer1+l1, Stream1, cLine-l1, &line1);
|
||
l2 += xfill (buffer2+l2, Stream2, cLine-l2, &line2);
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug) {
|
||
DebugPrintf ("buffers are adjusted, %d, %d remain\n", l1, l2);
|
||
}
|
||
#endif
|
||
|
||
xd = yd = FALSE;
|
||
xc = yc = 1;
|
||
xp = yp = 1;
|
||
|
||
l6:
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug) {
|
||
DebugPrintf ("Trying resync %d,%d %d,%d\n", xc, xp, yc, yp);
|
||
}
|
||
#endif
|
||
|
||
i = min (l1-xc,l2-yp);
|
||
i = min (i, ctSync);
|
||
|
||
if (compare (l1, xc, l2, yp, i)) {
|
||
|
||
fSame = FALSE;
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f1 );
|
||
dump (buffer1, 0, xc);
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f2 );
|
||
dump (buffer2, 0, yp);
|
||
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
||
|
||
l1 = adjust (buffer1, l1, xc);
|
||
l2 = adjust (buffer2, l2, yp);
|
||
|
||
goto l0;
|
||
}
|
||
|
||
i = min (l1-xp, l2-yc);
|
||
i = min (i, ctSync);
|
||
|
||
if (compare (l1, xp, l2, yc, i)) {
|
||
|
||
fSame = FALSE;
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f1 );
|
||
dump (buffer1, 0, xp);
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f2 );
|
||
dump (buffer2, 0, yc);
|
||
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
||
|
||
l1 = adjust (buffer1, l1, xp);
|
||
l2 = adjust (buffer2, l2, yc);
|
||
|
||
goto l0;
|
||
}
|
||
|
||
if (++xp > xc) {
|
||
|
||
xp = 1;
|
||
if (++xc >= l1) {
|
||
|
||
xc = l1;
|
||
xd = TRUE;
|
||
}
|
||
}
|
||
|
||
if (++yp > yc) {
|
||
|
||
yp = 1;
|
||
if (++yc >= l2) {
|
||
|
||
yc = l2;
|
||
yd = TRUE;
|
||
}
|
||
}
|
||
|
||
if (!xd || !yd) {
|
||
goto l6;
|
||
}
|
||
|
||
fSame = FALSE;
|
||
|
||
if (l1 >= cLine || l2 >= cLine) {
|
||
DisplayMessage( MSG_FC_RESYNC_FAILED, NORMAL_MESSAGE );
|
||
}
|
||
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f1 );
|
||
dump (buffer1, 0, l1-1);
|
||
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%s", f2 );
|
||
dump (buffer2, 0, l2-1);
|
||
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* Return number of lines read in. */
|
||
/**************************************************************************/
|
||
|
||
FC::xfill (struct lineType *pl, PSTREAM Stream, int ct, int *plnum)
|
||
{
|
||
int i;
|
||
DWORD StrSize;
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("xfill (%04x, %04x)\n", pl, fh);
|
||
#endif
|
||
|
||
i = 0;
|
||
|
||
if (!fUnicode) {
|
||
while ( ct-- && !Stream->IsAtEnd() && Stream->ReadMbLine( pl->text, MAXLINESIZE, &StrSize, fExpandTabs, 8 ) ) {
|
||
|
||
if (fIgnore && !MBSTR::Strcmps(pl->text, "")) {
|
||
|
||
pl->text[0] = 0;
|
||
++*plnum;
|
||
}
|
||
|
||
if (strlen (pl->text) != 0 || !fIgnore)
|
||
{
|
||
pl->line = ++*plnum;
|
||
pl++;
|
||
i++;
|
||
}
|
||
}
|
||
} else {
|
||
while( ct-- && !Stream->IsAtEnd() && Stream->ReadWLine( pl->wtext,MAXLINESIZE, &StrSize, fExpandTabs, 8 ) ) {
|
||
//while( ct-- && !Stream->IsAtEnd() && Stream->ReadLine( &_String , TRUE )) {
|
||
|
||
//_String.QueryWSTR(0,TO_END,pl->wtext,MAXLINESIZE,TRUE);
|
||
|
||
if (fIgnore && !WSTRING::Strcmps((PWSTR)pl->wtext, (PWSTR)L"")) {
|
||
|
||
pl->wtext[0] = 0;
|
||
++*plnum;
|
||
}
|
||
|
||
if (wcslen (pl->wtext) != 0 || !fIgnore)
|
||
{
|
||
pl->line = ++*plnum;
|
||
pl++;
|
||
i++;
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("xfill returns %d\n", i);
|
||
#endif
|
||
|
||
return (i);
|
||
|
||
#if 0
|
||
|
||
while (ct-- && (*funcRead) (pl->text, MAXLINESIZE, fh) != NULL)
|
||
{
|
||
if (funcRead == ( int (*) (char *,int, FILE *))fgets)
|
||
pl->text[strlen(pl->text)-1] = 0;
|
||
if (fIgnore && !MBSTR::Strcmps(pl->text, ""))
|
||
{
|
||
pl->text[0] = 0;
|
||
++*plnum;
|
||
}
|
||
if (strlen (pl->text) != 0 || !fIgnore)
|
||
{
|
||
pl->line = ++*plnum;
|
||
pl++;
|
||
i++;
|
||
}
|
||
}
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("xfill returns %d\n", i);
|
||
#endif
|
||
|
||
return (i);
|
||
|
||
# endif
|
||
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* Adjust returns number of lines in buffer. */
|
||
/**************************************************************************/
|
||
|
||
FC::adjust (struct lineType *pl, int ml, int lt)
|
||
{
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("adjust (%04x, %d, %d) = ", pl, ml, lt);
|
||
if (fDebug)
|
||
DebugPrintf ("%d\n", ml-lt);
|
||
#endif
|
||
|
||
if (ml <= lt)
|
||
return (0);
|
||
|
||
#ifdef DEBUG
|
||
if (fDebug)
|
||
DebugPrintf ("move (%04x, %04x, %04x)\n", &pl[lt], &pl[0], sizeof (*pl)*(ml-lt));
|
||
#endif
|
||
|
||
// Move((char *)&pl[lt], (char *)&pl[0], sizeof (*pl)*(ml-lt));
|
||
memmove( (char *)&pl[0], (char *)&pl[lt], sizeof (*pl)*(ml-lt) );
|
||
return ml-lt;
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* dump */
|
||
/* dump outputs a range of lines. */
|
||
/* */
|
||
/* INPUTS */
|
||
/* pl pointer to current lineType structure */
|
||
/* start starting line number */
|
||
/* end ending line number */
|
||
/* */
|
||
/* CALLS */
|
||
/* pline, printf */
|
||
/**************************************************************************/
|
||
|
||
void FC::dump (struct lineType *pl, int start, int end)
|
||
{
|
||
if (fAbbrev && end-start > 2)
|
||
{
|
||
pline (pl+start);
|
||
DisplayMessage( MSG_FC_ABBREVIATE_SYMBOL, NORMAL_MESSAGE );
|
||
pline (pl+end);
|
||
}
|
||
else
|
||
{
|
||
while (start <= end)
|
||
pline (pl+start++);
|
||
}
|
||
}
|
||
|
||
|
||
/**************************************************************************/
|
||
/* PrintLINE */
|
||
/* pline prints a single line of output. If the /n flag */
|
||
/* has been specified, the line number of the printed text is added. */
|
||
/* */
|
||
/* Inputs */
|
||
/* pl pointer to current lineType structure */
|
||
/* fNumb TRUE if /n specified */
|
||
/**************************************************************************/
|
||
|
||
void FC::pline (struct lineType *pl)
|
||
{
|
||
if (!fUnicode) {
|
||
if (fNumb)
|
||
DisplayMessage( MSG_FC_NUMBERED_DATA, NORMAL_MESSAGE, "%5d%s",
|
||
pl->line, pl->text );
|
||
else
|
||
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%s", pl->text );
|
||
} else {
|
||
FSTRING f;
|
||
if (fNumb)
|
||
DisplayMessage( MSG_FC_NUMBERED_DATA, NORMAL_MESSAGE, "%5d%W",
|
||
pl->line, f.Initialize(pl->wtext) );
|
||
else
|
||
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%W",
|
||
f.Initialize(pl->wtext) );
|
||
}
|
||
}
|
||
|
||
|
||
/*********************************************************************/
|
||
/* Routine: ParseFileNames */
|
||
/* */
|
||
/* Function: Parses the two given filenames and then compares the */
|
||
/* appropriate filenames. This routine handles wildcard */
|
||
/* characters in both filenames. */
|
||
/*********************************************************************/
|
||
|
||
void FC::ParseFileNames()
|
||
{
|
||
PATH File1;
|
||
PATH File2;
|
||
FSN_FILTER Filter;
|
||
PWSTRING Name;
|
||
PARRAY NodeArray;
|
||
PITERATOR Iterator;
|
||
PFSN_DIRECTORY Dir;
|
||
PFSNODE File;
|
||
PPATH ExpandedPath;
|
||
PATH TmpPath;
|
||
|
||
File1.Initialize( &_File1 );
|
||
File2.Initialize( &_File2 );
|
||
|
||
Filter.Initialize();
|
||
if (!(Name = File1.QueryName())) {
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File1.GetPathString() );
|
||
return;
|
||
}
|
||
Filter.SetFileName( Name );
|
||
DELETE( Name );
|
||
Filter.SetAttributes( (FSN_ATTRIBUTE)0, // ALL
|
||
FSN_ATTRIBUTE_FILES, // ANY
|
||
FSN_ATTRIBUTE_DIRECTORY ); // NONE
|
||
|
||
|
||
TmpPath.Initialize( &File1, TRUE );
|
||
TmpPath.TruncateBase();
|
||
|
||
if ( !( Dir = SYSTEM::QueryDirectory( &TmpPath ) ) ||
|
||
!(NodeArray = Dir->QueryFsnodeArray( &Filter )) ||
|
||
!(Iterator = NodeArray->QueryIterator()) ||
|
||
!(File = (PFSNODE)Iterator->GetNext())
|
||
) {
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File1.GetPathString() );
|
||
return;
|
||
}
|
||
|
||
Iterator->Reset();
|
||
|
||
while ( File = (PFSNODE)Iterator->GetNext() ) {
|
||
|
||
PWSTRING Name1;
|
||
PWSTRING Name2;
|
||
|
||
if (!(Name1 = File->QueryName())) {
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File->GetPath()->GetPathString() );
|
||
return;
|
||
}
|
||
|
||
if ( _File2.HasWildCard() ) {
|
||
|
||
if ( !(ExpandedPath = _File2.QueryWCExpansion( (PPATH)File->GetPath() ))) {
|
||
|
||
if (!(Name2 = _File2.QueryName())) {
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", _File2.GetPathString() );
|
||
return;
|
||
}
|
||
|
||
DisplayMessage( MSG_FC_CANT_EXPAND_TO_MATCH, ERROR_MESSAGE, "%W%W", Name1, Name2 );
|
||
DELETE( Name2 );
|
||
DELETE( Name1 );
|
||
DELETE( Iterator );
|
||
DELETE( NodeArray );
|
||
DELETE( Dir );
|
||
return;
|
||
}
|
||
|
||
} else {
|
||
|
||
if ( !(ExpandedPath = NEW PATH) ||
|
||
!ExpandedPath->Initialize( &_File2 ) ) {
|
||
|
||
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
||
DELETE( Name1 );
|
||
DELETE( Iterator );
|
||
DELETE( NodeArray );
|
||
DELETE( Dir );
|
||
return;
|
||
|
||
}
|
||
}
|
||
|
||
if (!(Name2 = ExpandedPath->QueryName())) {
|
||
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", ExpandedPath->GetPathString() );
|
||
return;
|
||
}
|
||
File1.SetName( Name1 );
|
||
File2.SetName( Name2 );
|
||
|
||
comp( File1.GetPathString()->QuerySTR(), File2.GetPathString()->QuerySTR() );
|
||
|
||
DELETE( Name2 );
|
||
DELETE( Name1 );
|
||
DELETE( ExpandedPath );
|
||
|
||
}
|
||
|
||
DELETE( Iterator );
|
||
NodeArray->DeleteAllMembers();
|
||
DELETE( NodeArray );
|
||
DELETE( Dir );
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
/*********************************************************************/
|
||
/* Routine: comp */
|
||
/* */
|
||
/* Function: Compares the two files. */
|
||
/*********************************************************************/
|
||
|
||
void FC::comp(char *file1, char *file2)
|
||
{
|
||
DisplayMessage( MSG_FC_COMPARING_FILES, NORMAL_MESSAGE, "%s%s", file1, file2 );
|
||
if (fBinary)
|
||
BinaryCompare (file1, file2);
|
||
else {
|
||
LineCompare (file1, file2);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
/* returns line from file (no CRLFs); returns NULL if EOF */
|
||
FC::fgetl ( char *buf, int len, FILE *fh)
|
||
{
|
||
register int c;
|
||
register char *p;
|
||
|
||
/* remember NUL at end */
|
||
len--;
|
||
p = buf;
|
||
while (len) {
|
||
c = getc (fh);
|
||
if (c == EOF || c == '\n')
|
||
break;
|
||
#if MSDOS
|
||
if (c != '\r')
|
||
#endif
|
||
if (c != '\t') {
|
||
*p++ = (char)c;
|
||
len--;
|
||
}
|
||
else {
|
||
c = min (8 - ((p-buf) & 0x0007), len);
|
||
memset(p, ' ', c);
|
||
p += c;
|
||
len -= c;
|
||
}
|
||
}
|
||
*p = 0;
|
||
return ! ( (c == EOF) && (p == buf) );
|
||
}
|