/*++ 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 #include #include } 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 '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; } }