Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1418 lines
41 KiB

  1. /*++
  2. Copyright (c) 1991-2000 Microsoft Corporation
  3. Module Name:
  4. Comp.cxx
  5. Abstract:
  6. Compares the contents of two files or sets of files.
  7. COMP [data1] [data2] [/D] [/A] [/L] [/N=number] [/C]
  8. data1 Specifies location and name(s) of first file(s) to compare.
  9. data2 Specifies location and name(s) of second files to compare.
  10. /D Displays differences in decimal format. This is the default
  11. setting.
  12. /A Displays differences in ASCII characters.
  13. /L Displays line numbers for differences.
  14. /N=number Compares only the first specified number of lines in each file.
  15. /C Disregards case of ASCII letters when comparing files.
  16. /OFFLINE Do not skip files with offline attribute set.
  17. To compare sets of files, use wildcards in data1 and data2 parameters.
  18. Author:
  19. Barry J. Gilhuly *** W-Barry *** Jun 91
  20. Environment:
  21. ULIB, User Mode
  22. --*/
  23. #include "ulib.hxx"
  24. #include "ulibcl.hxx"
  25. #include "arg.hxx"
  26. #include "array.hxx"
  27. #include "bytestrm.hxx"
  28. #include "dir.hxx"
  29. #include "file.hxx"
  30. #include "filestrm.hxx"
  31. #include "filter.hxx"
  32. #include "iterator.hxx"
  33. #include "path.hxx"
  34. #include "rtmsg.h"
  35. #include "system.hxx"
  36. #include "smsg.hxx"
  37. #include "comp.hxx"
  38. extern "C" {
  39. #include <ctype.h>
  40. #include <stdio.h>
  41. #include <string.h>
  42. }
  43. STREAM_MESSAGE *psmsg = NULL; // Create a pointer to the stream message
  44. // class for program output.
  45. ULONG Errlev; // The current program error level
  46. ULONG CompResult;
  47. //
  48. // Define a macro to deal with case insensitive comparisons
  49. //
  50. #define CASE_SENSITIVE( x ) ( ( _CaseInsensitive ) ? towupper( x ) : x )
  51. VOID
  52. StripQuotesFromString(
  53. IN PWSTRING String
  54. )
  55. /*++
  56. Routine Description:
  57. This routine removes leading and trailing quote marks (if
  58. present) from a quoted string. If the string is not a quoted
  59. string, it is left unchanged.
  60. --*/
  61. {
  62. if( String->QueryChCount() >= 2 &&
  63. String->QueryChAt( 0 ) == '\"' &&
  64. String->QueryChAt( String->QueryChCount() - 1 ) == '\"' ) {
  65. String->DeleteChAt( String->QueryChCount() - 1 );
  66. String->DeleteChAt( 0 );
  67. }
  68. }
  69. DEFINE_CONSTRUCTOR( COMP, PROGRAM );
  70. VOID
  71. COMP::Construct(
  72. )
  73. /*++
  74. Routine Description:
  75. Initializes the object.
  76. Arguments:
  77. None.
  78. Return Value:
  79. None.
  80. --*/
  81. {
  82. _InputPath1 = NULL;
  83. _InputPath2 = NULL;
  84. return;
  85. }
  86. VOID
  87. COMP::Destruct(
  88. )
  89. /*++
  90. Routine Description:
  91. Cleans up after finishing with an FC object.
  92. Arguments:
  93. None.
  94. Return Value:
  95. None.
  96. --*/
  97. {
  98. DELETE( psmsg );
  99. if( _InputPath1 != NULL ) {
  100. DELETE( _InputPath1 );
  101. }
  102. if( _InputPath2 != NULL ) {
  103. DELETE( _InputPath2 );
  104. }
  105. return;
  106. }
  107. BOOLEAN
  108. COMP::Initialize(
  109. )
  110. /*++
  111. Routine Description:
  112. Initializes an FC object.
  113. Arguments:
  114. None.
  115. Return Value:
  116. BOOLEAN - Indicates if the initialization succeeded.
  117. --*/
  118. {
  119. ARGUMENT_LEXEMIZER ArgLex;
  120. ARRAY LexArray;
  121. ARRAY ArrayOfArg;
  122. PATH_ARGUMENT ProgramName;
  123. FLAG_ARGUMENT FlagDecimalFormat;
  124. FLAG_ARGUMENT FlagAsciiFormat;
  125. FLAG_ARGUMENT FlagLineNumbers;
  126. FLAG_ARGUMENT FlagCaseInsensitive;
  127. FLAG_ARGUMENT FlagRequestHelp;
  128. FLAG_ARGUMENT FlagWrongNumber;
  129. FLAG_ARGUMENT FlagIncludeOffline;
  130. FLAG_ARGUMENT FlagIncludeOffline2;
  131. LONG_ARGUMENT LongMatchLines;
  132. PATH_ARGUMENT InFile1;
  133. PATH_ARGUMENT InFile2;
  134. STRING_ARGUMENT StringInvalidSwitch;
  135. WCHAR WChar;
  136. DSTRING InvalidString;
  137. _Numbered = FALSE;
  138. _Limited = FALSE;
  139. _InputPath1 = NULL;
  140. _InputPath2 = NULL;
  141. if( !LexArray.Initialize() ) {
  142. DebugPrintTrace(( "LexArray.Initialize() Failed!\n" ));
  143. Errlev = INTERNAL_ERROR;
  144. return( FALSE );
  145. }
  146. if( !ArgLex.Initialize(&LexArray) ) {
  147. DebugPrintTrace(( "ArgLex.Initialize() Failed!\n" ));
  148. Errlev = INTERNAL_ERROR;
  149. return( FALSE );
  150. }
  151. ArgLex.PutSwitches("/");
  152. ArgLex.SetCaseSensitive( FALSE );
  153. ArgLex.PutMultipleSwitch( "acdl" );
  154. ArgLex.PutSeparators( " /\t" );
  155. ArgLex.PutStartQuotes( "\"" );
  156. ArgLex.PutEndQuotes( "\"" );
  157. if( !ArgLex.PrepareToParse() ) {
  158. DebugPrintTrace(( "ArgLex.PrepareToParse() Failed!\n" ));
  159. Errlev = INTERNAL_ERROR;
  160. return( FALSE );
  161. }
  162. if( !ProgramName.Initialize("*") ||
  163. !FlagDecimalFormat.Initialize("/D") ||
  164. !FlagAsciiFormat.Initialize("/A") ||
  165. !FlagLineNumbers.Initialize("/L") ||
  166. !FlagCaseInsensitive.Initialize("/C") ||
  167. !FlagIncludeOffline.Initialize("/OFFLINE") ||
  168. !FlagIncludeOffline2.Initialize("/OFF") ||
  169. !FlagWrongNumber.Initialize("/N") ||
  170. !LongMatchLines.Initialize("/N=*") ||
  171. !FlagRequestHelp.Initialize("/?") ||
  172. !StringInvalidSwitch.Initialize("/*") ||
  173. !InFile1.Initialize("*") ||
  174. !InFile2.Initialize("*") ) {
  175. DebugPrintTrace(( "Unable to Initialize some or all of the Arguments!\n" ));
  176. Errlev = INTERNAL_ERROR;
  177. return( FALSE );
  178. }
  179. if( !ArrayOfArg.Initialize() ) {
  180. DebugPrintTrace(( "ArrayOfArg.Initialize() Failed\n" ));
  181. Errlev = INTERNAL_ERROR;
  182. return( FALSE );
  183. }
  184. if( !ArrayOfArg.Put(&ProgramName) ||
  185. !ArrayOfArg.Put(&FlagDecimalFormat) ||
  186. !ArrayOfArg.Put(&FlagAsciiFormat) ||
  187. !ArrayOfArg.Put(&FlagLineNumbers) ||
  188. !ArrayOfArg.Put(&FlagCaseInsensitive) ||
  189. !ArrayOfArg.Put(&FlagIncludeOffline) ||
  190. !ArrayOfArg.Put(&FlagIncludeOffline2) ||
  191. !ArrayOfArg.Put(&FlagWrongNumber) ||
  192. !ArrayOfArg.Put(&LongMatchLines) ||
  193. !ArrayOfArg.Put(&FlagRequestHelp) ||
  194. !ArrayOfArg.Put(&StringInvalidSwitch) ||
  195. !ArrayOfArg.Put(&InFile1) ||
  196. !ArrayOfArg.Put(&InFile2) ) {
  197. DebugPrintTrace(( "ArrayOfArg.Put() Failed!\n" ));
  198. Errlev = INTERNAL_ERROR;
  199. return( FALSE );
  200. }
  201. if( !ArgLex.DoParsing( &ArrayOfArg ) ||
  202. StringInvalidSwitch.IsValueSet() ) {
  203. if( StringInvalidSwitch.IsValueSet() ) {
  204. //
  205. // An invalid switch was found...
  206. //
  207. // InvalidString.Initialize( "/" );
  208. // InvalidString.Strcat( StringInvalidSwitch.GetString() );
  209. InvalidString.Initialize( "" );
  210. InvalidString.Strcat( StringInvalidSwitch.GetLexeme() );
  211. Errlev = INV_SWITCH;
  212. psmsg->Set( MSG_COMP_INVALID_SWITCH );
  213. psmsg->Display( "%W", &InvalidString );
  214. } else {
  215. psmsg->Set( MSG_COMP_BAD_COMMAND_LINE );
  216. psmsg->Display( "" );
  217. Errlev = SYNT_ERR;
  218. }
  219. return( FALSE );
  220. }
  221. if( FlagWrongNumber.IsValueSet() ) {
  222. psmsg->Set( MSG_COMP_NUMERIC_FORMAT );
  223. psmsg->Display( "" );
  224. }
  225. // It should now be safe to test the arguments for their values...
  226. if( FlagRequestHelp.QueryFlag() ) {
  227. // Send help message
  228. psmsg->Set( MSG_COMP_HELP_MESSAGE );
  229. psmsg->Display( "" );
  230. return( FALSE );
  231. }
  232. if( InFile1.IsValueSet() ) {
  233. StripQuotesFromString( (PWSTRING)InFile1.GetPath()->GetPathString() );
  234. if( ( _InputPath1 = NEW PATH ) == NULL ) {
  235. psmsg->Set( MSG_COMP_NO_MEMORY );
  236. psmsg->Display( "" );
  237. Errlev = NO_MEM_AVAIL;
  238. return( FALSE );
  239. }
  240. if( !_InputPath1->Initialize( InFile1.GetPath(), FALSE ) ) {
  241. DebugAbort( "Failed to initialize canonicolized version of the path 1\n" );
  242. Errlev = INTERNAL_ERROR;
  243. return( FALSE );
  244. }
  245. } else {
  246. _InputPath1 = NULL;
  247. }
  248. if( InFile2.IsValueSet() ) {
  249. StripQuotesFromString( (PWSTRING)InFile2.GetPath()->GetPathString() );
  250. if( ( _InputPath2 = NEW PATH ) == NULL ) {
  251. Errlev = NO_MEM_AVAIL;
  252. psmsg->Set( MSG_COMP_NO_MEMORY );
  253. psmsg->Display( "" );
  254. return( FALSE );
  255. }
  256. if( !_InputPath2->Initialize( InFile2.GetPath(), FALSE ) ) {
  257. DebugAbort( "Failed to initialize canonicolized version of the path 2\n" );
  258. Errlev = INTERNAL_ERROR;
  259. return( FALSE );
  260. }
  261. } else {
  262. _InputPath2 = NULL;
  263. }
  264. //
  265. // Set the output mode...
  266. //
  267. if( FlagAsciiFormat.QueryFlag() ) {
  268. _Mode = OUTPUT_ASCII;
  269. } else if( FlagDecimalFormat.QueryFlag() ) {
  270. _Mode = OUTPUT_DECIMAL;
  271. } else {
  272. _Mode = OUTPUT_HEX;
  273. }
  274. //
  275. // Set the remaining flags...
  276. //
  277. if( LongMatchLines.IsValueSet() ) {
  278. if( ( ( WChar = ( LongMatchLines.GetLexeme()->QueryChAt( 3 ) ) ) == '+' ) ||
  279. WChar == '-' ||
  280. ( _NumberOfLines = LongMatchLines.QueryLong() ) < 0 ) {
  281. Errlev = INV_SWITCH;
  282. psmsg->Set( MSG_COMP_BAD_NUMERIC_ARG );
  283. psmsg->Display( "%W", LongMatchLines.GetLexeme() );
  284. return( FALSE );
  285. }
  286. if ( _NumberOfLines != 0 ) {
  287. _Numbered = TRUE;
  288. _Limited = TRUE;
  289. }
  290. } else {
  291. _Numbered = FlagLineNumbers.QueryFlag();
  292. _Limited = FALSE;
  293. }
  294. _CaseInsensitive = FlagCaseInsensitive.QueryFlag();
  295. _SkipOffline = ( !FlagIncludeOffline.QueryFlag() ) &&
  296. ( !FlagIncludeOffline2.QueryFlag() );
  297. if( FlagDecimalFormat.IsValueSet() ||
  298. FlagAsciiFormat.IsValueSet() ||
  299. FlagLineNumbers.IsValueSet() ||
  300. FlagCaseInsensitive.IsValueSet() ||
  301. FlagIncludeOffline.IsValueSet() ||
  302. FlagIncludeOffline2.IsValueSet() ||
  303. LongMatchLines.IsValueSet() ||
  304. ( InFile1.IsValueSet() &&
  305. InFile2.IsValueSet() ) ) {
  306. _OptionsFound = TRUE;
  307. } else {
  308. _OptionsFound = FALSE;
  309. }
  310. return( TRUE );
  311. }
  312. VOID
  313. COMP::Start(
  314. )
  315. /*++
  316. Routine Description:
  317. Query missing information from the user and start the comparison
  318. Arguments:
  319. None.
  320. Return Value:
  321. None.
  322. --*/
  323. {
  324. DSTRING UserInput;
  325. PWSTRING InvalidSwitch;
  326. USHORT OptionCount;
  327. LONG Number;
  328. for( ;; ) {
  329. if( _InputPath1 == NULL ) {
  330. // Query a path for file 1...
  331. psmsg->Set( MSG_COMP_QUERY_FILE1, ERROR_MESSAGE );
  332. psmsg->Display( "" );
  333. if( !psmsg->QueryStringInput( &UserInput ) ) {
  334. psmsg->Set( MSG_COMP_UNEXPECTED_END );
  335. psmsg->Display( "" );
  336. Errlev = UNEXP_EOF;
  337. return;
  338. }
  339. if( ( _InputPath1 = NEW PATH ) == NULL ) {
  340. psmsg->Set( MSG_COMP_NO_MEMORY );
  341. psmsg->Display( "" );
  342. Errlev = NO_MEM_AVAIL;
  343. return;
  344. }
  345. if( !_InputPath1->Initialize( &UserInput, FALSE ) ) {
  346. DebugPrintTrace(( "Unable to initialize the path for file 1\n" ));
  347. Errlev = INTERNAL_ERROR;
  348. return;
  349. }
  350. }
  351. if( _InputPath2 == NULL ) {
  352. // Query a path for file 2...
  353. psmsg->Set( MSG_COMP_QUERY_FILE2, ERROR_MESSAGE );
  354. psmsg->Display( "" );
  355. if( !psmsg->QueryStringInput( &UserInput ) ) {
  356. psmsg->Set( MSG_COMP_UNEXPECTED_END );
  357. psmsg->Display( "" );
  358. Errlev = UNEXP_EOF;
  359. return;
  360. }
  361. if( ( _InputPath2 = NEW PATH ) == NULL ) {
  362. psmsg->Set( MSG_COMP_NO_MEMORY );
  363. psmsg->Display( "" );
  364. Errlev = NO_MEM_AVAIL;
  365. return;
  366. }
  367. if( !_InputPath2->Initialize( &UserInput, FALSE ) ) {
  368. DebugPrintTrace(( "Unable to initialize the path for file 2\n" ));
  369. Errlev = INTERNAL_ERROR;
  370. return;
  371. }
  372. }
  373. if( !_OptionsFound ) {
  374. //
  375. // Query Options from the user...
  376. //
  377. DSTRING Options;
  378. DSTRING Delim;
  379. CHNUM CurSwitchStart, NextSwitchStart, Len;
  380. Delim.Initialize( "/-" );
  381. // Query a new list of options from the user
  382. for( OptionCount = 0; OptionCount < 5; OptionCount++ ) {
  383. psmsg->Set( MSG_COMP_OPTION, ERROR_MESSAGE );
  384. psmsg->Display( "" );
  385. if( !psmsg->QueryStringInput( &Options ) ) {
  386. psmsg->Set( MSG_COMP_UNEXPECTED_END );
  387. psmsg->Display( "" );
  388. Errlev = UNEXP_EOF;
  389. return;
  390. }
  391. if( Options.QueryChCount() == 0 ) {
  392. break;
  393. }
  394. CurSwitchStart = Options.Strcspn( &Delim );
  395. if( CurSwitchStart != 0 ) {
  396. psmsg->Set( MSG_COMP_BAD_COMMAND_LINE );
  397. psmsg->Display( "" );
  398. Errlev = SYNT_ERR;
  399. return;
  400. }
  401. for( ;; ) {
  402. Len = 0;
  403. CurSwitchStart++;
  404. NextSwitchStart = Options.Strcspn( &Delim, CurSwitchStart );
  405. switch( towupper( Options.QueryChAt( CurSwitchStart ) ) ) {
  406. case 'A':
  407. _Mode = OUTPUT_ASCII;
  408. CurSwitchStart++;
  409. break;
  410. case 'D':
  411. _Mode = OUTPUT_DECIMAL;
  412. CurSwitchStart++;
  413. break;
  414. case 'C':
  415. _CaseInsensitive = TRUE;
  416. CurSwitchStart++;
  417. break;
  418. case 'L':
  419. _Numbered = TRUE;
  420. CurSwitchStart++;
  421. break;
  422. case 'O':
  423. PWSTRING pArg;
  424. if( NextSwitchStart == INVALID_CHNUM ) {
  425. pArg = Options.QueryString(CurSwitchStart);
  426. } else {
  427. pArg = Options.QueryString(CurSwitchStart, NextSwitchStart-CurSwitchStart);
  428. }
  429. if (pArg &&
  430. (0 == _wcsicmp(pArg->GetWSTR(), L"OFFLINE")) ) {
  431. _SkipOffline = FALSE;
  432. CurSwitchStart += wcslen( L"OFFLINE" );
  433. DELETE( pArg );
  434. } else if (pArg &&
  435. (0 == _wcsicmp(pArg->GetWSTR(), L"OFF")) ) {
  436. _SkipOffline = FALSE;
  437. CurSwitchStart += wcslen( L"OFF" );
  438. DELETE( pArg );
  439. } else {
  440. InvalidSwitch = Options.QueryString( CurSwitchStart );
  441. psmsg->Set( MSG_COMP_INVALID_SWITCH );
  442. psmsg->Display( "%W", InvalidSwitch );
  443. Errlev = INV_SWITCH;
  444. DELETE( InvalidSwitch );
  445. DELETE( pArg );
  446. return;
  447. }
  448. break;
  449. case 'N':
  450. ++CurSwitchStart;
  451. if( Options.QueryChAt( CurSwitchStart ) != '=' ) {
  452. psmsg->Set( MSG_COMP_NUMERIC_FORMAT );
  453. psmsg->Display( "" );
  454. break;
  455. }
  456. ++CurSwitchStart;
  457. if( CurSwitchStart == NextSwitchStart ) {
  458. break;
  459. }
  460. if( NextSwitchStart == INVALID_CHNUM ) {
  461. Len = INVALID_CHNUM;
  462. } else {
  463. Len = NextSwitchStart - CurSwitchStart;
  464. }
  465. if( !Options.QueryNumber( &Number, CurSwitchStart, Len ) ) {
  466. InvalidSwitch = Options.QueryString( CurSwitchStart );
  467. psmsg->Set( MSG_COMP_BAD_NUMERIC_ARG );
  468. psmsg->Display( "%W", InvalidSwitch );
  469. Errlev = BAD_NUMERIC_ARG;
  470. DELETE( InvalidSwitch );
  471. return;
  472. }
  473. if (Options.QueryNumber( &_NumberOfLines, CurSwitchStart, Len ) ) {
  474. _Numbered = TRUE;
  475. _Limited = TRUE;
  476. }
  477. CurSwitchStart += Len;
  478. break;
  479. default:
  480. InvalidSwitch = Options.QueryString( CurSwitchStart - 1 );
  481. psmsg->Set( MSG_COMP_INVALID_SWITCH );
  482. psmsg->Display( "%W", InvalidSwitch );
  483. Errlev = INV_SWITCH;
  484. DELETE( InvalidSwitch );
  485. return;
  486. }
  487. if( ( CurSwitchStart != NextSwitchStart ) ||
  488. ( Len == INVALID_CHNUM ) ) {
  489. break;
  490. }
  491. }
  492. }
  493. }
  494. DoCompare();
  495. //
  496. // Check if there are more files to be compared...
  497. //
  498. psmsg->Set( MSG_COMP_MORE, ERROR_MESSAGE );
  499. psmsg->Display( "" );
  500. if( !psmsg->IsYesResponse() ) {
  501. break;
  502. }
  503. DELETE( _InputPath1 );
  504. DELETE( _InputPath2 );
  505. _InputPath1 = NULL;
  506. _InputPath2 = NULL;
  507. _OptionsFound = NULL;
  508. }
  509. return;
  510. }
  511. VOID
  512. COMP::DoCompare(
  513. )
  514. /*++
  515. Routine Description:
  516. Perform the comparison of the files.
  517. Arguments:
  518. None.
  519. Return Value:
  520. None.
  521. --*/
  522. {
  523. FSN_FILTER Filter;
  524. PARRAY pNodeArray;
  525. PITERATOR pIterator;
  526. PWSTRING pTmp;
  527. PATH File1Path;
  528. PFSN_DIRECTORY pDirectory = NULL;
  529. PATH CanonPath1; // Canonicolized versions of the user paths
  530. PATH CanonPath2;
  531. DSTRING WildCardString;
  532. BOOLEAN PrintSkipWarning = FALSE;
  533. BOOLEAN OfflineSkipped;
  534. //
  535. // Initialize the wildcard string..
  536. //
  537. WildCardString.Initialize( "" );
  538. SYSTEM::QueryResourceString( &WildCardString, MSG_COMP_WILDCARD_STRING, "" );
  539. // Check to see if the input paths are empty.
  540. if (_InputPath1->GetPathString()->QueryChCount() == 0) {
  541. Errlev = CANT_OPEN_FILE;
  542. psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
  543. psmsg->Display( "%W", _InputPath1->GetPathString() );
  544. return;
  545. }
  546. if (_InputPath2->GetPathString()->QueryChCount() == 0) {
  547. Errlev = CANT_OPEN_FILE;
  548. psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
  549. psmsg->Display( "%W", _InputPath2->GetPathString() );
  550. return;
  551. }
  552. //
  553. // Test if the input paths contain only a directory name. If it
  554. // does, append '*.*' to the path so all files in that directory
  555. // may be compared.
  556. //
  557. if( _InputPath1->IsDrive() ||
  558. ( !_InputPath1->HasWildCard() &&
  559. ( pDirectory = SYSTEM::QueryDirectory( _InputPath1 ) ) != NULL ) ) {
  560. // The input path corresponds to a directory...
  561. _InputPath1->AppendBase( &WildCardString );
  562. }
  563. DELETE( pDirectory );
  564. if( _InputPath2->IsDrive() ||
  565. ( !_InputPath2->HasWildCard() &&
  566. ( pDirectory = SYSTEM::QueryDirectory( _InputPath2 ) ) != NULL ) ) {
  567. // The input path corresponds to a directory...
  568. _InputPath2->AppendBase( &WildCardString );
  569. }
  570. DELETE( pDirectory );
  571. //
  572. // Canonicolize the input paths...
  573. //
  574. CanonPath1.Initialize( _InputPath1, TRUE );
  575. CanonPath2.Initialize( _InputPath2, TRUE );
  576. //
  577. // Test if the first path name contains any wildcards. If it does,
  578. // the program must initialize an array of FSN_NODES (for multiple
  579. // files...
  580. //
  581. if( CanonPath1.HasWildCard() ) {
  582. PPATH pTmpPath;
  583. //
  584. // Get a directory based on what the user specified for File 1
  585. //
  586. if( ( pTmpPath = CanonPath1.QueryFullPath() ) == NULL ) {
  587. DebugPrintTrace(( "Unable to grab the Prefix from the input path...\n" ));
  588. Errlev = INTERNAL_ERROR;
  589. return;
  590. }
  591. pTmpPath->TruncateBase();
  592. if( ( pDirectory = SYSTEM::QueryDirectory( pTmpPath, FALSE ) ) != NULL ) {
  593. //
  594. // Create an FSN_FILTER so we can use the directory to create an
  595. // array of FSN_NODES
  596. Filter.Initialize();
  597. pTmp = CanonPath1.QueryName();
  598. Filter.SetFileName( pTmp );
  599. DELETE( pTmp );
  600. Filter.SetAttributes( (FSN_ATTRIBUTE)0, // ALL
  601. FSN_ATTRIBUTE_FILES, // ANY
  602. FSN_ATTRIBUTE_DIRECTORY ); // NONE
  603. pNodeArray = pDirectory->QueryFsnodeArray( &Filter );
  604. pIterator = pNodeArray->QueryIterator();
  605. DELETE( pDirectory );
  606. _File1 = (FSN_FILE *)pIterator->GetNext();
  607. } else {
  608. _File1 = NULL;
  609. }
  610. DELETE( pTmpPath );
  611. } else {
  612. _File1 = SYSTEM::QueryFile( &CanonPath1 );
  613. }
  614. if( _File1 == NULL ) {
  615. Errlev = CANT_OPEN_FILE;
  616. psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
  617. psmsg->Display( "%W", _InputPath1->GetPathString() );
  618. return;
  619. }
  620. do {
  621. //
  622. // Explicitly find if File1 has offline attributes set (This is required
  623. // because SYSTEM::QueryFile is not used for getting the FSN_FILE object)
  624. //
  625. if (_SkipOffline && IsOffline(_File1)) {
  626. PrintSkipWarning = TRUE;
  627. Errlev = FILES_SKIPPED;
  628. DELETE( _File1 );
  629. if( !CanonPath1.HasWildCard() ) {
  630. break;
  631. }
  632. continue;
  633. }
  634. //
  635. // Replace the input path filename with what is to be opened...
  636. //
  637. pTmp = _File1->GetPath()->QueryName();
  638. _InputPath1->SetName( pTmp );
  639. DELETE( pTmp );
  640. // Determine if filename 2 contains any wildcards...
  641. if( CanonPath2.HasWildCard() ) {
  642. // ...if it does, expand them...
  643. PPATH pExpanded;
  644. pExpanded = CanonPath2.QueryWCExpansion( (PATH *)_File1->GetPath() );
  645. if( pExpanded == NULL ) {
  646. Errlev = COULD_NOT_EXP;
  647. psmsg->Set( MSG_COMP_UNABLE_TO_EXPAND );
  648. psmsg->Display( "%W%W", _InputPath1->GetPathString(), _InputPath2->GetPathString() );
  649. DELETE( _File1 );
  650. break;
  651. }
  652. //
  653. // Place the expanded name in the input path...
  654. //
  655. pTmp = pExpanded->QueryName();
  656. _InputPath2->SetName( pTmp );
  657. DELETE( pTmp );
  658. psmsg->Set( MSG_COMP_COMPARE_FILES );
  659. psmsg->Display( "%W%W", _InputPath1->GetPathString(),
  660. _InputPath2->GetPathString()
  661. );
  662. _File2 = SYSTEM::QueryFile( pExpanded, _SkipOffline, &OfflineSkipped );
  663. DELETE( pExpanded );
  664. } else {
  665. psmsg->Set( MSG_COMP_COMPARE_FILES );
  666. psmsg->Display( "%W%W", _InputPath1->GetPathString(),
  667. _InputPath2->GetPathString()
  668. );
  669. _File2 = SYSTEM::QueryFile( &CanonPath2, _SkipOffline, &OfflineSkipped );
  670. }
  671. if( _File2 == NULL ) {
  672. if (OfflineSkipped) {
  673. // Skipping offline files is not an error, just track this happened
  674. PrintSkipWarning = TRUE;
  675. Errlev = FILES_SKIPPED;
  676. } else {
  677. // Display error message
  678. psmsg->Set( MSG_COMP_UNABLE_TO_OPEN );
  679. psmsg->Display( "%W", _InputPath2->GetPathString() );
  680. Errlev = CANT_OPEN_FILE;
  681. }
  682. DELETE( _File1 );
  683. if( !CanonPath1.HasWildCard() ) {
  684. break;
  685. }
  686. continue;
  687. }
  688. //
  689. // Open the streams...
  690. // Initialize _ByteStream1 and _ByteStream2 with BufferSize = 1024, to
  691. // improve performance
  692. //
  693. if( (( _FileStream1 = (FILE_STREAM *)_File1->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ) ) == NULL) ||
  694. !_ByteStream1.Initialize( _FileStream1, 1024 )
  695. ) {
  696. Errlev = CANT_READ_FILE;
  697. psmsg->Set( MSG_COMP_UNABLE_TO_READ );
  698. psmsg->Display( "%W", _File1->GetPath()->GetPathString() );
  699. DELETE( _File1 );
  700. DELETE( _File2 );
  701. if( !CanonPath1.HasWildCard() ) {
  702. break;
  703. }
  704. continue;
  705. }
  706. if( (( _FileStream2 = (FILE_STREAM *)_File2->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ) ) == NULL ) ||
  707. !_ByteStream2.Initialize( _FileStream2, 1024 )
  708. ) {
  709. Errlev = CANT_READ_FILE;
  710. psmsg->Set( MSG_COMP_UNABLE_TO_READ );
  711. psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
  712. DELETE( _FileStream1 );
  713. DELETE( _File1 );
  714. DELETE( _File2 );
  715. if( !CanonPath1.HasWildCard() ) {
  716. break;
  717. }
  718. continue;
  719. }
  720. BinaryCompare();
  721. // Close both streams now, since we are done with them...
  722. DELETE( _FileStream1 );
  723. DELETE( _FileStream2 );
  724. DELETE( _File1 );
  725. DELETE( _File2 );
  726. if( !CanonPath1.HasWildCard() ) {
  727. break;
  728. }
  729. } while( ( _File1 = (FSN_FILE *)pIterator->GetNext() ) != NULL );
  730. //
  731. // Print warning message if offline files were skipped
  732. //
  733. if(PrintSkipWarning) {
  734. psmsg->Set( MSG_COMP_OFFLINE_FILES_SKIPPED );
  735. psmsg->Display( "" );
  736. }
  737. return;
  738. }
  739. BOOLEAN
  740. COMP::IsOffline(
  741. PFSN_FILE pFile
  742. )
  743. /*++
  744. Routine Description:
  745. Checks if a file object represents an offline file
  746. Arguments:
  747. pFile - The file to check
  748. Return Value:
  749. TRUE - if offline
  750. Notes:
  751. This routine assumes a valid object that represnts a valid file
  752. On error, it returns FALSE since a none offline file is the default.
  753. --*/
  754. {
  755. PWSTRING FullPath = NULL;
  756. BOOLEAN fRet = FALSE;
  757. PCWSTR FileName;
  758. DWORD dwAttributes;
  759. DebugAssert( pFile );
  760. if ( pFile &&
  761. ((FullPath = pFile->GetPath()->QueryFullPathString()) != NULL ) &&
  762. ((FileName = FullPath->GetWSTR()) != NULL ) &&
  763. ( FileName[0] != (WCHAR)'\0' ) &&
  764. ((dwAttributes = GetFileAttributes( FileName )) != -1) ) {
  765. if (dwAttributes & FILE_ATTRIBUTE_OFFLINE) {
  766. fRet = TRUE;
  767. }
  768. }
  769. DELETE( FullPath );
  770. return fRet;
  771. }
  772. #ifdef FE_SB // v-junm - 08/30/93
  773. BOOLEAN
  774. COMP::CharEqual(
  775. PUCHAR c1,
  776. PUCHAR c2
  777. )
  778. /*++
  779. Routine Description:
  780. Checks to see if a PCHAR DBCS or SBCS char is equal or not. For SBCS
  781. chars, if the CaseInsensitive flag is set, the characters are converted
  782. to uppercase and checked for equality.
  783. Arguments:
  784. c1 - NULL terminating DBCS/SBCS char *.
  785. c2 - NULL terminating DBCS/SBCS char *.
  786. Return Value:
  787. TRUE - if equal.
  788. Notes:
  789. The char string sequence is:
  790. SBCS:
  791. c1[0] - char code.
  792. c1[1] - 0.
  793. DBCS:
  794. c1[0] - leadbyte.
  795. c1[1] - tailbyte.
  796. c1[2] - 0.
  797. --*/
  798. {
  799. if ( (*(c1+1) == 0) && (*(c2+1) == 0 ) )
  800. return( CASE_SENSITIVE( *c1 ) == CASE_SENSITIVE( *c2 ) );
  801. else
  802. return( (*c1 == *c2) && (*(c1+1) == *(c2+1)) );
  803. }
  804. #endif
  805. VOID
  806. COMP::BinaryCompare(
  807. )
  808. /*++
  809. Routine Description:
  810. Does the actual binary compare between the two streams
  811. Arguments:
  812. None.
  813. Return Value:
  814. None.
  815. Notes:
  816. The binary compare simply does a byte by byte comparison of the two
  817. files and reports all differences, as well as the offset into the
  818. file... ...no line buffer is required for this comparision...
  819. --*/
  820. {
  821. ULONG FileOffset = 0;
  822. ULONG LineCount = 1; // Start the line count at 1...
  823. USHORT Differences = 0;
  824. BYTE Byte1, Byte2;
  825. #ifdef FE_SB // v-junm - 08/30/93
  826. BOOLEAN Lead = 0; // Set when leadbyte is read.
  827. UCHAR Byte1W[3], Byte2W[3]; // PCHAR to contain DBCS/SBCS char.
  828. ULONG DBCSFileOffset = 0; // When ASCII output and DBCS char,
  829. // the offset is where the lead
  830. // byte is, not the tail byte.
  831. #endif
  832. STR Message[ 9 ];
  833. DSTRING ErrType;
  834. //
  835. // Set up the message string...
  836. //
  837. Message[ 0 ] = '%';
  838. Message[ 1 ] = 'W';
  839. if( !_Numbered ) {
  840. if (!SYSTEM::QueryResourceString(&ErrType, MSG_COMP_OFFSET_STRING, "")) {
  841. DebugPrintTrace(("COMP: Unable to read resource string %d\n", MSG_COMP_OFFSET_STRING));
  842. Errlev = INTERNAL_ERROR;
  843. return;
  844. }
  845. Message[ 2 ] = '%';
  846. Message[ 3 ] = 'X';
  847. } else {
  848. if (!SYSTEM::QueryResourceString(&ErrType, MSG_COMP_LINE_STRING, "")) {
  849. DebugPrintTrace(("COMP: Unable to read resource string %d\n", MSG_COMP_LINE_STRING));
  850. Errlev = INTERNAL_ERROR;
  851. return;
  852. }
  853. Message[ 2 ] = '%';
  854. Message[ 3 ] = 'd';
  855. }
  856. if( _Mode == OUTPUT_HEX ) {
  857. Message[ 4 ] = '%';
  858. Message[ 5 ] = 'X';
  859. Message[ 6 ] = '%';
  860. Message[ 7 ] = 'X';
  861. } else if( _Mode == OUTPUT_DECIMAL ) {
  862. Message[ 4 ] = '%';
  863. Message[ 5 ] = 'd';
  864. Message[ 6 ] = '%';
  865. Message[ 7 ] = 'd';
  866. } else {
  867. #ifdef FE_SB // v-junm - 08/30/93
  868. // This is needed to display DBCS chars. DBCS chars will be handed over
  869. // as a pointer of chars. In which turns out to go to a call to swprintf in
  870. // basesys.cxx. You may wonder why a DBCS char is stored as a string, but
  871. // it works because before the call to swprintf is made, a 'h' is placed before
  872. // the '%s' and makes a conversion to unicode.
  873. Message[ 4 ] = '%';
  874. Message[ 5 ] = 's';
  875. Message[ 6 ] = '%';
  876. Message[ 7 ] = 's';
  877. #else // FE_SB
  878. Message[ 4 ] = '%';
  879. Message[ 5 ] = 'c';
  880. Message[ 6 ] = '%';
  881. Message[ 7 ] = 'c';
  882. #endif // FE_SB
  883. }
  884. Message[ 8 ] = 0;
  885. // Compare the lengths of the files - if they aren't the same and
  886. // the number of lines to match hasn't been specified, then return
  887. // 'Files are different sizes'.
  888. if( !_Limited ) {
  889. if( _File1->QuerySize() != _File2->QuerySize() ) {
  890. Errlev = DIFFERENT_SIZES;
  891. CompResult = FILES_ARE_DIFFERENT;
  892. psmsg->Set( MSG_COMP_DIFFERENT_SIZES );
  893. psmsg->Display( "" );
  894. return;
  895. }
  896. }
  897. for( ;; FileOffset++ ) {
  898. //if( !_FileStream1->ReadByte( &Byte1 ) ) {
  899. if( !_ByteStream1.ReadByte( &Byte1 ) ) {
  900. if( !_ByteStream1.IsAtEnd() ) {
  901. Errlev = CANT_READ_FILE;
  902. psmsg->Set( MSG_COMP_UNABLE_TO_READ );
  903. psmsg->Display( "%W", _File1->GetPath()->GetPathString() );
  904. return;
  905. }
  906. //if( !_FileStream2->ReadByte( &Byte2 ) ) {
  907. if( !_ByteStream2.ReadByte( &Byte2 ) ) {
  908. if( !_ByteStream2.IsAtEnd() ) {
  909. Errlev = CANT_READ_FILE;
  910. psmsg->Set( MSG_COMP_UNABLE_TO_READ );
  911. psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
  912. return;
  913. }
  914. break;
  915. } else {
  916. Errlev = FILE1_LINES;
  917. psmsg->Set( MSG_COMP_FILE1_TOO_SHORT );
  918. psmsg->Display( "%d", LineCount-1 );
  919. return;
  920. }
  921. } else {
  922. //if( !_FileStream2->ReadByte( &Byte2 ) ) {
  923. if( !_ByteStream2.ReadByte( &Byte2 ) ) {
  924. if( !_ByteStream2.IsAtEnd() ) {
  925. Errlev = CANT_READ_FILE;
  926. psmsg->Set( MSG_COMP_UNABLE_TO_READ );
  927. psmsg->Display( "%W", _File2->GetPath()->GetPathString() );
  928. return;
  929. }
  930. Errlev = FILE2_LINES;
  931. psmsg->Set( MSG_COMP_FILE2_TOO_SHORT );
  932. psmsg->Display( "%d", LineCount-1 );
  933. return;
  934. }
  935. }
  936. #ifdef FE_SB // v-junm - 08/30/93
  937. // For hex and decimal display, we don't want to worry about DBCS chars. This
  938. // is a different spec than DOS/V (Japanese DOS), but it's much cleaner this
  939. // way. So, we will only worry about DBCS chars when the user asks us to
  940. // display the difference in characters (/A option). The file offset displayed
  941. // for DBCS characters is always where the leadbyte is in the file even though
  942. // only the tailbyte is different.
  943. DBCSFileOffset = FileOffset;
  944. //
  945. // Only going to worry about DBCS when user is comparing with
  946. // ASCII output.
  947. //
  948. //if ( _Mode == OUTPUT_ASCII ) {
  949. //kksuzuka: #133
  950. //We have to worry about DBCS with 'c' option also.
  951. if ( (_Mode==OUTPUT_ASCII) || ( _CaseInsensitive ) ) {
  952. if ( Lead ) {
  953. //
  954. // DBCS leadbyte already found. Setup variables and
  955. // fill in tailbyte and null.
  956. //
  957. DBCSFileOffset--;
  958. Lead = FALSE;
  959. *(Byte1W+1) = Byte1;
  960. *(Byte2W+1) = Byte2;
  961. *(Byte1W+2) = *(Byte2W+2) = 0;
  962. }
  963. else if ( IsDBCSLeadByte( Byte1 ) || IsDBCSLeadByte( Byte2 ) ) {
  964. //
  965. // Found leadbyte. Set lead flag telling the next time
  966. // around that the character is a tailbyte.
  967. //
  968. //
  969. // Save the leadbyte. Tailbyte will be filled next time
  970. // around(above).
  971. //
  972. *Byte1W = Byte1;
  973. *Byte2W = Byte2;
  974. Lead = TRUE;
  975. continue;
  976. }
  977. else {
  978. //
  979. // SBCS char.
  980. //
  981. *Byte1W = Byte1;
  982. *Byte2W = Byte2;
  983. *(Byte1W+1) = *(Byte2W+1) = 0;
  984. Lead = FALSE;
  985. }
  986. }
  987. else {
  988. //
  989. // Not ASCII output (/a option). Perform original routines.
  990. //
  991. *Byte1W = Byte1;
  992. *Byte2W = Byte2;
  993. *(Byte1W+1) = *(Byte2W+1) = 0;
  994. }
  995. //
  996. // Check to see if chars are equal. If not, display difference.
  997. //
  998. if ( CharEqual( Byte1W, Byte2W ) == FALSE ) {
  999. psmsg->Set( MSG_COMP_COMPARE_ERROR );
  1000. if ( _Mode == OUTPUT_ASCII ) {
  1001. if ( _Numbered )
  1002. psmsg->Display( Message, &ErrType,
  1003. LineCount, Byte1W, Byte2W );
  1004. else
  1005. psmsg->Display( Message, &ErrType,
  1006. DBCSFileOffset, Byte1W, Byte2W );
  1007. }
  1008. else {
  1009. if ( _Numbered )
  1010. psmsg->Display( Message, &ErrType,
  1011. LineCount, *Byte1W, *Byte2W );
  1012. //kksuzuka: #133
  1013. //We have to worry about DBCS with c option also.
  1014. //else
  1015. else {
  1016. if( *Byte1W != *Byte2W ) {
  1017. psmsg->Display( Message, &ErrType,
  1018. FileOffset, *Byte1W, *Byte2W );
  1019. }
  1020. else {
  1021. psmsg->Display( Message, &ErrType,
  1022. FileOffset, *(Byte1W+1), *(Byte2W+1) );
  1023. }
  1024. }
  1025. }
  1026. #else // FE_SB
  1027. // Now compare the bytes...if they are different, report the
  1028. // difference...
  1029. if( CASE_SENSITIVE( Byte1 ) != CASE_SENSITIVE( Byte2 ) ) {
  1030. if( _Numbered ) {
  1031. psmsg->Set( MSG_COMP_COMPARE_ERROR );
  1032. psmsg->Display( Message, &ErrType, LineCount, Byte1, Byte2 );
  1033. } else {
  1034. psmsg->Set( MSG_COMP_COMPARE_ERROR );
  1035. psmsg->Display( Message, &ErrType, FileOffset, Byte1, Byte2 );
  1036. }
  1037. #endif // FE_SB
  1038. if( ++Differences == MAX_DIFF ) {
  1039. psmsg->Set( MSG_COMP_TOO_MANY_ERRORS );
  1040. psmsg->Display( "" );
  1041. Errlev = TEN_MISM;
  1042. CompResult = FILES_ARE_DIFFERENT;
  1043. return;
  1044. }
  1045. }
  1046. //
  1047. // Use <CR>'s imbedded in File1 to determine the line count. This is
  1048. // an inexact method (the differing byte may be '/r') but it is good
  1049. // enough for the purposes of this program.
  1050. //
  1051. #ifdef FE_SB // v-junm - 08/30/93
  1052. if( *Byte1W == '\r' ) {
  1053. #else // FE_SB
  1054. if( Byte1 == '\r' ) {
  1055. #endif // FE_SB
  1056. LineCount++;
  1057. }
  1058. if( _Limited ) {
  1059. if( LineCount > (ULONG)_NumberOfLines ) {
  1060. break;
  1061. }
  1062. }
  1063. }
  1064. #ifdef FE_SB // v-junm - 08/30/93
  1065. // There may be a leadbyte without a tailbyte at the end of the file. Check
  1066. // for it, and process accordingly.
  1067. if ( _Mode == OUTPUT_ASCII && Lead ) {
  1068. //
  1069. // There is a leadbyte left. Check to see if they are equal and
  1070. // print difference if not.
  1071. //
  1072. if ( *Byte2W != *Byte1W ) {
  1073. *(Byte1W+1) = *(Byte2W+1) = 0;
  1074. Differences++;
  1075. psmsg->Set( MSG_COMP_COMPARE_ERROR );
  1076. if ( _Numbered )
  1077. psmsg->Display(Message, &ErrType, LineCount, Byte1W, Byte2W);
  1078. else
  1079. psmsg->Display(Message, &ErrType, FileOffset-1, Byte1W, Byte2W);
  1080. }
  1081. }
  1082. #endif // FE_SB
  1083. //
  1084. // Check if any differences were found in the files
  1085. //
  1086. if( !Differences ) {
  1087. psmsg->Set( MSG_COMP_FILES_OK );
  1088. psmsg->Display( " " );
  1089. } else {
  1090. CompResult = FILES_ARE_DIFFERENT;
  1091. }
  1092. return;
  1093. }
  1094. int __cdecl
  1095. main(
  1096. )
  1097. {
  1098. DEFINE_CLASS_DESCRIPTOR( COMP );
  1099. __try {
  1100. COMP Comp;
  1101. psmsg = NEW STREAM_MESSAGE;
  1102. if (psmsg == NULL) {
  1103. DebugPrint("COMP: Out of memory\n");
  1104. Errlev = NO_MEM_AVAIL;
  1105. return( CANNOT_COMPARE_FILES );
  1106. }
  1107. // Initialize the stream message for standard input, stdout
  1108. if (!Get_Standard_Output_Stream() ||
  1109. !Get_Standard_Input_Stream() ||
  1110. !psmsg->Initialize( Get_Standard_Output_Stream(),
  1111. Get_Standard_Input_Stream(),
  1112. Get_Standard_Error_Stream() )) {
  1113. if (!Get_Standard_Output_Stream()) {
  1114. DebugPrintTrace(("COMP: Output stream is NULL\n"));
  1115. } else if (!Get_Standard_Input_Stream()) {
  1116. DebugPrintTrace(("COMP: Input stream is NULL\n"));
  1117. } else {
  1118. DebugPrintTrace(("COMP: Unable to initialize message stream\n"));
  1119. }
  1120. Comp.Destruct();
  1121. return( CANNOT_COMPARE_FILES );
  1122. }
  1123. if( !SYSTEM::IsCorrectVersion() ) {
  1124. DebugPrintTrace(( "COMP: Incorrect Version Number...\n" ));
  1125. psmsg->Set( MSG_COMP_INCORRECT_VERSION );
  1126. psmsg->Display( "" );
  1127. Comp.Destruct();
  1128. return( CANNOT_COMPARE_FILES );
  1129. // return( INCORRECT_DOS_VER );
  1130. }
  1131. // Set the Error level to Zero - No error...
  1132. Errlev = NO_ERRORS;
  1133. CompResult = FILES_ARE_EQUAL;
  1134. if( !( Comp.Initialize() ) ) {
  1135. //
  1136. // The Command line didn't initialize properly, die nicely
  1137. // without printing any error messages - Main doesn't know
  1138. // why the Initialization failed...
  1139. //
  1140. // What has to be deleted by hand, or can everything be removed
  1141. // by the destructor for the FC class?
  1142. //
  1143. Comp.Destruct();
  1144. return( CANNOT_COMPARE_FILES );
  1145. // return( Errlev );
  1146. }
  1147. // Do file comparison stuff...
  1148. Comp.Start();
  1149. Comp.Destruct();
  1150. // return( Errlev );
  1151. if( ( Errlev == NO_ERRORS ) || ( Errlev == TEN_MISM ) || ( Errlev == DIFFERENT_SIZES ) ) {
  1152. return( CompResult );
  1153. } else {
  1154. return( CANNOT_COMPARE_FILES );
  1155. }
  1156. } __except ((_exception_code() == STATUS_STACK_OVERFLOW) ?
  1157. EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
  1158. // may not be able to display anything if initialization failed
  1159. // in additional to out of stack space
  1160. // so just send a message to the debug port
  1161. DebugPrint("COMP: Out of stack space\n");
  1162. Errlev = NO_MEM_AVAIL;
  1163. return CANNOT_COMPARE_FILES;
  1164. }
  1165. }