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.

2933 lines
80 KiB

  1. /*++
  2. Copyright (c) 1990-2001 Microsoft Corporation
  3. Module Name:
  4. XCopy.cxx
  5. Abstract:
  6. Xcopy is a DOS5-Compatible directory copy utility
  7. Author:
  8. Ramon Juan San Andres (ramonsa) 01-May-1991
  9. Revision History:
  10. --*/
  11. #define _NTAPI_ULIB_
  12. #include "ulib.hxx"
  13. #include "array.hxx"
  14. #include "arrayit.hxx"
  15. #include "dir.hxx"
  16. #include "file.hxx"
  17. #include "filter.hxx"
  18. #include "stream.hxx"
  19. #include "system.hxx"
  20. #include "xcopy.hxx"
  21. #include "bigint.hxx"
  22. #include "ifssys.hxx"
  23. #include "stringar.hxx"
  24. #include "arrayit.hxx"
  25. extern "C" {
  26. #include <ctype.h>
  27. #include "winbasep.h"
  28. }
  29. #define CTRL_C (WCHAR)3
  30. int __cdecl
  31. main (
  32. )
  33. /*++
  34. Routine Description:
  35. Main function of the XCopy utility
  36. Arguments:
  37. None.
  38. Return Value:
  39. None.
  40. Notes:
  41. --*/
  42. {
  43. //
  44. // Initialize stuff
  45. //
  46. DEFINE_CLASS_DESCRIPTOR( XCOPY );
  47. //
  48. // Now do the copy
  49. //
  50. {
  51. __try {
  52. XCOPY XCopy;
  53. //
  54. // Initialize the XCOPY object.
  55. //
  56. if ( XCopy.Initialize() ) {
  57. __try {
  58. //
  59. // Do the copy
  60. //
  61. XCopy.DoCopy();
  62. } __except ((_exception_code() == STATUS_STACK_OVERFLOW) ?
  63. EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
  64. // display may not work due to out of stack space
  65. XCopy.DisplayMessageAndExit(XCOPY_ERROR_STACK_SPACE, NULL, EXIT_MISC_ERROR);
  66. }
  67. }
  68. } __except ((_exception_code() == STATUS_STACK_OVERFLOW) ?
  69. EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
  70. // may not be able to display anything if initialization failed
  71. // in additional to out of stack space
  72. // so just send a message to the debug port
  73. DebugPrintTrace(("XCOPY: Out of stack space\n"));
  74. return EXIT_MISC_ERROR;
  75. }
  76. }
  77. return EXIT_NORMAL;
  78. }
  79. DEFINE_CONSTRUCTOR( XCOPY, PROGRAM );
  80. VOID
  81. XCOPY::Construct (
  82. )
  83. {
  84. _Keyboard = NULL;
  85. _TargetPath = NULL;
  86. _SourcePath = NULL;
  87. _DestinationPath = NULL;
  88. _Date = NULL;
  89. _FileNamePattern = NULL;
  90. _ExclusionList = NULL;
  91. _Iterator = NULL;
  92. }
  93. BOOLEAN
  94. XCOPY::Initialize (
  95. )
  96. /*++
  97. Routine Description:
  98. Initializes the XCOPY object
  99. Arguments:
  100. None.
  101. Return Value:
  102. None.
  103. Notes:
  104. --*/
  105. {
  106. //
  107. // Initialize program object
  108. //
  109. if( !PROGRAM::Initialize( XCOPY_MESSAGE_USAGE ) ) {
  110. return FALSE;
  111. }
  112. //
  113. // Allocate resources
  114. //
  115. InitializeThings();
  116. //
  117. // Parse the arguments
  118. //
  119. SetArguments();
  120. return TRUE;
  121. }
  122. XCOPY::~XCOPY (
  123. )
  124. /*++
  125. Routine Description:
  126. Destructs an XCopy object
  127. Arguments:
  128. None.
  129. Return Value:
  130. None.
  131. Notes:
  132. --*/
  133. {
  134. //
  135. // Deallocate the global structures previously allocated
  136. //
  137. DeallocateThings();
  138. //
  139. // Exit without error
  140. //
  141. if( _Standard_Input != NULL &&
  142. _Standard_Output != NULL ) {
  143. DisplayMessageAndExit( 0, NULL, EXIT_NORMAL );
  144. }
  145. }
  146. VOID
  147. XCOPY::InitializeThings (
  148. )
  149. /*++
  150. Routine Description:
  151. Initializes the global variables that need initialization
  152. Arguments:
  153. None.
  154. Return Value:
  155. None.
  156. Notes:
  157. --*/
  158. {
  159. //
  160. // Get a keyboard, because we will need to switch back and
  161. // forth between raw and cooked mode and because we need
  162. // to enable ctrl-c handling (so that we can exit with
  163. // the right level if the program is interrupted).
  164. //
  165. if ( !( _Keyboard = KEYBOARD::Cast(GetStandardInput()) )) {
  166. //
  167. // Not reading from standard input, we will get
  168. // the real keyboard.
  169. //
  170. _Keyboard = NEW KEYBOARD;
  171. if( !_Keyboard ) {
  172. exit(4);
  173. }
  174. _Keyboard->Initialize();
  175. }
  176. //
  177. // Set Ctrl-C handler
  178. //
  179. _Keyboard->EnableBreakHandling();
  180. //
  181. // Initialize our internal data
  182. //
  183. _FilesCopied = 0;
  184. _CanRemoveEmptyDirectories = TRUE;
  185. _TargetIsFile = FALSE;
  186. _TargetPath = NULL;
  187. _SourcePath = NULL;
  188. _DestinationPath = NULL;
  189. _Date = NULL;
  190. _FileNamePattern = NULL;
  191. _ExclusionList = NULL;
  192. _Iterator = NULL;
  193. // The following switches are being used by DisplayMessageAndExit
  194. // before any of those boolean _*Switch is being initialized
  195. _DontCopySwitch = FALSE;
  196. _StructureOnlySwitch = TRUE;
  197. }
  198. VOID
  199. XCOPY::DeallocateThings (
  200. )
  201. /*++
  202. Routine Description:
  203. Deallocates the stuff that was initialized in InitializeThings()
  204. Arguments:
  205. None.
  206. Return Value:
  207. None.
  208. Notes:
  209. --*/
  210. {
  211. //
  212. // Deallocate local data
  213. //
  214. DELETE( _TargetPath );
  215. DELETE( _SourcePath );
  216. DELETE( _DestinationPath );
  217. DELETE( _Date );
  218. DELETE( _FileNamePattern );
  219. DELETE( _Iterator );
  220. if( _ExclusionList ) {
  221. _ExclusionList->DeleteAllMembers();
  222. }
  223. DELETE( _ExclusionList );
  224. //
  225. // Reset Ctrl-C handleing
  226. //
  227. _Keyboard->DisableBreakHandling();
  228. //
  229. // If standard input is not the keyboard, we get rid of
  230. // the keyboard object.
  231. //
  232. if ( !(_Keyboard == KEYBOARD::Cast(GetStandardInput()) )) {
  233. DELETE( _Keyboard );
  234. }
  235. }
  236. STATIC BOOLEAN
  237. GetTokenHandle(
  238. IN OUT PHANDLE TokenHandle
  239. )
  240. /*++
  241. Routine Description:
  242. This routine opens the current process object and returns a
  243. handle to its token.
  244. Arguments:
  245. Return Value:
  246. FALSE - Failure.
  247. TRUE - Success.
  248. --*/
  249. {
  250. HANDLE ProcessHandle;
  251. BOOL Result;
  252. ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,
  253. GetCurrentProcessId());
  254. if (ProcessHandle == NULL)
  255. return(FALSE);
  256. Result = OpenProcessToken(ProcessHandle,
  257. TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, TokenHandle);
  258. CloseHandle(ProcessHandle);
  259. return Result != FALSE;
  260. }
  261. STATIC BOOLEAN
  262. SetPrivs(
  263. IN HANDLE TokenHandle,
  264. IN LPTSTR lpszPriv
  265. )
  266. /*++
  267. Routine Description:
  268. This routine enables the given privilege in the given token.
  269. Arguments:
  270. Return Value:
  271. FALSE - Failure.
  272. TRUE - Success.
  273. --*/
  274. {
  275. LUID SetPrivilegeValue;
  276. TOKEN_PRIVILEGES TokenPrivileges;
  277. //
  278. // First, find out the value of the privilege
  279. //
  280. if (!LookupPrivilegeValue(NULL, lpszPriv, &SetPrivilegeValue)) {
  281. return FALSE;
  282. }
  283. //
  284. // Set up the privilege set we will need
  285. //
  286. TokenPrivileges.PrivilegeCount = 1;
  287. TokenPrivileges.Privileges[0].Luid = SetPrivilegeValue;
  288. TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  289. if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges,
  290. sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
  291. return FALSE;
  292. }
  293. return TRUE;
  294. }
  295. BOOLEAN
  296. XCOPY::DoCopy (
  297. )
  298. /*++
  299. Routine Description:
  300. This is the function that performs the XCopy.
  301. Arguments:
  302. None.
  303. Return Value:
  304. None.
  305. Notes:
  306. --*/
  307. {
  308. PFSN_DIRECTORY SourceDirectory = NULL;
  309. PFSN_DIRECTORY DestinationDirectory = NULL;
  310. PFSN_DIRECTORY PartialDirectory = NULL;
  311. PATH PathToDelete;
  312. WCHAR Char;
  313. CHNUM CharsInPartialDirectoryPath;
  314. BOOLEAN DirDeleted;
  315. BOOLEAN CopyingManyFiles;
  316. PATH TmpPath;
  317. PFSN_FILTER FileFilter = NULL;
  318. PFSN_FILTER DirectoryFilter = NULL;
  319. WIN32_FIND_DATA FindData;
  320. PWSTRING Device = NULL;
  321. HANDLE FindHandle;
  322. //
  323. // Make sure that we won't try to copy to ourselves
  324. //
  325. if ( _SubdirSwitch && IsCyclicalCopy( _SourcePath, _DestinationPath ) ) {
  326. DisplayMessageAndExit( XCOPY_ERROR_CYCLE, NULL, EXIT_MISC_ERROR );
  327. }
  328. AbortIfCtrlC();
  329. //
  330. // Get the source directory object and the filename that we will be
  331. // matching.
  332. //
  333. GetDirectoryAndFilters( _SourcePath, &SourceDirectory, &FileFilter, &DirectoryFilter, &CopyingManyFiles );
  334. //
  335. // Make sure that we won't try to copy to ourselves
  336. //
  337. if ( _SubdirSwitch && IsCyclicalCopy( (PPATH)SourceDirectory->GetPath(), _DestinationPath ) ) {
  338. DisplayMessageAndExit( XCOPY_ERROR_CYCLE, NULL, EXIT_MISC_ERROR );
  339. }
  340. DebugPtrAssert( SourceDirectory );
  341. DebugPtrAssert( FileFilter );
  342. DebugPtrAssert( DirectoryFilter );
  343. if ( _WaitSwitch ) {
  344. // Pause before we start copying.
  345. //
  346. DisplayMessage( XCOPY_MESSAGE_WAIT );
  347. AbortIfCtrlC();
  348. //
  349. // All input is in raw mode.
  350. //
  351. _Keyboard->DisableLineMode();
  352. if( GetStandardInput()->IsAtEnd() ) {
  353. // Insufficient input--treat as CONTROL-C.
  354. //
  355. Char = ' ';
  356. } else {
  357. GetStandardInput()->ReadChar( &Char );
  358. }
  359. _Keyboard->EnableLineMode();
  360. if ( Char == CTRL_C ) {
  361. exit ( EXIT_TERMINATED );
  362. } else {
  363. GetStandardOutput()->WriteChar( Char );
  364. GetStandardOutput()->WriteChar( (WCHAR)'\r');
  365. GetStandardOutput()->WriteChar( (WCHAR)'\n');
  366. }
  367. }
  368. //
  369. // Get the destination directory and the file pattern.
  370. //
  371. GetDirectoryAndFilePattern( _DestinationPath, CopyingManyFiles, &_TargetPath, &_FileNamePattern );
  372. DebugPtrAssert( _TargetPath );
  373. DebugPtrAssert( _FileNamePattern );
  374. //
  375. // Get as much of the destination directory as possible.
  376. //
  377. if ( !_DontCopySwitch ) {
  378. PartialDirectory = SYSTEM::QueryDirectory( _TargetPath, TRUE );
  379. if (PartialDirectory == NULL ) {
  380. DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY, NULL, EXIT_MISC_ERROR );
  381. }
  382. //
  383. // All the directories up to the parent of the target have to exist. If
  384. // they don't, we have to create them.
  385. //
  386. if ( *(PartialDirectory->GetPath()->GetPathString()) ==
  387. *(_TargetPath->GetPathString()) ) {
  388. DestinationDirectory = PartialDirectory;
  389. } else {
  390. TmpPath.Initialize( _TargetPath );
  391. if( !_TargetIsFile ) {
  392. TmpPath.TruncateBase();
  393. }
  394. DestinationDirectory = PartialDirectory->CreateDirectoryPath( &TmpPath );
  395. }
  396. if( !DestinationDirectory ) {
  397. DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR );
  398. }
  399. //
  400. // Determine if destination if floppy
  401. //
  402. Device = _TargetPath->QueryDevice();
  403. if ( Device ) {
  404. _DisketteCopy = (SYSTEM::QueryDriveType( Device ) == RemovableDrive);
  405. DELETE( Device );
  406. }
  407. }
  408. if (_OwnerSwitch) {
  409. HANDLE hToken;
  410. // Enable the privileges necessary to copy security information.
  411. if (!GetTokenHandle(&hToken)) {
  412. DisplayMessageAndExit(XCOPY_ERROR_NO_MEMORY,
  413. NULL, EXIT_MISC_ERROR );
  414. }
  415. SetPrivs(hToken, TEXT("SeBackupPrivilege"));
  416. SetPrivs(hToken, TEXT("SeRestorePrivilege"));
  417. SetPrivs(hToken, TEXT("SeSecurityPrivilege"));
  418. SetPrivs(hToken, TEXT("SeTakeOwnershipPrivilege"));
  419. }
  420. //
  421. // Now traverse the source directory.
  422. //
  423. TmpPath.Initialize( _TargetPath );
  424. if (!_UpdateSwitch) {
  425. Traverse( SourceDirectory,
  426. &TmpPath,
  427. FileFilter,
  428. DirectoryFilter,
  429. !SourceDirectory->GetPath()->GetPathString()->Strcmp(
  430. _SourcePath->GetPathString()));
  431. } else {
  432. PATH DestDirectoryPath;
  433. PFSN_DIRECTORY DestDirectory;
  434. DestDirectoryPath.Initialize(&TmpPath);
  435. DestDirectory = SYSTEM::QueryDirectory(&DestDirectoryPath);
  436. TmpPath.Initialize(SourceDirectory->GetPath());
  437. UpdateTraverse( DestDirectory,
  438. &TmpPath,
  439. FileFilter,
  440. DirectoryFilter,
  441. !SourceDirectory->GetPath()->GetPathString()->Strcmp(
  442. _SourcePath->GetPathString()));
  443. DELETE(DestDirectory);
  444. }
  445. DELETE( _TargetPath);
  446. if (( _FilesCopied == 0 ) && _CanRemoveEmptyDirectories && !_DontCopySwitch ) {
  447. //
  448. // Delete any directories that we created
  449. //
  450. if ( PartialDirectory != DestinationDirectory ) {
  451. if (!PathToDelete.Initialize( DestinationDirectory->GetPath() )) {
  452. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  453. }
  454. CharsInPartialDirectoryPath = PartialDirectory->GetPath()->GetPathString()->QueryChCount();
  455. while ( PathToDelete.GetPathString()->QueryChCount() >
  456. CharsInPartialDirectoryPath ) {
  457. DirDeleted = DestinationDirectory->DeleteDirectory();
  458. DebugAssert( DirDeleted );
  459. DELETE( DestinationDirectory );
  460. PathToDelete.TruncateBase();
  461. DestinationDirectory = SYSTEM::QueryDirectory( &PathToDelete );
  462. DebugPtrAssert( DestinationDirectory );
  463. }
  464. }
  465. //
  466. // We display the "File not found" message only if there are no
  467. // files that match our pattern, regardless of other factors such
  468. // as attributes etc. This is just to maintain DOS5 compatibility.
  469. //
  470. TmpPath.Initialize( SourceDirectory->GetPath() );
  471. TmpPath.AppendBase( FileFilter->GetFileName() );
  472. if ((FindHandle = FindFirstFile( &TmpPath, &FindData )) == INVALID_HANDLE_VALUE ) {
  473. DisplayMessage( XCOPY_ERROR_FILE_NOT_FOUND, ERROR_MESSAGE, "%W", FileFilter->GetFileName() );
  474. }
  475. FindClose(FindHandle);
  476. }
  477. DELETE( SourceDirectory );
  478. if ( PartialDirectory != DestinationDirectory ) {
  479. DELETE( PartialDirectory );
  480. }
  481. DELETE( DestinationDirectory );
  482. DELETE( FileFilter );
  483. DELETE( DirectoryFilter );
  484. return TRUE;
  485. }
  486. BOOLEAN
  487. XCOPY::Traverse (
  488. IN PFSN_DIRECTORY Directory,
  489. IN OUT PPATH DestinationPath,
  490. IN PFSN_FILTER FileFilter,
  491. IN PFSN_FILTER DirectoryFilter,
  492. IN BOOLEAN CopyDirectoryStreams
  493. )
  494. /*++
  495. Routine Description:
  496. Traverses a directory, calling the callback function for each node
  497. (directory of file) visited. The traversal may be finished
  498. prematurely when the callback function returnes FALSE.
  499. The destination path is modified to reflect the directory structure
  500. being traversed.
  501. Arguments:
  502. Directory - Supplies pointer to directory to traverse
  503. DestinationPath - Supplies pointer to path to be used with the
  504. callback function.
  505. FileFilter - Supplies a pointer to the file filter.
  506. DirectoryFilter - Supplies a pointer to the directory filter.
  507. CopyDirectoryStreams - Specifies to copy directory streams when
  508. copying directories.
  509. Return Value:
  510. BOOLEAN - TRUE if everything traversed
  511. FALSE otherwise.
  512. --*/
  513. {
  514. PFSN_DIRECTORY TargetDirectory = NULL;
  515. PWSTRING CurrentPathStr;
  516. PWSTRING TargetPathStr;
  517. BOOLEAN MemoryOk;
  518. PFSN_FILE File;
  519. PFSN_DIRECTORY Dir;
  520. PWSTRING Name;
  521. BOOLEAN Created = FALSE;
  522. PCPATH TemplatePath = NULL;
  523. HANDLE h;
  524. PWSTRING CurrentFileName, PrevFileName;
  525. DWORD GetNextError = ERROR_SUCCESS;
  526. DebugPtrAssert( Directory );
  527. DebugPtrAssert( DestinationPath );
  528. DebugPtrAssert( FileFilter );
  529. DebugPtrAssert( DirectoryFilter );
  530. //
  531. // We only traverse this directory if it is not empty (unless the
  532. // empty switch is set).
  533. //
  534. if ( _EmptySwitch || !Directory->IsEmpty() ) {
  535. //
  536. // Create the target directory (if we are not copying to a file).
  537. //
  538. if ( !_TargetIsFile && !_DontCopySwitch ) {
  539. //
  540. // The target directory may not exist, create the
  541. // directory and remember that we might delete it if
  542. // no files or subdirectories were created.
  543. // Even if the directory exists, it may not have
  544. // all the streams/ACLs in it.
  545. if (CopyDirectoryStreams) {
  546. TemplatePath = Directory->GetPath();
  547. }
  548. if (TemplatePath == NULL) {
  549. TargetDirectory = SYSTEM::QueryDirectory( DestinationPath );
  550. }
  551. if (!TargetDirectory) {
  552. TargetDirectory = MakeDirectory( DestinationPath, TemplatePath );
  553. if (TargetDirectory && !_CopyAttrSwitch) {
  554. DWORD dwError;
  555. // always set the archive bit so that it gets backup
  556. TargetDirectory->MakeArchived(&dwError);
  557. }
  558. Created = TRUE;
  559. }
  560. if ( !TargetDirectory ) {
  561. //
  562. // If the Continue Switch is set, we just display an error message and
  563. // continue, otherwise we exit with error.
  564. //
  565. if ( _ContinueSwitch ) {
  566. DisplayMessage( XCOPY_ERROR_CREATE_DIRECTORY1, ERROR_MESSAGE, "%W", DestinationPath->GetPathString() );
  567. return TRUE;
  568. } else {
  569. DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY1,
  570. (PWSTRING)DestinationPath->GetPathString(),
  571. EXIT_MISC_ERROR );
  572. }
  573. }
  574. if( !_CopyAttrSwitch ) {
  575. TargetDirectory->ResetReadOnlyAttribute();
  576. }
  577. }
  578. //
  579. // Iterate through all files and copy them as needed
  580. //
  581. MemoryOk = TRUE;
  582. h = NULL;
  583. CurrentFileName = PrevFileName = NULL;
  584. while ( MemoryOk &&
  585. (( File = (PFSN_FILE)Directory->GetNext( &h, &GetNextError )) != NULL )) {
  586. //
  587. // Don't know how expensive it is to check for infinite loop below
  588. //
  589. if (PrevFileName) {
  590. CurrentFileName = File->QueryName();
  591. if (PrevFileName && CurrentFileName) {
  592. if (CurrentFileName->Strcmp(PrevFileName) == 0) {
  593. // something went wrong
  594. // GetNext should not return two files of the same name
  595. DELETE(File);
  596. DELETE(PrevFileName);
  597. DELETE(CurrentFileName);
  598. if (_ContinueSwitch) {
  599. DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE );
  600. break;
  601. } else {
  602. DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR );
  603. }
  604. }
  605. } else
  606. MemoryOk = FALSE;
  607. DELETE(PrevFileName);
  608. PrevFileName = CurrentFileName;
  609. CurrentFileName = NULL;
  610. if (!MemoryOk)
  611. break;
  612. } else
  613. PrevFileName = File->QueryName();
  614. if ( !FileFilter->DoesNodeMatch( (PFSNODE)File ) ) {
  615. DELETE(File);
  616. continue;
  617. }
  618. DebugAssert( !File->IsDirectory() );
  619. // If we're supposed to use the short name then convert fsnode.
  620. if (_UseShortSwitch && !File->UseAlternateName()) {
  621. DELETE(File);
  622. MemoryOk = FALSE;
  623. continue;
  624. }
  625. //
  626. // Append the name portion of the node to the destination path.
  627. //
  628. Name = File->QueryName();
  629. DebugPtrAssert( Name );
  630. if ( Name ) {
  631. MemoryOk = DestinationPath->AppendBase( Name );
  632. DebugAssert( MemoryOk );
  633. DELETE( Name );
  634. if ( MemoryOk ) {
  635. //
  636. // Copy the file
  637. //
  638. if ( !Copier( File, DestinationPath ) ) {
  639. DELETE(File);
  640. ExitProgram( EXIT_MISC_ERROR );
  641. }
  642. //
  643. // Restore the destination path
  644. //
  645. DestinationPath->TruncateBase();
  646. }
  647. } else {
  648. MemoryOk = FALSE;
  649. }
  650. DELETE(File);
  651. }
  652. DELETE(PrevFileName);
  653. DELETE(CurrentFileName);
  654. if ( MemoryOk && (_ContinueSwitch || (ERROR_SUCCESS == GetNextError) ||
  655. (ERROR_NO_MORE_FILES == GetNextError))) {
  656. //
  657. // If recursing, Traverse all the subdirectories
  658. //
  659. if ( _SubdirSwitch ) {
  660. MemoryOk = TRUE;
  661. h = NULL;
  662. if (Created) {
  663. TargetPathStr = TargetDirectory->GetPath()->QueryFullPathString();
  664. MemoryOk = (TargetPathStr != NULL);
  665. } else
  666. TargetPathStr = NULL;
  667. CurrentFileName = PrevFileName = NULL;
  668. //
  669. // Recurse thru all the subdirectories
  670. //
  671. while ( MemoryOk &&
  672. (( Dir = (PFSN_DIRECTORY)Directory->GetNext( &h, &GetNextError )) != NULL )) {
  673. //
  674. // Don't know how expensive it is to check for infinite loop below
  675. //
  676. if (PrevFileName) {
  677. CurrentFileName = Dir->QueryName();
  678. if (PrevFileName && CurrentFileName) {
  679. if (CurrentFileName->Strcmp(PrevFileName) == 0) {
  680. // something went wrong
  681. // GetNext should not return two files of the same name
  682. DELETE(Dir);
  683. DELETE(PrevFileName);
  684. DELETE(CurrentFileName);
  685. if (_ContinueSwitch) {
  686. DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE );
  687. break;
  688. } else {
  689. DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR );
  690. }
  691. }
  692. } else
  693. MemoryOk = FALSE;
  694. DELETE(PrevFileName);
  695. PrevFileName = CurrentFileName;
  696. CurrentFileName = NULL;
  697. if (!MemoryOk)
  698. break;
  699. } else
  700. PrevFileName = Dir->QueryName();
  701. if ( !DirectoryFilter->DoesNodeMatch( (PFSNODE)Dir ) ) {
  702. DELETE(Dir);
  703. continue;
  704. }
  705. if (_ExclusionList != NULL &&
  706. IsExcluded( Dir->GetPath() ) ) {
  707. DELETE(Dir);
  708. continue;
  709. }
  710. if (Created) {
  711. CurrentPathStr = Dir->GetPath()->QueryFullPathString();
  712. if (CurrentPathStr == NULL) {
  713. DELETE(Dir);
  714. MemoryOk = FALSE;
  715. continue;
  716. }
  717. if (TargetPathStr->Stricmp(CurrentPathStr) == 0) {
  718. DELETE(CurrentPathStr);
  719. DELETE(Dir);
  720. continue;
  721. }
  722. DELETE(CurrentPathStr);
  723. }
  724. DebugAssert( Dir->IsDirectory() );
  725. // If we're using short names then convert this fsnode.
  726. if (_UseShortSwitch && !Dir->UseAlternateName()) {
  727. DELETE(Dir);
  728. MemoryOk = FALSE;
  729. continue;
  730. }
  731. //
  732. // Append the name portion of the node to the destination path.
  733. //
  734. Name = Dir->QueryName();
  735. DebugPtrAssert( Name );
  736. if ( Name ) {
  737. MemoryOk = DestinationPath->AppendBase( Name );
  738. DebugAssert( MemoryOk );
  739. DELETE( Name );
  740. _CanRemoveEmptyDirectories = (BOOLEAN)!_EmptySwitch;
  741. if ( MemoryOk ) {
  742. //
  743. // Recurse
  744. //
  745. Traverse( Dir,
  746. DestinationPath, FileFilter,
  747. DirectoryFilter, TRUE );
  748. //
  749. // Restore the destination path
  750. //
  751. DestinationPath->TruncateBase();
  752. }
  753. } else {
  754. MemoryOk = FALSE;
  755. }
  756. DELETE(Dir);
  757. }
  758. DELETE(PrevFileName);
  759. DELETE(CurrentFileName);
  760. if (TargetPathStr)
  761. DELETE(TargetPathStr);
  762. }
  763. }
  764. if ( !MemoryOk ) {
  765. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  766. }
  767. //
  768. // If we created this directory but did not copy anything to it, we
  769. // have to remove it.
  770. //
  771. if ( Created && TargetDirectory->IsEmpty() && !_EmptySwitch && !_StructureOnlySwitch) {
  772. SYSTEM::RemoveNode( (PFSNODE *)&TargetDirectory, TRUE );
  773. } else {
  774. DELETE( TargetDirectory );
  775. }
  776. if ((ERROR_NO_MORE_FILES != GetNextError) && (ERROR_SUCCESS != GetNextError)) {
  777. //
  778. // Some other error when traversing a directory. We will already have
  779. // exited whatever loop we were inside due to the NULL file return.
  780. //
  781. SYSTEM::DisplaySystemError( GetNextError, !_ContinueSwitch);
  782. }
  783. }
  784. return TRUE;
  785. }
  786. BOOLEAN
  787. XCOPY::UpdateTraverse (
  788. IN PFSN_DIRECTORY DestDirectory,
  789. IN OUT PPATH SourcePath,
  790. IN PFSN_FILTER FileFilter,
  791. IN PFSN_FILTER DirectoryFilter,
  792. IN BOOLEAN CopyDirectoryStreams
  793. )
  794. /*++
  795. Routine Description:
  796. Traverse routine for update.
  797. Like XCOPY::Traverse, except we traverse the *destination*
  798. directory, possibly updating files we find there. The theory
  799. being that there will be fewer files in the destination than
  800. the source, so we can save time this way.
  801. The callback function is invoked on each node
  802. (directory or file) visited. The traversal may be finished
  803. prematurely when the callback function returns FALSE.
  804. Arguments:
  805. DestDirectory - Supplies pointer to destination directory
  806. SourcePath - Supplies pointer to path to be used with the
  807. callback function.
  808. FileFilter - Supplies a pointer to the file filter.
  809. DirectoryFilter - Supplies a pointer to the directory filter.
  810. CopyDirectoryStreams - Specifies to copy directory streams when
  811. copying directories.
  812. Return Value:
  813. BOOLEAN - TRUE if everything traversed
  814. FALSE otherwise.
  815. --*/
  816. {
  817. BOOLEAN MemoryOk;
  818. PFSN_FILE File;
  819. PFSN_DIRECTORY Dir;
  820. PWSTRING Name;
  821. BOOLEAN Created = FALSE;
  822. PCPATH TemplatePath = NULL;
  823. HANDLE h;
  824. PWSTRING CurrentFileName, PrevFileName;
  825. DWORD GetNextError = ERROR_SUCCESS;
  826. DebugPtrAssert( SourcePath );
  827. DebugPtrAssert( FileFilter );
  828. DebugPtrAssert( DirectoryFilter );
  829. // Don't bother to traverse if
  830. // destination directory is null
  831. if (!DestDirectory)
  832. return TRUE;
  833. //
  834. // We only traverse this directory if it is not empty (unless the
  835. // empty switch is set).
  836. //
  837. if ( _EmptySwitch || !DestDirectory->IsEmpty() ) {
  838. //
  839. // Iterate through all files and copy them as needed
  840. //
  841. MemoryOk = TRUE;
  842. h = NULL;
  843. CurrentFileName = PrevFileName = NULL;
  844. while (MemoryOk &&
  845. ((File = (PFSN_FILE)DestDirectory->GetNext( &h, &GetNextError )) != NULL)) {
  846. //
  847. // Don't know how expensive it is to check for infinite loop below
  848. //
  849. if (PrevFileName) {
  850. CurrentFileName = File->QueryName();
  851. if (PrevFileName && CurrentFileName) {
  852. if (CurrentFileName->Strcmp(PrevFileName) == 0) {
  853. // something went wrong
  854. // GetNext should not return two files of the same name
  855. DELETE(File);
  856. DELETE(PrevFileName);
  857. DELETE(CurrentFileName);
  858. if (_ContinueSwitch) {
  859. DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE );
  860. break;
  861. } else {
  862. DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR );
  863. }
  864. }
  865. } else
  866. MemoryOk = FALSE;
  867. DELETE(PrevFileName);
  868. PrevFileName = CurrentFileName;
  869. CurrentFileName = NULL;
  870. if (!MemoryOk)
  871. break;
  872. } else
  873. PrevFileName = File->QueryName();
  874. if ( !FileFilter->DoesNodeMatch( (PFSNODE)File ) ) {
  875. DELETE(File);
  876. continue;
  877. }
  878. DebugAssert( !File->IsDirectory() );
  879. // If we're supposed to use the short name then convert fsnode.
  880. if (_UseShortSwitch && !File->UseAlternateName()) {
  881. DELETE(File);
  882. MemoryOk = FALSE;
  883. continue;
  884. }
  885. //
  886. // Append the name portion of the node to the destination path.
  887. //
  888. Name = File->QueryName();
  889. DebugPtrAssert( Name );
  890. if ( Name ) {
  891. PFSN_FILE SourceFile;
  892. PATH DestinationPath;
  893. PATH TmpPath;
  894. TmpPath.Initialize(SourcePath);
  895. TmpPath.AppendBase(Name);
  896. SourceFile = SYSTEM::QueryFile(&TmpPath);
  897. DestinationPath.Initialize(DestDirectory->GetPath());
  898. MemoryOk = DestinationPath.AppendBase( Name );
  899. DebugAssert( MemoryOk );
  900. DELETE( Name );
  901. if ( MemoryOk && NULL != SourceFile ) {
  902. //
  903. // Copy the file
  904. //
  905. if ( !Copier( SourceFile, &DestinationPath ) ) {
  906. DELETE(SourceFile);
  907. DELETE(File);
  908. ExitProgram( EXIT_MISC_ERROR );
  909. }
  910. }
  911. DELETE(SourceFile);
  912. } else {
  913. MemoryOk = FALSE;
  914. }
  915. DELETE(File);
  916. }
  917. DELETE(PrevFileName);
  918. DELETE(CurrentFileName);
  919. if ( MemoryOk && (_ContinueSwitch || (ERROR_SUCCESS == GetNextError) ||
  920. (ERROR_NO_MORE_FILES == GetNextError))) {
  921. //
  922. // If recursing, Traverse all the subdirectories
  923. //
  924. if ( _SubdirSwitch ) {
  925. MemoryOk = TRUE;
  926. h = NULL;
  927. CurrentFileName = PrevFileName = NULL;
  928. //
  929. // Recurse thru all the subdirectories
  930. //
  931. while (MemoryOk &&
  932. ((Dir = (PFSN_DIRECTORY)DestDirectory->GetNext( &h, &GetNextError )) != NULL)) {
  933. //
  934. // Don't know how expensive it is to check for infinite loop below
  935. //
  936. if (PrevFileName) {
  937. CurrentFileName = Dir->QueryName();
  938. if (PrevFileName && CurrentFileName) {
  939. if (CurrentFileName->Strcmp(PrevFileName) == 0) {
  940. // something went wrong
  941. // GetNext should not return two files of the same name
  942. DELETE(Dir);
  943. DELETE(PrevFileName);
  944. DELETE(CurrentFileName);
  945. if (_ContinueSwitch) {
  946. DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE );
  947. break;
  948. } else {
  949. DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR );
  950. }
  951. }
  952. } else
  953. MemoryOk = FALSE;
  954. DELETE(PrevFileName);
  955. PrevFileName = CurrentFileName;
  956. CurrentFileName = NULL;
  957. if (!MemoryOk)
  958. break;
  959. } else
  960. PrevFileName = Dir->QueryName();
  961. if ( !DirectoryFilter->DoesNodeMatch( (PFSNODE)Dir ) ) {
  962. DELETE(Dir);
  963. continue;
  964. }
  965. DebugAssert( Dir->IsDirectory() );
  966. // If we're using short names then convert this fsnode.
  967. if (_UseShortSwitch && !Dir->UseAlternateName()) {
  968. DELETE(Dir);
  969. MemoryOk = FALSE;
  970. continue;
  971. }
  972. //
  973. // Append the name portion of the node to the destination
  974. // path.
  975. //
  976. Name = Dir->QueryName();
  977. DebugPtrAssert( Name );
  978. if ( Name ) {
  979. MemoryOk = SourcePath->AppendBase( Name );
  980. DebugAssert( MemoryOk );
  981. DELETE( Name );
  982. _CanRemoveEmptyDirectories = (BOOLEAN)!_EmptySwitch;
  983. if ( MemoryOk ) {
  984. if( _ExclusionList != NULL &&
  985. IsExcluded( SourcePath ) ) {
  986. SourcePath->TruncateBase();
  987. DELETE(Dir);
  988. continue;
  989. }
  990. //
  991. // Recurse
  992. //
  993. UpdateTraverse( Dir, SourcePath,
  994. FileFilter, DirectoryFilter, TRUE );
  995. }
  996. SourcePath->TruncateBase();
  997. } else {
  998. MemoryOk = FALSE;
  999. }
  1000. DELETE(Dir);
  1001. }
  1002. DELETE(PrevFileName);
  1003. DELETE(CurrentFileName);
  1004. }
  1005. }
  1006. if ( !MemoryOk ) {
  1007. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1008. }
  1009. else if ((ERROR_NO_MORE_FILES != GetNextError) && (ERROR_SUCCESS != GetNextError)) {
  1010. //
  1011. // Some other error when traversing a directory.
  1012. //
  1013. SYSTEM::DisplaySystemError( GetNextError, !_ContinueSwitch);
  1014. }
  1015. }
  1016. return TRUE;
  1017. }
  1018. XCOPY::ProgressCallBack(
  1019. LARGE_INTEGER TotalFileSize,
  1020. LARGE_INTEGER TotalBytesTransferred,
  1021. LARGE_INTEGER StreamSize,
  1022. LARGE_INTEGER StreamBytesTransferred,
  1023. DWORD dwStreamNumber,
  1024. DWORD dwCallbackReason,
  1025. HANDLE hSourceFile,
  1026. HANDLE hDestinationFile,
  1027. LPVOID lpData OPTIONAL
  1028. )
  1029. /*++
  1030. Routine Description:
  1031. Callback routine passed to CopyFileEx.
  1032. Check to see if the user hit Ctrl-C and return appropriate
  1033. value to CopyFileEx.
  1034. Arguments:
  1035. TotalFileSize - Total size of the file in bytes.
  1036. TotalBytesTransferred - Total number of bytes transferred.
  1037. StreamSize - Size of the stream being copied in bytes.
  1038. StreamBytesTransferred - Number of bytes in current stream transferred.
  1039. dwStreamNumber - Stream number of the current stream.
  1040. dwCallBackReason - CALLBACK_CHUNK_FINISHED if a block was transferred,
  1041. CALLBACK_STREAM_SWITCH if a stream completed copying.
  1042. hSourceFile - Handle to the source file.
  1043. hDestinationFile - Handle to the destination file.
  1044. lpData - Pointer to opaque data that was passed to CopyFileEx. Used
  1045. in this instance to pass the "this" pointer to an XCOPY object.
  1046. Return Value:
  1047. DWORD - PROGRESS_STOP if a Ctrl-C was hit and the copy was restartable,
  1048. PROGESS_CANCEL otherwise.
  1049. --*/
  1050. {
  1051. FILETIME LastWriteTime;
  1052. //
  1053. // If the file was just created then roll back LastWriteTime a little so a subsequent
  1054. // xcopy /d /z
  1055. // will work if the copy was interrupted.
  1056. //
  1057. if ( dwStreamNumber == 1 && dwCallbackReason == CALLBACK_STREAM_SWITCH )
  1058. {
  1059. if ( GetFileTime(hSourceFile, NULL, NULL, &LastWriteTime) )
  1060. {
  1061. LastWriteTime.dwLowDateTime -= 1000;
  1062. SetFileTime(hDestinationFile, NULL, NULL, &LastWriteTime);
  1063. }
  1064. }
  1065. switch (dwCallbackReason) {
  1066. case PRIVCALLBACK_STREAMS_NOT_SUPPORTED:
  1067. case PRIVCALLBACK_COMPRESSION_NOT_SUPPORTED:
  1068. case PRIVCALLBACK_ENCRYPTION_NOT_SUPPORTED:
  1069. case PRIVCALLBACK_EAS_NOT_SUPPORTED:
  1070. case PRIVCALLBACK_SPARSE_NOT_SUPPORTED:
  1071. return PROGRESS_CONTINUE;
  1072. case PRIVCALLBACK_ENCRYPTION_FAILED:
  1073. // GetLastError will return ERROR_NOT_SUPPORTED if PROGRESS_STOP is
  1074. // returned. The message is misleading so display our own error
  1075. // message and return PROGRESS_CANCEL.
  1076. ((XCOPY *)lpData)->DisplayMessage(XCOPY_ERROR_ENCRYPTION_FAILED);
  1077. return PROGRESS_CANCEL;
  1078. case PRIVCALLBACK_COMPRESSION_FAILED:
  1079. case PRIVCALLBACK_SPARSE_FAILED:
  1080. case PRIVCALLBACK_DACL_ACCESS_DENIED:
  1081. case PRIVCALLBACK_SACL_ACCESS_DENIED:
  1082. case PRIVCALLBACK_OWNER_GROUP_ACCESS_DENIED:
  1083. case PRIVCALLBACK_OWNER_GROUP_FAILED:
  1084. // display whatever GetLastError() contains
  1085. return PROGRESS_STOP;
  1086. case PRIVCALLBACK_SECURITY_INFORMATION_NOT_SUPPORTED:
  1087. // GetLastError will return ERROR_NOT_SUPPORTED if PROGRESS_STOP is
  1088. // returned. The message is misleading so display our own error
  1089. // message and return PROGRESS_CANCEL.
  1090. ((XCOPY *)lpData)->DisplayMessage(XCOPY_ERROR_SECURITY_INFO_NOT_SUPPORTED);
  1091. return PROGRESS_CANCEL;
  1092. }
  1093. // GetPFlagBreak returns a pointer to the flag indicating whether a Ctrl-C was hit
  1094. if ( *((( XCOPY *) lpData)->_Keyboard->GetPFlagBreak()) )
  1095. return ((XCOPY *) lpData)->_RestartableSwitch ? PROGRESS_STOP : PROGRESS_CANCEL;
  1096. return PROGRESS_CONTINUE;
  1097. }
  1098. BOOLEAN
  1099. XCOPY::Copier (
  1100. IN OUT PFSN_FILE File,
  1101. IN PPATH DestinationPath
  1102. )
  1103. /*++
  1104. Routine Description:
  1105. This is the heart of XCopy. This is the guy who actually does
  1106. the copying.
  1107. Arguments:
  1108. File - Supplies pointer to the source File.
  1109. DestinationPath - Supplies path of the desired destination.
  1110. Return Value:
  1111. BOOLEAN - TRUE if copy successful.
  1112. FALSE otherwise
  1113. Notes:
  1114. --*/
  1115. {
  1116. PATH PathToCopy;
  1117. PCWSTRING Name;
  1118. COPY_ERROR CopyError;
  1119. PFSN_FILE TargetFile = NULL;
  1120. BOOLEAN Proceed;
  1121. DWORD Attempts;
  1122. WCHAR PathBuffer[MAX_PATH + 3];
  1123. FSTRING WriteBuffer;
  1124. FSTRING EndOfLine;
  1125. DSTRING ErrorMessage;
  1126. PATH CanonSourcePath;
  1127. BOOLEAN badCopy;
  1128. ULONG flags;
  1129. PTIMEINFO SourceFileTime, TargetFileTime;
  1130. BOOLEAN TargetFileEncrypted, TargetFileExist;
  1131. EndOfLine.Initialize((PWSTR) L"\r\n");
  1132. PathBuffer[0] = 0;
  1133. WriteBuffer.Initialize(PathBuffer, MAX_PATH+3);
  1134. //
  1135. // Maximum number of attempts to copy a file
  1136. //
  1137. #define MAX_ATTEMPTS 3
  1138. AbortIfCtrlC();
  1139. _CanRemoveEmptyDirectories = FALSE;
  1140. if( _ExclusionList != NULL && IsExcluded( File->GetPath() ) ) {
  1141. return TRUE;
  1142. }
  1143. if ( _TargetIsFile ) {
  1144. //
  1145. // We replace the entire path
  1146. //
  1147. PathToCopy.Initialize( _TargetPath->GetPathString() );
  1148. PathToCopy.AppendBase( _FileNamePattern );
  1149. } else {
  1150. //
  1151. // Set the correct target file name.
  1152. //
  1153. PathToCopy.Initialize( DestinationPath );
  1154. if (!PathToCopy.ModifyName( _FileNamePattern )) {
  1155. _Message.Set(MSG_COMP_UNABLE_TO_EXPAND);
  1156. _Message.Display("%W%W", PathToCopy.QueryName(),
  1157. _FileNamePattern);
  1158. return FALSE;
  1159. }
  1160. }
  1161. //
  1162. // If in Update or CopyIfOld mode, determine if the target file
  1163. // already exists and if it is older than the source file.
  1164. //
  1165. TargetFile = SYSTEM::QueryFile( &PathToCopy );
  1166. if (TargetFile) {
  1167. TargetFileEncrypted = TargetFile->IsEncrypted();
  1168. TargetFileExist = TRUE;
  1169. } else
  1170. TargetFileEncrypted = TargetFileExist = FALSE;
  1171. if ( _CopyIfOldSwitch || _UpdateSwitch ) {
  1172. if ( TargetFile ) {
  1173. //
  1174. // Target exists. If in CopyIfOld mode, copy only if target
  1175. // is older. If in Update mode, copy always.
  1176. //
  1177. if ( _CopyIfOldSwitch ) {
  1178. SourceFileTime = File->QueryTimeInfo();
  1179. TargetFileTime = TargetFile->QueryTimeInfo();
  1180. if (SourceFileTime && TargetFileTime)
  1181. Proceed = (*SourceFileTime > *TargetFileTime);
  1182. else {
  1183. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1184. }
  1185. DELETE(SourceFileTime);
  1186. DELETE(TargetFileTime);
  1187. } else {
  1188. Proceed = TRUE;
  1189. }
  1190. if ( !Proceed ) {
  1191. DELETE( TargetFile );
  1192. return TRUE;
  1193. }
  1194. } else if ( _UpdateSwitch ) {
  1195. //
  1196. // In update mode but target does not exist. We do not
  1197. // copy.
  1198. //
  1199. return TRUE;
  1200. }
  1201. }
  1202. DELETE(TargetFile);
  1203. //
  1204. // If the target is a file, we use that file path. Otherwise
  1205. // we figure out the correct path for the destination. Then
  1206. // we do the copy.
  1207. //
  1208. Name = File->GetPath()->GetPathString();
  1209. //
  1210. // Make sure that we are not copying to ourselves
  1211. //
  1212. CanonSourcePath.Initialize(Name, TRUE);
  1213. badCopy = (*(CanonSourcePath.GetPathString()) == *(PathToCopy.GetPathString()));
  1214. if ( (!_PromptSwitch ||
  1215. UserConfirmedCopy( File->GetPath()->GetPathString(),
  1216. _VerboseSwitch ? PathToCopy.GetPathString() : NULL )) &&
  1217. (badCopy ||
  1218. _OverWriteSwitch ||
  1219. !TargetFileExist ||
  1220. UserConfirmedOverWrite( &PathToCopy )) ) {
  1221. //
  1222. // If we are not prompting, we display the file name (unless we
  1223. // are in silent mode ).
  1224. //
  1225. if ( !_PromptSwitch && !_SilentSwitch && !_StructureOnlySwitch ) {
  1226. if ( _VerboseSwitch ) {
  1227. DisplayMessage( XCOPY_MESSAGE_VERBOSE_COPY, NORMAL_MESSAGE, "%W%W", Name, PathToCopy.GetPathString() );
  1228. } else {
  1229. WriteBuffer.Resize(0);
  1230. if (WriteBuffer.Strcat(Name) &&
  1231. WriteBuffer.Strcat(&EndOfLine)) {
  1232. GetStandardOutput()->WriteString(&WriteBuffer);
  1233. } else {
  1234. DisplayMessage( XCOPY_ERROR_PATH_TOO_LONG, ERROR_MESSAGE );
  1235. }
  1236. }
  1237. }
  1238. //
  1239. // Make sure that we are not copying to ourselves
  1240. //
  1241. if (badCopy) {
  1242. DisplayMessageAndExit( XCOPY_ERROR_SELF_COPY, NULL, EXIT_MISC_ERROR );
  1243. }
  1244. //
  1245. // Copy file (unless we are in display-only mode)
  1246. //
  1247. if ( _DontCopySwitch || _StructureOnlySwitch ) {
  1248. _FilesCopied++;
  1249. } else {
  1250. Attempts = 0;
  1251. while ( TRUE ) {
  1252. LPPROGRESS_ROUTINE Progress = NULL;
  1253. PBOOL PCancelFlag = NULL;
  1254. BOOLEAN bSuccess;
  1255. //
  1256. // If copying to floppy, we must determine if there is
  1257. // enough disk space for the file, and if not then we
  1258. // must ask for another disk and create all the directory
  1259. // structure up to the parent directory.
  1260. //
  1261. if ( _DisketteCopy ) {
  1262. if (!CheckTargetSpace( File, &PathToCopy ))
  1263. return FALSE;
  1264. }
  1265. Progress = (LPPROGRESS_ROUTINE) ProgressCallBack;
  1266. PCancelFlag = _Keyboard->GetPFlagBreak();
  1267. flags = (_ReadOnlySwitch ? FSN_FILE_COPY_OVERWRITE_READ_ONLY : 0);
  1268. flags |= (!_CopyAttrSwitch ? FSN_FILE_COPY_RESET_READ_ONLY : 0);
  1269. flags |= (_RestartableSwitch ? FSN_FILE_COPY_RESTARTABLE : 0);
  1270. flags |= (_OwnerSwitch ? FSN_FILE_COPY_COPY_OWNER : 0);
  1271. flags |= (_AuditSwitch ? FSN_FILE_COPY_COPY_ACL : 0);
  1272. flags |= (_DecryptSwitch ? FSN_FILE_COPY_ALLOW_DECRYPTED_DESTINATION : 0);
  1273. bSuccess = File->Copy(&PathToCopy, &CopyError, flags,
  1274. Progress, (VOID *)this,
  1275. PCancelFlag);
  1276. if (bSuccess) {
  1277. if (!_CopyAttrSwitch && (TargetFile = SYSTEM::QueryFile( &PathToCopy )) ) {
  1278. DWORD dwError;
  1279. TargetFile->MakeArchived(&dwError);
  1280. DELETE(TargetFile);
  1281. }
  1282. if ( _ModifySwitch ) {
  1283. File->ResetArchivedAttribute();
  1284. }
  1285. if( _VerifySwitch ) {
  1286. // Check that the new file is the same length as
  1287. // the old file.
  1288. //
  1289. if( (TargetFile = SYSTEM::QueryFile( &PathToCopy )) == NULL ||
  1290. TargetFile->QuerySize() != File->QuerySize() ) {
  1291. DELETE( TargetFile );
  1292. DisplayMessage( XCOPY_ERROR_VERIFY_FAILED, ERROR_MESSAGE );
  1293. if ( !_ContinueSwitch ) {
  1294. return FALSE;
  1295. }
  1296. break;
  1297. }
  1298. DELETE( TargetFile );
  1299. }
  1300. _FilesCopied++;
  1301. break;
  1302. } else {
  1303. //
  1304. // If the copy was cancelled mid-stream, exit.
  1305. //
  1306. AbortIfCtrlC();
  1307. if (CopyError == COPY_ERROR_REQUEST_ABORTED)
  1308. return FALSE;
  1309. if (CopyError == COPY_ERROR_ACCESS_DENIED &&
  1310. TargetFileExist && TargetFileEncrypted) {
  1311. Attempts = MAX_ATTEMPTS;
  1312. }
  1313. //
  1314. // In case of error, wait for a little while and try
  1315. // again, otherwise display the error.
  1316. //
  1317. if ( Attempts++ < MAX_ATTEMPTS ) {
  1318. Sleep( 100 );
  1319. } else {
  1320. switch ( CopyError ) {
  1321. case COPY_ERROR_ACCESS_DENIED:
  1322. DisplayMessage( XCOPY_ERROR_ACCESS_DENIED, ERROR_MESSAGE);
  1323. break;
  1324. case COPY_ERROR_SHARE_VIOLATION:
  1325. DisplayMessage( XCOPY_ERROR_SHARING_VIOLATION, ERROR_MESSAGE);
  1326. break;
  1327. default:
  1328. //
  1329. // At this point we don't know if the copy left a
  1330. // bogus file on disk. If the target file exist,
  1331. // we assume that it is bogus so we delete it.
  1332. //
  1333. if ((TargetFile = SYSTEM::QueryFile( &PathToCopy )) &&
  1334. !_RestartableSwitch) {
  1335. TargetFile->DeleteFromDisk( TRUE );
  1336. DELETE( TargetFile );
  1337. }
  1338. switch ( CopyError ) {
  1339. case COPY_ERROR_DISK_FULL:
  1340. DisplayMessageAndExit( XCOPY_ERROR_DISK_FULL, NULL, EXIT_MISC_ERROR );
  1341. break;
  1342. default:
  1343. if (SYSTEM::QueryWindowsErrorMessage(CopyError, &ErrorMessage)) {
  1344. DisplayMessage( XCOPY_ERROR_CANNOT_MAKE, ERROR_MESSAGE, "%W", &ErrorMessage );
  1345. }
  1346. break;
  1347. }
  1348. break;
  1349. }
  1350. if ( !_ContinueSwitch ) {
  1351. return FALSE;
  1352. }
  1353. break;
  1354. }
  1355. }
  1356. }
  1357. }
  1358. }
  1359. DELETE( TargetFile );
  1360. return TRUE;
  1361. }
  1362. PFSN_DIRECTORY
  1363. XCOPY::MakeDirectory (
  1364. IN PPATH DestinationPath,
  1365. IN PCPATH TemplatePath
  1366. )
  1367. /*++
  1368. Routine Description:
  1369. This is the heart of XCopy. This is the guy who actually does
  1370. the copying.
  1371. Arguments:
  1372. DestinationPath - Supplies path of the desired directory
  1373. TemplatePath - Supplies path of the source directory
  1374. Return Value:
  1375. BOOLEAN - TRUE if copy successful. FALSE otherwise.
  1376. Notes:
  1377. --*/
  1378. {
  1379. ULONG flags;
  1380. BOOLEAN bSuccess;
  1381. PFSN_DIRECTORY rtn;
  1382. DSTRING ErrorMessage;
  1383. COPY_ERROR CopyError;
  1384. flags = (_RestartableSwitch ? FSN_FILE_COPY_RESTARTABLE : 0);
  1385. flags |= (_OwnerSwitch ? FSN_FILE_COPY_COPY_OWNER : 0);
  1386. flags |= (_AuditSwitch ? FSN_FILE_COPY_COPY_ACL : 0);
  1387. rtn = SYSTEM::MakeDirectory( DestinationPath,
  1388. TemplatePath,
  1389. &CopyError,
  1390. (LPPROGRESS_ROUTINE)ProgressCallBack,
  1391. (VOID *)this,
  1392. _Keyboard->GetPFlagBreak(),
  1393. flags );
  1394. if (rtn == NULL) {
  1395. //
  1396. // If the copy was cancelled mid-stream, exit.
  1397. //
  1398. AbortIfCtrlC();
  1399. switch ( CopyError ) {
  1400. case COPY_ERROR_SUCCESS:
  1401. DisplayMessage( XCOPY_ERROR_UNKNOWN, ERROR_MESSAGE);
  1402. break;
  1403. case COPY_ERROR_REQUEST_ABORTED:
  1404. break;
  1405. case COPY_ERROR_ACCESS_DENIED:
  1406. DisplayMessage( XCOPY_ERROR_ACCESS_DENIED, ERROR_MESSAGE);
  1407. break;
  1408. case COPY_ERROR_SHARE_VIOLATION:
  1409. DisplayMessage( XCOPY_ERROR_SHARING_VIOLATION, ERROR_MESSAGE);
  1410. break;
  1411. case COPY_ERROR_DISK_FULL:
  1412. DisplayMessageAndExit( XCOPY_ERROR_DISK_FULL, NULL, EXIT_MISC_ERROR );
  1413. break;
  1414. default:
  1415. if (SYSTEM::QueryWindowsErrorMessage(CopyError, &ErrorMessage))
  1416. DisplayMessage( XCOPY_ERROR_CANNOT_MAKE, ERROR_MESSAGE, "%W", &ErrorMessage );
  1417. break;
  1418. }
  1419. }
  1420. return rtn;
  1421. }
  1422. BOOLEAN
  1423. XCOPY::CheckTargetSpace (
  1424. IN OUT PFSN_FILE File,
  1425. IN PPATH DestinationPath
  1426. )
  1427. /*++
  1428. Routine Description:
  1429. Makes sure that there is enought disk space in the target disk.
  1430. Asks the user to change the disk if necessary.
  1431. Arguments:
  1432. File - Supplies pointer to the source File.
  1433. DestinationPath - Supplies path of the desired destination.
  1434. Return Value:
  1435. BOOLEAN - TRUE if OK
  1436. FALSE otherwise
  1437. --*/
  1438. {
  1439. PFSN_FILE TargetFile = NULL;
  1440. BIG_INT TargetSize;
  1441. PWSTRING TargetDrive;
  1442. WCHAR Resp;
  1443. DSTRING TargetRoot;
  1444. DSTRING Slash;
  1445. PATH TmpPath;
  1446. PATH TmpPath1;
  1447. PFSN_DIRECTORY PartialDirectory = NULL;
  1448. PFSN_DIRECTORY DestinationDirectory = NULL;
  1449. BOOLEAN DirDeleted = NULL;
  1450. PATH PathToDelete;
  1451. CHNUM CharsInPartialDirectoryPath;
  1452. BIG_INT FreeSpace;
  1453. BIG_INT FileSize;
  1454. if ( TargetFile = SYSTEM::QueryFile( DestinationPath ) ) {
  1455. TargetSize = TargetFile->QuerySize();
  1456. DELETE( TargetFile );
  1457. } else {
  1458. TargetSize = 0;
  1459. }
  1460. TargetDrive = DestinationPath->QueryDevice();
  1461. FileSize = File->QuerySize();
  1462. if ( TargetDrive ) {
  1463. TargetRoot.Initialize( TargetDrive );
  1464. if ( TargetRoot.QueryChAt( TargetRoot.QueryChCount()-1) != (WCHAR)'\\' ) {
  1465. Slash.Initialize( "\\" );
  1466. TargetRoot.Strcat( &Slash );
  1467. }
  1468. while ( TRUE ) {
  1469. if ( IFS_SYSTEM::QueryFreeDiskSpace( &TargetRoot, &FreeSpace ) ) {
  1470. FreeSpace = FreeSpace + TargetSize;
  1471. // DebugPrintTrace(( "Disk Space: %d Needed: %d\n", FreeSpace.GetLowPart(), FileSize.GetLowPart() ));
  1472. if ( FreeSpace < FileSize ) {
  1473. //
  1474. // Not enough free space, ask for another
  1475. // disk and create the directory structure.
  1476. //
  1477. DisplayMessage( XCOPY_MESSAGE_CHANGE_DISK, NORMAL_MESSAGE );
  1478. AbortIfCtrlC();
  1479. _Keyboard->DisableLineMode();
  1480. if ( GetStandardInput()->IsAtEnd() ) {
  1481. // Insufficient input--treat as CONTROL-C.
  1482. //
  1483. Resp = CTRL_C;
  1484. } else {
  1485. GetStandardInput()->ReadChar( &Resp );
  1486. }
  1487. _Keyboard->EnableLineMode();
  1488. if ( Resp == CTRL_C ) {
  1489. exit( EXIT_TERMINATED );
  1490. } else {
  1491. GetStandardOutput()->WriteChar( Resp );
  1492. GetStandardOutput()->WriteChar( '\r' );
  1493. GetStandardOutput()->WriteChar( '\n' );
  1494. }
  1495. //
  1496. // Create directory structure in target
  1497. //
  1498. TmpPath.Initialize( DestinationPath );
  1499. TmpPath.TruncateBase();
  1500. PartialDirectory = SYSTEM::QueryDirectory( &TmpPath, TRUE );
  1501. if (PartialDirectory == NULL ) {
  1502. if (GetLastError() == COPY_ERROR_REQUEST_ABORTED) {
  1503. DELETE( TargetDrive );
  1504. return FALSE;
  1505. }
  1506. continue;
  1507. } else {
  1508. if ( *(PartialDirectory->GetPath()->GetPathString()) !=
  1509. *(TmpPath.GetPathString()) ) {
  1510. TmpPath1.Initialize( &TmpPath );
  1511. DestinationDirectory = PartialDirectory->CreateDirectoryPath( &TmpPath1 );
  1512. if ( !DestinationDirectory ) {
  1513. DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY, NULL, EXIT_MISC_ERROR );
  1514. }
  1515. } else {
  1516. DestinationDirectory = PartialDirectory;
  1517. }
  1518. }
  1519. //
  1520. // If still not enough disk space, remove the directories
  1521. // that we created and try again
  1522. //
  1523. IFS_SYSTEM::QueryFreeDiskSpace( TargetDrive, &FreeSpace );
  1524. FreeSpace = FreeSpace + TargetSize;
  1525. if ( FreeSpace < FileSize ) {
  1526. if ( PartialDirectory != DestinationDirectory ) {
  1527. if (!PathToDelete.Initialize( DestinationDirectory->GetPath() )) {
  1528. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1529. }
  1530. CharsInPartialDirectoryPath = PartialDirectory->GetPath()->GetPathString()->QueryChCount();
  1531. while ( PathToDelete.GetPathString()->QueryChCount() >
  1532. CharsInPartialDirectoryPath ) {
  1533. DirDeleted = DestinationDirectory->DeleteDirectory();
  1534. DebugAssert( DirDeleted );
  1535. DELETE( DestinationDirectory );
  1536. DestinationDirectory = NULL;
  1537. PathToDelete.TruncateBase();
  1538. DestinationDirectory = SYSTEM::QueryDirectory( &PathToDelete );
  1539. DebugPtrAssert( DestinationDirectory );
  1540. }
  1541. }
  1542. }
  1543. if ( PartialDirectory != DestinationDirectory ) {
  1544. DELETE( PartialDirectory );
  1545. DELETE( DestinationDirectory );
  1546. } else {
  1547. DELETE( PartialDirectory );
  1548. }
  1549. } else {
  1550. break;
  1551. }
  1552. } else {
  1553. //
  1554. // Cannot determine free disk space!
  1555. //
  1556. if (GetLastError() == COPY_ERROR_REQUEST_ABORTED) {
  1557. DELETE( TargetDrive );
  1558. return FALSE;
  1559. }
  1560. break;
  1561. }
  1562. }
  1563. DELETE( TargetDrive );
  1564. }
  1565. return TRUE;
  1566. }
  1567. VOID
  1568. XCOPY::GetDirectoryAndFilters (
  1569. IN PPATH Path,
  1570. OUT PFSN_DIRECTORY *OutDirectory,
  1571. OUT PFSN_FILTER *FileFilter,
  1572. OUT PFSN_FILTER *DirectoryFilter,
  1573. OUT PBOOLEAN CopyingManyFiles
  1574. )
  1575. /*++
  1576. Routine Description:
  1577. Obtains a directory object and the filename to match
  1578. Arguments:
  1579. Path - Supplies pointer to the path
  1580. OutDirectory - Supplies pointer to pointer to directory
  1581. FileFilter - Supplies filter for files
  1582. DirectoryFilter - Supplies filter for directories
  1583. CopyingManyFiles - Supplies pointer to flag which if TRUE means that
  1584. we are copying many files
  1585. Return Value:
  1586. None.
  1587. Notes:
  1588. --*/
  1589. {
  1590. PFSN_DIRECTORY Directory;
  1591. PFSN_FILE File;
  1592. PWSTRING Prefix = NULL;
  1593. PWSTRING FileName = NULL;
  1594. PATH PrefixPath;
  1595. PATH TmpPath;
  1596. FSN_ATTRIBUTE All = (FSN_ATTRIBUTE)0;
  1597. FSN_ATTRIBUTE Any = (FSN_ATTRIBUTE)0;
  1598. FSN_ATTRIBUTE None = (FSN_ATTRIBUTE)0;
  1599. PFSN_FILTER FilFilter;
  1600. PFSN_FILTER DirFilter;
  1601. DSTRING Name;
  1602. //
  1603. // Create filters
  1604. //
  1605. if ( ( (FilFilter = NEW FSN_FILTER) == NULL ) ||
  1606. ( (DirFilter = NEW FSN_FILTER) == NULL ) ||
  1607. !FilFilter->Initialize() ||
  1608. !DirFilter->Initialize() ) {
  1609. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1610. }
  1611. if (( Directory = SYSTEM::QueryDirectory( Path )) != NULL ) {
  1612. //
  1613. // Copying a directory. We will want everything in the directory
  1614. //
  1615. FilFilter->SetFileName( "*.*" );
  1616. *CopyingManyFiles = TRUE;
  1617. } else {
  1618. //
  1619. // The path is not a directory. Get the prefix part (which SHOULD
  1620. // be a directory, and try to make a directory from it
  1621. //
  1622. *CopyingManyFiles = Path->HasWildCard();
  1623. if ( !*CopyingManyFiles ) {
  1624. //
  1625. // If the path is not a file, then this is an error
  1626. //
  1627. if ( !(File = SYSTEM::QueryFile( Path )) ) {
  1628. if ((FileName = Path->QueryName()) == NULL ||
  1629. !Name.Initialize( FileName )) {
  1630. DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR );
  1631. }
  1632. DisplayMessageAndExit( XCOPY_ERROR_FILE_NOT_FOUND,
  1633. &Name,
  1634. EXIT_MISC_ERROR );
  1635. }
  1636. DELETE( File );
  1637. }
  1638. Prefix = Path->QueryPrefix();
  1639. if ( !Prefix ) {
  1640. //
  1641. // No prefix, use the drive part.
  1642. //
  1643. TmpPath.Initialize( Path, TRUE );
  1644. Prefix = TmpPath.QueryDevice();
  1645. if (Prefix == NULL) {
  1646. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1647. return;
  1648. }
  1649. }
  1650. if ( !PrefixPath.Initialize( Prefix, FALSE ) ) {
  1651. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1652. }
  1653. if (( Directory = SYSTEM::QueryDirectory( &PrefixPath )) != NULL ) {
  1654. //
  1655. // Directory is ok, set the filter's filename criteria
  1656. // with the file (pattern) specified.
  1657. //
  1658. if ((FileName = Path->QueryName()) == NULL ) {
  1659. DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR );
  1660. }
  1661. FilFilter->SetFileName( FileName );
  1662. } else {
  1663. //
  1664. // Something went wrong...
  1665. //
  1666. if ((FileName = Path->QueryName()) == NULL ||
  1667. !Name.Initialize( FileName )) {
  1668. DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR );
  1669. }
  1670. DisplayMessageAndExit( XCOPY_ERROR_FILE_NOT_FOUND,
  1671. &Name,
  1672. EXIT_MISC_ERROR );
  1673. }
  1674. DELETE( Prefix );
  1675. DELETE( FileName );
  1676. }
  1677. //
  1678. // Ok, we have the directory object and the filefilter's path set.
  1679. //
  1680. //
  1681. // Set the file filter attribute criteria
  1682. //
  1683. None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_DIRECTORY );
  1684. if ( !_HiddenSwitch ) {
  1685. None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_HIDDEN | FSN_ATTRIBUTE_SYSTEM );
  1686. }
  1687. if (_ArchiveSwitch) {
  1688. All = (FSN_ATTRIBUTE)(All | FSN_ATTRIBUTE_ARCHIVE);
  1689. }
  1690. FilFilter->SetAttributes( All, Any, None );
  1691. //
  1692. // Set the file filter's time criteria
  1693. //
  1694. if ( _Date != NULL ) {
  1695. FilFilter->SetTimeInfo( _Date,
  1696. FSN_TIME_MODIFIED,
  1697. (TIME_AT | TIME_AFTER) );
  1698. }
  1699. //
  1700. // Set the directory filter attribute criteria.
  1701. //
  1702. All = (FSN_ATTRIBUTE)0;
  1703. Any = (FSN_ATTRIBUTE)0;
  1704. None = (FSN_ATTRIBUTE)0;
  1705. if ( !_HiddenSwitch ) {
  1706. None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_HIDDEN | FSN_ATTRIBUTE_SYSTEM );
  1707. }
  1708. if (_SubdirSwitch) {
  1709. All = (FSN_ATTRIBUTE)(All | FSN_ATTRIBUTE_DIRECTORY);
  1710. } else {
  1711. None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_DIRECTORY);
  1712. }
  1713. DirFilter->SetAttributes( All, Any, None );
  1714. *FileFilter = FilFilter;
  1715. *DirectoryFilter = DirFilter;
  1716. *OutDirectory = Directory;
  1717. }
  1718. VOID
  1719. XCOPY::GetDirectoryAndFilePattern(
  1720. IN PPATH Path,
  1721. IN BOOLEAN CopyingManyFiles,
  1722. OUT PPATH *OutDirectory,
  1723. OUT PWSTRING *OutFilePattern
  1724. )
  1725. /*++
  1726. Routine Description:
  1727. Gets the path of the destination directory and the pattern that
  1728. will be used for filename conversion.
  1729. Arguments:
  1730. Path - Supplies pointer to the path
  1731. CopyingManyFiles - Supplies flag which if true means that we are copying many
  1732. files.
  1733. OutDirectory - Supplies pointer to pointer to directory path
  1734. OutFilePattern - Supplies pointer to pointer to file name
  1735. IsDir ` - Supplies pointer to isdir flag
  1736. Return Value:
  1737. None.
  1738. Notes:
  1739. --*/
  1740. {
  1741. PPATH Directory;
  1742. PWSTRING FileName;
  1743. PWSTRING Prefix = NULL;
  1744. PWSTRING Name = NULL;
  1745. BOOLEAN DeletePath = FALSE;
  1746. PFSN_DIRECTORY TmpDir;
  1747. PATH TmpPath;
  1748. DSTRING Slash;
  1749. DSTRING TmpPath1Str;
  1750. PATH TmpPath1;
  1751. if ( !Path ) {
  1752. //
  1753. // There is no path, we invent our own
  1754. //
  1755. if ( ((Path = NEW PATH) == NULL ) ||
  1756. !Path->Initialize( (LPWSTR)L"*.*", FALSE)) {
  1757. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1758. }
  1759. DeletePath = TRUE;
  1760. }
  1761. TmpDir = SYSTEM::QueryDirectory( Path );
  1762. if ( !TmpDir && (Path->HasWildCard() || IsFileName( Path, CopyingManyFiles ))) {
  1763. //
  1764. // The path is not a directory, so we use the prefix as a
  1765. // directory path and the filename becomes the pattern.
  1766. //
  1767. if ( !TmpPath.Initialize( Path, TRUE ) ||
  1768. ((Prefix = TmpPath.QueryPrefix()) == NULL) ||
  1769. !Slash.Initialize( "\\" ) ||
  1770. !TmpPath1Str.Initialize( Prefix ) ||
  1771. !TmpPath1.Initialize( &TmpPath1Str, FALSE ) ||
  1772. ((Name = TmpPath.QueryName()) == NULL) ||
  1773. ((Directory = NEW PATH) == NULL) ||
  1774. !Directory->Initialize( &TmpPath1, TRUE ) ||
  1775. ((FileName = NEW DSTRING) == NULL ) ||
  1776. !FileName->Initialize( Name ) ) {
  1777. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1778. }
  1779. DELETE( Prefix );
  1780. DELETE( Name );
  1781. } else {
  1782. //
  1783. // The path specifies a directory, so we use all of it and the
  1784. // pattern is "*.*"
  1785. //
  1786. if ( ((Directory = NEW PATH) == NULL ) ||
  1787. !Directory->Initialize( Path,TRUE ) ||
  1788. ((FileName = NEW DSTRING) == NULL ) ||
  1789. !FileName->Initialize( "*.*" ) ) {
  1790. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1791. }
  1792. DELETE( TmpDir );
  1793. }
  1794. *OutDirectory = Directory;
  1795. *OutFilePattern = FileName;
  1796. //
  1797. // If we created the path, we have to delete it
  1798. //
  1799. if ( DeletePath ) {
  1800. DELETE( Path );
  1801. }
  1802. }
  1803. BOOL
  1804. XCOPY::IsCyclicalCopy(
  1805. IN PPATH PathSrc,
  1806. IN PPATH PathTrg
  1807. )
  1808. /*++
  1809. Routine Description:
  1810. Determines if there is a cycle between two paths
  1811. Arguments:
  1812. PathSrc - Supplies pointer to first path
  1813. PathTrg - Supplies pointer to second path
  1814. Return Value:
  1815. TRUE if there is a cycle,
  1816. FALSE otherwise
  1817. --*/
  1818. {
  1819. PATH SrcPath;
  1820. PATH TrgPath;
  1821. PARRAY ArraySrc, ArrayTrg;
  1822. PARRAY_ITERATOR IteratorSrc, IteratorTrg;
  1823. PWSTRING ComponentSrc, ComponentTrg;
  1824. BOOLEAN IsCyclical = FALSE;
  1825. DebugAssert( PathSrc != NULL );
  1826. if ( PathTrg != NULL ) {
  1827. //
  1828. // Get canonicalized paths for both source and target
  1829. //
  1830. SrcPath.Initialize(PathSrc, TRUE );
  1831. TrgPath.Initialize(PathTrg, TRUE );
  1832. //
  1833. // Split the paths into their components
  1834. //
  1835. ArraySrc = SrcPath.QueryComponentArray();
  1836. ArrayTrg = TrgPath.QueryComponentArray();
  1837. DebugPtrAssert( ArraySrc );
  1838. DebugPtrAssert( ArrayTrg );
  1839. if ( !ArraySrc || !ArrayTrg ) {
  1840. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1841. }
  1842. //
  1843. // Get iterators for the components
  1844. //
  1845. IteratorSrc = ( PARRAY_ITERATOR )ArraySrc->QueryIterator();
  1846. IteratorTrg = ( PARRAY_ITERATOR )ArrayTrg->QueryIterator();
  1847. DebugPtrAssert( IteratorSrc );
  1848. DebugPtrAssert( IteratorTrg );
  1849. if ( !IteratorSrc || !IteratorTrg ) {
  1850. DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
  1851. }
  1852. //
  1853. // There is a cycle if all of the source is along the target.
  1854. //
  1855. while ( TRUE ) {
  1856. ComponentSrc = (PWSTRING)IteratorSrc->GetNext();
  1857. if ( !ComponentSrc ) {
  1858. //
  1859. // The source path is along the target path. This is a
  1860. // cycle.
  1861. //
  1862. IsCyclical = TRUE;
  1863. break;
  1864. }
  1865. ComponentTrg = (PWSTRING)IteratorTrg->GetNext();
  1866. if ( !ComponentTrg ) {
  1867. //
  1868. // The target path is along the source path. This is no
  1869. // cycle.
  1870. //
  1871. break;
  1872. }
  1873. if ( *ComponentSrc != *ComponentTrg ) {
  1874. //
  1875. // One path is not along the other. There is no cycle.
  1876. //
  1877. break;
  1878. }
  1879. }
  1880. DELETE( IteratorSrc );
  1881. DELETE( IteratorTrg );
  1882. ArraySrc->DeleteAllMembers();
  1883. ArrayTrg->DeleteAllMembers();
  1884. DELETE( ArraySrc );
  1885. DELETE( ArrayTrg );
  1886. }
  1887. return IsCyclical;
  1888. }
  1889. BOOL
  1890. XCOPY::IsFileName(
  1891. IN PPATH Path,
  1892. IN BOOLEAN CopyingManyFiles
  1893. )
  1894. /*++
  1895. Routine Description:
  1896. Figures out if a name refers to a directory or a file.
  1897. Arguments:
  1898. Path - Supplies pointer to the path
  1899. CopyingManyFiles - Supplies flag which if TRUE means that we are
  1900. copying many files.
  1901. Return Value:
  1902. BOOLEAN - TRUE if name refers to file,
  1903. FALSE otherwise
  1904. Notes:
  1905. --*/
  1906. {
  1907. PFSN_DIRECTORY FsnDirectory;
  1908. PFSN_FILE FsnFile;
  1909. WCHAR Resp;
  1910. PWSTRING DirMsg;
  1911. PWSTRING FilMsg;
  1912. //
  1913. // If the path is an existing directory, then this is obviously
  1914. // not a file.
  1915. //
  1916. //
  1917. if ((FsnDirectory = SYSTEM::QueryDirectory( Path )) != NULL ) {
  1918. DELETE( FsnDirectory );
  1919. return FALSE;
  1920. }
  1921. //
  1922. // If the path ends with a delimiter, then it is a directory.
  1923. // We remove the delimiter.
  1924. //
  1925. if ( Path->EndsWithDelimiter() ) {
  1926. ((PWSTRING) Path->GetPathString())->Truncate( Path->GetPathString()->QueryChCount() - 1 );
  1927. Path->Initialize( Path->GetPathString() );
  1928. return FALSE;
  1929. }
  1930. //
  1931. // If the path is an existing file, then it is a file.
  1932. //
  1933. if ((FsnFile = SYSTEM::QueryFile( Path )) != NULL ) {
  1934. DELETE( FsnFile );
  1935. return _TargetIsFile = TRUE;
  1936. }
  1937. DirMsg = QueryMessageString(XCOPY_RESPONSE_DIRECTORY);
  1938. FilMsg = QueryMessageString(XCOPY_RESPONSE_FILE);
  1939. DebugPtrAssert( DirMsg );
  1940. DebugPtrAssert( FilMsg );
  1941. //
  1942. // If the path does not exist, we are copying many files, and we are intelligent,
  1943. // then the target is obviously a directory.
  1944. //
  1945. // Otherwise we simply ask the user.
  1946. //
  1947. if ( _IntelligentSwitch && CopyingManyFiles ) {
  1948. _TargetIsFile = FALSE;
  1949. } else {
  1950. while ( TRUE ) {
  1951. DisplayMessage( XCOPY_MESSAGE_FILE_OR_DIRECTORY, NORMAL_MESSAGE, "%W", Path->GetPathString() );
  1952. AbortIfCtrlC();
  1953. _Keyboard->DisableLineMode();
  1954. if( GetStandardInput()->IsAtEnd() ) {
  1955. // Insufficient input--treat as CONTROL-C.
  1956. //
  1957. Resp = CTRL_C;
  1958. } else {
  1959. GetStandardInput()->ReadChar( &Resp );
  1960. }
  1961. _Keyboard->EnableLineMode();
  1962. if ( Resp == CTRL_C ) {
  1963. exit( EXIT_TERMINATED );
  1964. } else {
  1965. GetStandardOutput()->WriteChar( Resp );
  1966. GetStandardOutput()->WriteChar( '\r' );
  1967. GetStandardOutput()->WriteChar( '\n' );
  1968. }
  1969. Resp = (WCHAR)towupper( (wchar_t)Resp );
  1970. if ( FilMsg->QueryChAt(0) == Resp ) {
  1971. _TargetIsFile = TRUE;
  1972. break;
  1973. } else if ( DirMsg->QueryChAt(0) == Resp ) {
  1974. _TargetIsFile = FALSE;
  1975. break;
  1976. }
  1977. }
  1978. }
  1979. DELETE( DirMsg );
  1980. DELETE( FilMsg );
  1981. return _TargetIsFile;
  1982. }
  1983. BOOLEAN
  1984. XCOPY::UserConfirmedCopy (
  1985. IN PCWSTRING SourcePath,
  1986. IN PCWSTRING DestinationPath
  1987. )
  1988. /*++
  1989. Routine Description:
  1990. Gets confirmation from the user about a file to be copied
  1991. Arguments:
  1992. SourcePath - Supplies the path to the file to be copied
  1993. DestinationPath - Supplies the destination path of the file to be created
  1994. Return Value:
  1995. BOOLEAN - TRUE if the user confirmed the copy
  1996. FALSE otherwise
  1997. --*/
  1998. {
  1999. PWSTRING YesMsg;
  2000. PWSTRING NoMsg;
  2001. WCHAR Resp;
  2002. BOOLEAN Confirmed;
  2003. YesMsg = QueryMessageString(XCOPY_RESPONSE_YES);
  2004. NoMsg = QueryMessageString(XCOPY_RESPONSE_NO);
  2005. DebugPtrAssert( YesMsg );
  2006. DebugPtrAssert( NoMsg );
  2007. while ( TRUE ) {
  2008. if (DestinationPath) {
  2009. DisplayMessage( XCOPY_MESSAGE_CONFIRM3, NORMAL_MESSAGE, "%W%W",
  2010. SourcePath, DestinationPath );
  2011. } else {
  2012. DisplayMessage( XCOPY_MESSAGE_CONFIRM, NORMAL_MESSAGE, "%W", SourcePath );
  2013. }
  2014. AbortIfCtrlC();
  2015. _Keyboard->DisableLineMode();
  2016. if( GetStandardInput()->IsAtEnd() ) {
  2017. // Insufficient input--treat as CONTROL-C.
  2018. //
  2019. Resp = NoMsg->QueryChAt( 0 );
  2020. break;
  2021. } else {
  2022. GetStandardInput()->ReadChar( &Resp );
  2023. }
  2024. _Keyboard->EnableLineMode();
  2025. if ( Resp == CTRL_C ) {
  2026. exit( EXIT_TERMINATED );
  2027. } else {
  2028. GetStandardOutput()->WriteChar( Resp );
  2029. GetStandardOutput()->WriteChar( '\r' );
  2030. GetStandardOutput()->WriteChar( '\n' );
  2031. }
  2032. Resp = (WCHAR)towupper( (wchar_t)Resp );
  2033. if ( YesMsg->QueryChAt( 0 ) == Resp ) {
  2034. Confirmed = TRUE;
  2035. break;
  2036. }
  2037. else if ( NoMsg->QueryChAt( 0 ) == Resp ) {
  2038. Confirmed = FALSE;
  2039. break;
  2040. }
  2041. }
  2042. DELETE( YesMsg );
  2043. DELETE( NoMsg );
  2044. return Confirmed;
  2045. }
  2046. BOOLEAN
  2047. XCOPY::UserConfirmedOverWrite (
  2048. IN PPATH DestinationFile
  2049. )
  2050. /*++
  2051. Routine Description:
  2052. Gets confirmation from the user about the overwriting of an existing file
  2053. Arguments:
  2054. FsNode - Supplies pointer to FSNODE of file to be
  2055. copied
  2056. Return Value:
  2057. BOOLEAN - TRUE if the user confirmed the overwrite
  2058. FALSE otherwise
  2059. --*/
  2060. {
  2061. PWSTRING YesMsg;
  2062. PWSTRING NoMsg;
  2063. PWSTRING AllMsg;
  2064. WCHAR Resp;
  2065. BOOLEAN Confirmed;
  2066. YesMsg = QueryMessageString(XCOPY_RESPONSE_YES);
  2067. NoMsg = QueryMessageString(XCOPY_RESPONSE_NO);
  2068. AllMsg = QueryMessageString(XCOPY_RESPONSE_ALL);
  2069. DebugPtrAssert( YesMsg );
  2070. DebugPtrAssert( NoMsg );
  2071. DebugPtrAssert( AllMsg );
  2072. while ( TRUE ) {
  2073. DisplayMessage( XCOPY_MESSAGE_CONFIRM2, NORMAL_MESSAGE, "%W", DestinationFile->GetPathString() );
  2074. AbortIfCtrlC();
  2075. _Keyboard->DisableLineMode();
  2076. if( GetStandardInput()->IsAtEnd() ) {
  2077. // Insufficient input--treat as CONTROL-C.
  2078. //
  2079. exit( EXIT_TERMINATED );
  2080. break;
  2081. } else {
  2082. GetStandardInput()->ReadChar( &Resp );
  2083. }
  2084. _Keyboard->EnableLineMode();
  2085. if ( Resp == CTRL_C ) {
  2086. exit( EXIT_TERMINATED );
  2087. } else {
  2088. GetStandardOutput()->WriteChar( Resp );
  2089. GetStandardOutput()->WriteChar( '\r' );
  2090. GetStandardOutput()->WriteChar( '\n' );
  2091. }
  2092. Resp = (WCHAR)towupper( (wchar_t)Resp );
  2093. if ( YesMsg->QueryChAt( 0 ) == Resp ) {
  2094. Confirmed = TRUE;
  2095. break;
  2096. }
  2097. else if ( NoMsg->QueryChAt( 0 ) == Resp ) {
  2098. Confirmed = FALSE;
  2099. break;
  2100. } else if ( AllMsg->QueryChAt(0) == Resp ) {
  2101. Confirmed = _OverWriteSwitch = TRUE;
  2102. break;
  2103. }
  2104. }
  2105. DELETE( YesMsg );
  2106. DELETE( NoMsg );
  2107. DELETE( AllMsg );
  2108. return Confirmed;
  2109. }
  2110. BOOLEAN
  2111. XCOPY::IsExcluded(
  2112. IN PCPATH Path
  2113. )
  2114. /*++
  2115. Routine Description:
  2116. This method determines whether the specified path should be
  2117. excluded from the XCOPY.
  2118. Arguments:
  2119. Path -- Supplies the path of the file in question.
  2120. Return Value:
  2121. TRUE if this file should be excluded, i.e. if any element of
  2122. the exclusion list array appears as a substring of this path.
  2123. --*/
  2124. {
  2125. PWSTRING CurrentString;
  2126. DSTRING UpcasedPath;
  2127. DSTRING BackSlash;
  2128. if( _ExclusionList == NULL ) {
  2129. return FALSE;
  2130. }
  2131. if (!UpcasedPath.Initialize(Path->GetPathString()) ||
  2132. !BackSlash.Initialize(L"\\")) {
  2133. DebugPrint("XCOPY: Out of memory \n");
  2134. return FALSE;
  2135. }
  2136. UpcasedPath.Strupr( );
  2137. if (!Path-> EndsWithDelimiter() &&
  2138. !UpcasedPath.Strcat(&BackSlash)) {
  2139. DebugPrint("XCOPY: Out of memory \n");
  2140. return FALSE;
  2141. }
  2142. _Iterator->Reset();
  2143. while ((CurrentString = (PWSTRING)_Iterator->GetNext()) != NULL) {
  2144. if (UpcasedPath.Strstr(CurrentString) != INVALID_CHNUM) {
  2145. return TRUE;
  2146. }
  2147. }
  2148. return FALSE;
  2149. }