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.

960 lines
21 KiB

  1. /*++
  2. Copyright (c) 1990-2000 Microsoft Corporation
  3. Module Name:
  4. Replace
  5. Abstract:
  6. Replace utility
  7. Author:
  8. Ramon Juan San Andres (ramonsa) 01-May-1991
  9. Revision History:
  10. --*/
  11. #include "ulib.hxx"
  12. #include "arrayit.hxx"
  13. #include "dir.hxx"
  14. #include "filter.hxx"
  15. #include "file.hxx"
  16. #include "fsnode.hxx"
  17. #include "stream.hxx"
  18. #include "substrng.hxx"
  19. #include "system.hxx"
  20. #include "replace.hxx"
  21. //
  22. // Pattern that matches all files in a directory
  23. //
  24. #define MATCH_ALL_FILES "*.*"
  25. #define CTRL_C (WCHAR)3
  26. //
  27. // Size of buffers to hold path strings
  28. //
  29. #define INITIAL_PATHSTRING_BUFFER_SIZE MAX_PATH
  30. VOID __cdecl
  31. main (
  32. )
  33. /*++
  34. Routine Description:
  35. Main function of the Replace utility
  36. Arguments:
  37. None.
  38. Return Value:
  39. None.
  40. Notes:
  41. --*/
  42. {
  43. //
  44. // Initialize stuff
  45. //
  46. DEFINE_CLASS_DESCRIPTOR( REPLACE );
  47. //
  48. // Now do the replacement
  49. //
  50. {
  51. REPLACE Replace;
  52. //
  53. // Initialize the Replace object.
  54. //
  55. Replace.Initialize();
  56. //
  57. // Do our thing
  58. //
  59. Replace.DoReplace();
  60. }
  61. }
  62. DEFINE_CONSTRUCTOR( REPLACE, PROGRAM );
  63. BOOLEAN
  64. REPLACE::Initialize (
  65. )
  66. /*++
  67. Routine Description:
  68. Initializes the REPLACE object
  69. Arguments:
  70. None.
  71. Return Value:
  72. TRUE if initialized, FALSE otherwise
  73. Notes:
  74. --*/
  75. {
  76. //
  77. // Initialize program object
  78. //
  79. PROGRAM::Initialize( REPLACE_MESSAGE_USAGE );
  80. //
  81. // Allocate global structures and initialize them
  82. //
  83. InitializeThings();
  84. //
  85. // Parse the arguments
  86. //
  87. SetArguments();
  88. return TRUE;
  89. }
  90. REPLACE::~REPLACE (
  91. )
  92. /*++
  93. Routine Description:
  94. Destructs a REPLACE object
  95. Arguments:
  96. None.
  97. Return Value:
  98. None.
  99. Notes:
  100. --*/
  101. {
  102. //
  103. // Deallocate the global structures previously allocated
  104. //
  105. DeallocateThings();
  106. //
  107. // Exit without error
  108. //
  109. DisplayMessageAndExit( 0, NULL, EXIT_NORMAL );
  110. }
  111. VOID
  112. REPLACE::InitializeThings (
  113. )
  114. /*++
  115. Routine Description:
  116. Initializes the global variables that need initialization
  117. Arguments:
  118. None.
  119. Return Value:
  120. None.
  121. Notes:
  122. --*/
  123. {
  124. //
  125. // Initialize the path string buffers
  126. //
  127. _PathString1 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE );
  128. _PathString2 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE );
  129. _PathString1Size = _PathString2Size = INITIAL_PATHSTRING_BUFFER_SIZE;
  130. _Keyboard = NEW KEYBOARD;
  131. if ( !_PathString1 || !_PathString2 || !_Keyboard ) {
  132. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  133. }
  134. //
  135. // initialize the keyboard and set ctrl-c handling
  136. //
  137. _Keyboard->Initialize();
  138. _Keyboard->EnableBreakHandling();
  139. //
  140. // Initialize our data
  141. //
  142. _SourcePath = NULL;
  143. _DestinationPath = NULL;
  144. _FilesAdded = 0;
  145. _FilesReplaced = 0;
  146. _SourceDirectory = NULL;
  147. _Pattern = NULL;
  148. _FilesInSrc = NULL;
  149. _AddSwitch = FALSE; // use by DisplayMessageAndExit before any
  150. // of those boolean _*Switch is being initialized
  151. }
  152. VOID
  153. REPLACE::DeallocateThings (
  154. )
  155. /*++
  156. Routine Description:
  157. Deallocates the stuff that was initialized in InitializeThings()
  158. Arguments:
  159. None.
  160. Return Value:
  161. None.
  162. Notes:
  163. --*/
  164. {
  165. DELETE( _FilesInSrc );
  166. DELETE( _SourceDirectory );
  167. DELETE( _Pattern );
  168. DELETE( _Keyboard );
  169. FREE( _PathString1 );
  170. FREE( _PathString2 );
  171. }
  172. BOOLEAN
  173. REPLACE::DoReplace (
  174. )
  175. /*++
  176. Routine Description:
  177. This is the function that performs the Replace.
  178. Arguments:
  179. None.
  180. Return Value:
  181. TRUE
  182. Notes:
  183. --*/
  184. {
  185. PFSN_DIRECTORY DestinationDirectory;
  186. FSN_FILTER Filter;
  187. WCHAR Char;
  188. //
  189. // Get the source directory object and the pattern that we will use
  190. // for file matching.
  191. //
  192. GetDirectoryAndPattern( _SourcePath, &_SourceDirectory, &_Pattern );
  193. DebugPtrAssert( _SourceDirectory );
  194. DebugPtrAssert( _Pattern );
  195. //
  196. // Get the destination directory
  197. //
  198. GetDirectory( _DestinationPath, &DestinationDirectory );
  199. DebugPtrAssert( DestinationDirectory );
  200. //
  201. // Wait if requested
  202. //
  203. if ( _WaitSwitch ) {
  204. DisplayMessage( REPLACE_MESSAGE_PRESS_ANY_KEY );
  205. AbortIfCtrlC();
  206. //
  207. // All input is in raw mode.
  208. //
  209. _Keyboard->DisableLineMode();
  210. GetStandardInput()->ReadChar( &Char );
  211. _Keyboard->EnableLineMode();
  212. GetStandardOutput()->WriteChar( Char );
  213. GetStandardOutput()->WriteChar( (WCHAR)'\r');
  214. GetStandardOutput()->WriteChar( (WCHAR)'\n');
  215. //
  216. // Check for ctrl-c
  217. //
  218. if ( Char == CTRL_C ) {
  219. exit ( EXIT_PATH_NOT_FOUND );
  220. }
  221. }
  222. //
  223. // Get an array containing all the files in the source directory
  224. // that match the pattern.
  225. //
  226. // This is so that Replacer() does not have to get the same
  227. // information over and over when the Subdir switch is set.
  228. //
  229. _FilesInSrc = GetFileArray( _SourceDirectory, _Pattern );
  230. DebugPtrAssert( _FilesInSrc );
  231. if ( _SubdirSwitch ) {
  232. //
  233. // First, replace the files in the directory specified
  234. //
  235. Replacer( this, DestinationDirectory, NULL );
  236. Filter.Initialize();
  237. Filter.SetAttributes( (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
  238. //
  239. // Now traverse the destination directory, calling the
  240. // replacer function for each subdirectory.
  241. //
  242. DestinationDirectory->Traverse( this,
  243. &Filter,
  244. NULL,
  245. REPLACE::Replacer );
  246. } else {
  247. //
  248. // Call the replace function, which takes care of replacements
  249. //
  250. Replacer(this, DestinationDirectory, NULL );
  251. }
  252. DELETE( DestinationDirectory );
  253. return TRUE;
  254. }
  255. VOID
  256. REPLACE::GetDirectoryAndPattern(
  257. IN PPATH Path,
  258. OUT PFSN_DIRECTORY *Directory,
  259. OUT PWSTRING *Pattern
  260. )
  261. /*++
  262. Routine Description:
  263. Given a path, this function obtains a directory object and a pattern.
  264. Normally, the pattern is the filename portion of the path, but if the
  265. entire path refers to a directory, then the pattern is "*.*"
  266. Arguments:
  267. Path - Supplies pointer to path
  268. Directory - Supplies pointer to pointer to directory
  269. Pattern - Supplies pointer to pointer to pattern
  270. Return Value:
  271. TRUE
  272. Notes:
  273. --*/
  274. {
  275. PATH TmpPath;
  276. PWSTRING Name;
  277. PFSN_DIRECTORY Dir;
  278. PWSTRING Ptrn;
  279. DebugAssert( Path );
  280. DebugAssert( Directory );
  281. DebugAssert( Pattern );
  282. //
  283. // If the name passed is a directory, it is an error.
  284. // Otherwise, split the path into Directory and Pattern
  285. // portions.
  286. //
  287. Dir = SYSTEM::QueryDirectory( Path );
  288. if ( Dir ||
  289. (Name = Path->QueryName()) == NULL ||
  290. (Ptrn = Name->QueryString()) == NULL ) {
  291. DisplayMessageAndExit( REPLACE_ERROR_NO_FILES_FOUND,
  292. Path->GetPathString(),
  293. EXIT_FILE_NOT_FOUND );
  294. } else {
  295. // We're finished with Name.
  296. //
  297. DELETE( Name );
  298. //
  299. // Get the directory
  300. //
  301. TmpPath.Initialize( Path, TRUE );
  302. TmpPath.TruncateBase();
  303. Dir = SYSTEM::QueryDirectory( &TmpPath );
  304. if ( !Dir ) {
  305. DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND,
  306. Path->GetPathString(),
  307. EXIT_PATH_NOT_FOUND );
  308. }
  309. *Directory = Dir;
  310. *Pattern = Ptrn;
  311. }
  312. }
  313. VOID
  314. REPLACE::GetDirectory(
  315. IN PCPATH Path,
  316. OUT PFSN_DIRECTORY *Directory
  317. )
  318. /*++
  319. Routine Description:
  320. Makes a directory out of a path.
  321. Arguments:
  322. Path - Supplies pointer to path
  323. Directory - Supplies pointer to pointer to directory
  324. Return Value:
  325. TRUE
  326. Notes:
  327. --*/
  328. {
  329. PFSN_DIRECTORY Dir;
  330. if ( !(Dir = SYSTEM::QueryDirectory( Path )) ) {
  331. DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND,
  332. Path->GetPathString(),
  333. EXIT_PATH_NOT_FOUND );
  334. }
  335. *Directory = Dir;
  336. }
  337. PARRAY
  338. REPLACE::GetFileArray(
  339. IN PFSN_DIRECTORY Directory,
  340. IN PWSTRING Pattern
  341. )
  342. /*++
  343. Routine Description:
  344. Gets an array of those files in a directory matching a pattern.
  345. Arguments:
  346. Directory - Supplies pointer to directory
  347. Pattern - Supplies pointer to pattern
  348. Return Value:
  349. Pointer to the array of files
  350. Notes:
  351. --*/
  352. {
  353. PARRAY Array;
  354. FSN_FILTER Filter;
  355. DebugPtrAssert( Directory );
  356. DebugPtrAssert( Pattern );
  357. Filter.Initialize();
  358. Filter.SetFileName( Pattern );
  359. Filter.SetAttributes( (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
  360. Array = Directory->QueryFsnodeArray( &Filter );
  361. DebugPtrAssert( Array );
  362. return Array;
  363. }
  364. BOOLEAN
  365. REPLACE::Replacer (
  366. IN PVOID This,
  367. IN OUT PFSNODE DirectoryNode,
  368. IN PPATH DummyPath
  369. )
  370. /*++
  371. Routine Description:
  372. This is the heart of Replace. Given a destination directory, it
  373. performs the replacement/additions according to the global switches
  374. and the SourceDirectory and Pattern.
  375. Arguments:
  376. This - Supplies pointer to the REPLACE object
  377. Node - Supplies pointer to the directory node.
  378. DummyPath - Required by FSN_DIRECTORY::Traverse(), must be
  379. NULL.
  380. Return Value:
  381. BOOLEAN - TRUE if operation successful.
  382. FALSE otherwise
  383. Notes:
  384. --*/
  385. {
  386. DebugAssert( DummyPath == NULL );
  387. DebugAssert( DirectoryNode->IsDirectory() );
  388. ((PREPLACE)This)->AbortIfCtrlC();
  389. if ( ((PREPLACE)This)->_AddSwitch ) {
  390. return ((PREPLACE)This)->AddFiles( (PFSN_DIRECTORY)DirectoryNode );
  391. } else {
  392. return ((PREPLACE)This)->ReplaceFiles( (PFSN_DIRECTORY)DirectoryNode );
  393. }
  394. }
  395. BOOLEAN
  396. REPLACE::AddFiles (
  397. IN OUT PFSN_DIRECTORY DestinationDirectory
  398. )
  399. /*++
  400. Routine Description:
  401. Adds those files from the SourceDirectory that match Pattern to the
  402. DestinationDirectory. The array of files is already in the
  403. FilesInSrc array.
  404. Arguments:
  405. DestinationDirectory - Supplies pointer to destination
  406. directory.
  407. Return Value:
  408. BOOLEAN - TRUE if operation successful.
  409. FALSE otherwise
  410. Notes:
  411. --*/
  412. {
  413. PARRAY_ITERATOR Iterator;
  414. PFSN_FILE File;
  415. PFSN_FILE FileToCreate;
  416. PATH DestinationPath;
  417. PWSTRING Name;
  418. DebugPtrAssert( DestinationDirectory );
  419. DebugPtrAssert( _FilesInSrc );
  420. //
  421. // Get destination path
  422. //
  423. DestinationPath.Initialize( DestinationDirectory->GetPath() );
  424. //
  425. // Obtain an iterator for going thru the files
  426. //
  427. Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( );
  428. if (Iterator == NULL) {
  429. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  430. return FALSE; // help lint
  431. }
  432. //
  433. // For each file in the array, see if it exists in the destination
  434. // directory, and if it does not, then copy it.
  435. //
  436. while ( File = (PFSN_FILE)Iterator->GetNext() ) {
  437. DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
  438. Name = File->QueryName();
  439. if (Name == NULL) {
  440. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  441. return FALSE; // help lint
  442. }
  443. //
  444. // Form the path in the target file
  445. //
  446. DestinationPath.AppendBase( Name );
  447. DELETE( Name );
  448. //
  449. // See if the file exists
  450. //
  451. FileToCreate = SYSTEM::QueryFile( &DestinationPath );
  452. //
  453. // If the file does not exist, then it has to be added
  454. //
  455. if ( !FileToCreate ) {
  456. if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_ADD_YES_NO, &DestinationPath ) ) {
  457. DisplayMessage( REPLACE_MESSAGE_ADDING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
  458. CopyTheFile( File->GetPath(), &DestinationPath );
  459. _FilesAdded++;
  460. }
  461. }
  462. DELETE( FileToCreate );
  463. //
  464. // Set the destination path back to what it originally was
  465. // ( i.e. directory specification, no file ).
  466. //
  467. DestinationPath.TruncateBase();
  468. }
  469. DELETE( Iterator );
  470. return TRUE;
  471. }
  472. BOOLEAN
  473. REPLACE::ReplaceFiles (
  474. IN OUT PFSN_DIRECTORY DestinationDirectory
  475. )
  476. /*++
  477. Routine Description:
  478. Replaces those files in the DestinationDirectory that match Pattern
  479. by the corresponding files in SourceDirectory.
  480. Arguments:
  481. DestinationDirectory - Supplies pointer to destination
  482. directory.
  483. Return Value:
  484. BOOLEAN - TRUE if operation successful.
  485. FALSE otherwise
  486. Notes:
  487. --*/
  488. {
  489. PARRAY_ITERATOR Iterator;
  490. PFSN_FILE File;
  491. PFSN_FILE FileToReplace;
  492. PATH DestinationPath;
  493. PWSTRING Name;
  494. PTIMEINFO TimeSrc;
  495. PTIMEINFO TimeDst;
  496. BOOLEAN Proceed = TRUE;
  497. DebugPtrAssert( DestinationDirectory );
  498. DebugPtrAssert( _FilesInSrc );
  499. //
  500. // Get destination path
  501. //
  502. DestinationPath.Initialize( DestinationDirectory->GetPath() );
  503. //
  504. // Obtain an iterator for going thru the files
  505. //
  506. Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( );
  507. if (Iterator == NULL) {
  508. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  509. return FALSE; // help lint
  510. }
  511. //
  512. // For each file in the array, see if it exists in the destination
  513. // directory, and if it does, replace it
  514. //
  515. while ( File = (PFSN_FILE)Iterator->GetNext() ) {
  516. AbortIfCtrlC();
  517. DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
  518. Name = File->QueryName();
  519. if (Name == NULL) {
  520. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  521. return FALSE; // help lint
  522. }
  523. //
  524. // Form the path in the target file
  525. //
  526. DestinationPath.AppendBase( Name );
  527. DELETE( Name );
  528. //
  529. // See if the file exists
  530. //
  531. FileToReplace = SYSTEM::QueryFile( &DestinationPath );
  532. if ( FileToReplace ) {
  533. //
  534. // If the CompareTime switch is set, then we only proceed if
  535. // the destination file is older than the source file.
  536. //
  537. if ( _CompareTimeSwitch ) {
  538. TimeSrc = File->QueryTimeInfo();
  539. TimeDst = FileToReplace->QueryTimeInfo();
  540. if (TimeSrc == NULL) {
  541. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  542. return FALSE; // help lint
  543. }
  544. if (TimeDst == NULL) {
  545. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  546. return FALSE; // help lint
  547. }
  548. Proceed = *TimeDst < *TimeSrc;
  549. DELETE( TimeSrc );
  550. DELETE( TimeDst );
  551. }
  552. if ( Proceed ) {
  553. //
  554. // We replace the file if it is NOT read-only
  555. // (unless the ReadOnly switch is set )
  556. //
  557. if ( _ReadOnlySwitch || !(FileToReplace->IsReadOnly()) ) {
  558. if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_REPLACE_YES_NO, &DestinationPath ) ) {
  559. DisplayMessage( REPLACE_MESSAGE_REPLACING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
  560. //
  561. // If the file is read-only, we reset the read-only attribute
  562. // before copying.
  563. //
  564. if ( FileToReplace->IsReadOnly() ) {
  565. FileToReplace->ResetReadOnlyAttribute();
  566. }
  567. CopyTheFile( File->GetPath(), &DestinationPath );
  568. _FilesReplaced++;
  569. }
  570. } else {
  571. //
  572. // The file is read-only but the ReadOnly flag was
  573. // not set, we error out.
  574. //
  575. DisplayMessageAndExit( REPLACE_ERROR_ACCESS_DENIED,
  576. DestinationPath.GetPathString(),
  577. EXIT_ACCESS_DENIED );
  578. }
  579. }
  580. }
  581. DELETE( FileToReplace );
  582. //
  583. // Set the destination path back to what it originally was
  584. // ( i.e. directory specification, no file name part ).
  585. //
  586. DestinationPath.TruncateBase();
  587. }
  588. DELETE( Iterator );
  589. return TRUE;
  590. }
  591. BOOLEAN
  592. REPLACE::Prompt (
  593. IN MSGID MessageId,
  594. IN PCPATH Path
  595. )
  596. /*++
  597. Routine Description:
  598. Gets confirmation from the user about a file to be added/replaced
  599. Arguments:
  600. MessageId - Supplies the Id of the message to use for prompting
  601. Path - Supplies path to use as parameter for the message.
  602. Return Value:
  603. BOOLEAN - TRUE if the user confirmed the add/replace
  604. FALSE otherwise
  605. --*/
  606. {
  607. DisplayMessage( MessageId, NORMAL_MESSAGE, "%W", Path->GetPathString() );
  608. return _Message.IsYesResponse();
  609. }
  610. BOOLEAN
  611. REPLACE::CopyTheFile (
  612. IN PCPATH SrcPath,
  613. IN PCPATH DstPath
  614. )
  615. /*++
  616. Routine Description:
  617. Copies a file
  618. Arguments:
  619. SrcPath - Supplies path of source file
  620. DstFile - Supplies path of destination file
  621. Return Value:
  622. BOOLEAN - TRUE
  623. --*/
  624. {
  625. ULONG Size;
  626. DebugPtrAssert( SrcPath );
  627. DebugPtrAssert( DstPath );
  628. //
  629. // Make sure that the buffers are big enough to hold the
  630. // paths
  631. //
  632. Size = (SrcPath->GetPathString()->QueryChCount() + 1) * 2;
  633. if ( Size > _PathString1Size ) {
  634. _PathString1 = (LPWSTR)REALLOC( _PathString1, (unsigned int)Size );
  635. DebugPtrAssert( _PathString1 );
  636. _PathString1Size = Size;
  637. }
  638. Size = (DstPath->GetPathString()->QueryChCount() + 1) * 2;
  639. if ( Size > _PathString2Size ) {
  640. _PathString2 = (LPWSTR)REALLOC( _PathString2, (unsigned int)Size );
  641. DebugPtrAssert( _PathString2 );
  642. _PathString2Size = Size;
  643. }
  644. if ( !_PathString1 || !_PathString2 ) {
  645. DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
  646. }
  647. //
  648. // Convert the paths to LPWSTR so that we can call CopyFile()
  649. //
  650. SrcPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString1, _PathString1Size/sizeof(WCHAR) );
  651. DstPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString2, _PathString2Size/sizeof(WCHAR) );
  652. //
  653. // Now do the copy
  654. //
  655. if ( !CopyFile( _PathString1, _PathString2, FALSE ) ) {
  656. ExitWithError( GetLastError() );
  657. }
  658. return TRUE;
  659. }