/*++ Copyright (c) 1990-2001 Microsoft Corporation Module Name: XCopy.cxx Abstract: Xcopy is a DOS5-Compatible directory copy utility Author: Ramon Juan San Andres (ramonsa) 01-May-1991 Revision History: --*/ #define _NTAPI_ULIB_ #include "ulib.hxx" #include "array.hxx" #include "arrayit.hxx" #include "dir.hxx" #include "file.hxx" #include "filter.hxx" #include "stream.hxx" #include "system.hxx" #include "xcopy.hxx" #include "bigint.hxx" #include "ifssys.hxx" #include "stringar.hxx" #include "arrayit.hxx" extern "C" { #include #include "winbasep.h" } #define CTRL_C (WCHAR)3 int __cdecl main ( ) /*++ Routine Description: Main function of the XCopy utility Arguments: None. Return Value: None. Notes: --*/ { // // Initialize stuff // DEFINE_CLASS_DESCRIPTOR( XCOPY ); // // Now do the copy // { __try { XCOPY XCopy; // // Initialize the XCOPY object. // if ( XCopy.Initialize() ) { __try { // // Do the copy // XCopy.DoCopy(); } __except ((_exception_code() == STATUS_STACK_OVERFLOW) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { // display may not work due to out of stack space XCopy.DisplayMessageAndExit(XCOPY_ERROR_STACK_SPACE, NULL, EXIT_MISC_ERROR); } } } __except ((_exception_code() == STATUS_STACK_OVERFLOW) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { // may not be able to display anything if initialization failed // in additional to out of stack space // so just send a message to the debug port DebugPrintTrace(("XCOPY: Out of stack space\n")); return EXIT_MISC_ERROR; } } return EXIT_NORMAL; } DEFINE_CONSTRUCTOR( XCOPY, PROGRAM ); VOID XCOPY::Construct ( ) { _Keyboard = NULL; _TargetPath = NULL; _SourcePath = NULL; _DestinationPath = NULL; _Date = NULL; _FileNamePattern = NULL; _ExclusionList = NULL; _Iterator = NULL; } BOOLEAN XCOPY::Initialize ( ) /*++ Routine Description: Initializes the XCOPY object Arguments: None. Return Value: None. Notes: --*/ { // // Initialize program object // if( !PROGRAM::Initialize( XCOPY_MESSAGE_USAGE ) ) { return FALSE; } // // Allocate resources // InitializeThings(); // // Parse the arguments // SetArguments(); return TRUE; } XCOPY::~XCOPY ( ) /*++ Routine Description: Destructs an XCopy object Arguments: None. Return Value: None. Notes: --*/ { // // Deallocate the global structures previously allocated // DeallocateThings(); // // Exit without error // if( _Standard_Input != NULL && _Standard_Output != NULL ) { DisplayMessageAndExit( 0, NULL, EXIT_NORMAL ); } } VOID XCOPY::InitializeThings ( ) /*++ Routine Description: Initializes the global variables that need initialization Arguments: None. Return Value: None. Notes: --*/ { // // Get a keyboard, because we will need to switch back and // forth between raw and cooked mode and because we need // to enable ctrl-c handling (so that we can exit with // the right level if the program is interrupted). // if ( !( _Keyboard = KEYBOARD::Cast(GetStandardInput()) )) { // // Not reading from standard input, we will get // the real keyboard. // _Keyboard = NEW KEYBOARD; if( !_Keyboard ) { exit(4); } _Keyboard->Initialize(); } // // Set Ctrl-C handler // _Keyboard->EnableBreakHandling(); // // Initialize our internal data // _FilesCopied = 0; _CanRemoveEmptyDirectories = TRUE; _TargetIsFile = FALSE; _TargetPath = NULL; _SourcePath = NULL; _DestinationPath = NULL; _Date = NULL; _FileNamePattern = NULL; _ExclusionList = NULL; _Iterator = NULL; // The following switches are being used by DisplayMessageAndExit // before any of those boolean _*Switch is being initialized _DontCopySwitch = FALSE; _StructureOnlySwitch = TRUE; } VOID XCOPY::DeallocateThings ( ) /*++ Routine Description: Deallocates the stuff that was initialized in InitializeThings() Arguments: None. Return Value: None. Notes: --*/ { // // Deallocate local data // DELETE( _TargetPath ); DELETE( _SourcePath ); DELETE( _DestinationPath ); DELETE( _Date ); DELETE( _FileNamePattern ); DELETE( _Iterator ); if( _ExclusionList ) { _ExclusionList->DeleteAllMembers(); } DELETE( _ExclusionList ); // // Reset Ctrl-C handleing // _Keyboard->DisableBreakHandling(); // // If standard input is not the keyboard, we get rid of // the keyboard object. // if ( !(_Keyboard == KEYBOARD::Cast(GetStandardInput()) )) { DELETE( _Keyboard ); } } STATIC BOOLEAN GetTokenHandle( IN OUT PHANDLE TokenHandle ) /*++ Routine Description: This routine opens the current process object and returns a handle to its token. Arguments: Return Value: FALSE - Failure. TRUE - Success. --*/ { HANDLE ProcessHandle; BOOL Result; ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); if (ProcessHandle == NULL) return(FALSE); Result = OpenProcessToken(ProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, TokenHandle); CloseHandle(ProcessHandle); return Result != FALSE; } STATIC BOOLEAN SetPrivs( IN HANDLE TokenHandle, IN LPTSTR lpszPriv ) /*++ Routine Description: This routine enables the given privilege in the given token. Arguments: Return Value: FALSE - Failure. TRUE - Success. --*/ { LUID SetPrivilegeValue; TOKEN_PRIVILEGES TokenPrivileges; // // First, find out the value of the privilege // if (!LookupPrivilegeValue(NULL, lpszPriv, &SetPrivilegeValue)) { return FALSE; } // // Set up the privilege set we will need // TokenPrivileges.PrivilegeCount = 1; TokenPrivileges.Privileges[0].Luid = SetPrivilegeValue; TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { return FALSE; } return TRUE; } BOOLEAN XCOPY::DoCopy ( ) /*++ Routine Description: This is the function that performs the XCopy. Arguments: None. Return Value: None. Notes: --*/ { PFSN_DIRECTORY SourceDirectory = NULL; PFSN_DIRECTORY DestinationDirectory = NULL; PFSN_DIRECTORY PartialDirectory = NULL; PATH PathToDelete; WCHAR Char; CHNUM CharsInPartialDirectoryPath; BOOLEAN DirDeleted; BOOLEAN CopyingManyFiles; PATH TmpPath; PFSN_FILTER FileFilter = NULL; PFSN_FILTER DirectoryFilter = NULL; WIN32_FIND_DATA FindData; PWSTRING Device = NULL; HANDLE FindHandle; // // Make sure that we won't try to copy to ourselves // if ( _SubdirSwitch && IsCyclicalCopy( _SourcePath, _DestinationPath ) ) { DisplayMessageAndExit( XCOPY_ERROR_CYCLE, NULL, EXIT_MISC_ERROR ); } AbortIfCtrlC(); // // Get the source directory object and the filename that we will be // matching. // GetDirectoryAndFilters( _SourcePath, &SourceDirectory, &FileFilter, &DirectoryFilter, &CopyingManyFiles ); // // Make sure that we won't try to copy to ourselves // if ( _SubdirSwitch && IsCyclicalCopy( (PPATH)SourceDirectory->GetPath(), _DestinationPath ) ) { DisplayMessageAndExit( XCOPY_ERROR_CYCLE, NULL, EXIT_MISC_ERROR ); } DebugPtrAssert( SourceDirectory ); DebugPtrAssert( FileFilter ); DebugPtrAssert( DirectoryFilter ); if ( _WaitSwitch ) { // Pause before we start copying. // DisplayMessage( XCOPY_MESSAGE_WAIT ); AbortIfCtrlC(); // // All input is in raw mode. // _Keyboard->DisableLineMode(); if( GetStandardInput()->IsAtEnd() ) { // Insufficient input--treat as CONTROL-C. // Char = ' '; } else { GetStandardInput()->ReadChar( &Char ); } _Keyboard->EnableLineMode(); if ( Char == CTRL_C ) { exit ( EXIT_TERMINATED ); } else { GetStandardOutput()->WriteChar( Char ); GetStandardOutput()->WriteChar( (WCHAR)'\r'); GetStandardOutput()->WriteChar( (WCHAR)'\n'); } } // // Get the destination directory and the file pattern. // GetDirectoryAndFilePattern( _DestinationPath, CopyingManyFiles, &_TargetPath, &_FileNamePattern ); DebugPtrAssert( _TargetPath ); DebugPtrAssert( _FileNamePattern ); // // Get as much of the destination directory as possible. // if ( !_DontCopySwitch ) { PartialDirectory = SYSTEM::QueryDirectory( _TargetPath, TRUE ); if (PartialDirectory == NULL ) { DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY, NULL, EXIT_MISC_ERROR ); } // // All the directories up to the parent of the target have to exist. If // they don't, we have to create them. // if ( *(PartialDirectory->GetPath()->GetPathString()) == *(_TargetPath->GetPathString()) ) { DestinationDirectory = PartialDirectory; } else { TmpPath.Initialize( _TargetPath ); if( !_TargetIsFile ) { TmpPath.TruncateBase(); } DestinationDirectory = PartialDirectory->CreateDirectoryPath( &TmpPath ); } if( !DestinationDirectory ) { DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR ); } // // Determine if destination if floppy // Device = _TargetPath->QueryDevice(); if ( Device ) { _DisketteCopy = (SYSTEM::QueryDriveType( Device ) == RemovableDrive); DELETE( Device ); } } if (_OwnerSwitch) { HANDLE hToken; // Enable the privileges necessary to copy security information. if (!GetTokenHandle(&hToken)) { DisplayMessageAndExit(XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } SetPrivs(hToken, TEXT("SeBackupPrivilege")); SetPrivs(hToken, TEXT("SeRestorePrivilege")); SetPrivs(hToken, TEXT("SeSecurityPrivilege")); SetPrivs(hToken, TEXT("SeTakeOwnershipPrivilege")); } // // Now traverse the source directory. // TmpPath.Initialize( _TargetPath ); if (!_UpdateSwitch) { Traverse( SourceDirectory, &TmpPath, FileFilter, DirectoryFilter, !SourceDirectory->GetPath()->GetPathString()->Strcmp( _SourcePath->GetPathString())); } else { PATH DestDirectoryPath; PFSN_DIRECTORY DestDirectory; DestDirectoryPath.Initialize(&TmpPath); DestDirectory = SYSTEM::QueryDirectory(&DestDirectoryPath); TmpPath.Initialize(SourceDirectory->GetPath()); UpdateTraverse( DestDirectory, &TmpPath, FileFilter, DirectoryFilter, !SourceDirectory->GetPath()->GetPathString()->Strcmp( _SourcePath->GetPathString())); DELETE(DestDirectory); } DELETE( _TargetPath); if (( _FilesCopied == 0 ) && _CanRemoveEmptyDirectories && !_DontCopySwitch ) { // // Delete any directories that we created // if ( PartialDirectory != DestinationDirectory ) { if (!PathToDelete.Initialize( DestinationDirectory->GetPath() )) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } CharsInPartialDirectoryPath = PartialDirectory->GetPath()->GetPathString()->QueryChCount(); while ( PathToDelete.GetPathString()->QueryChCount() > CharsInPartialDirectoryPath ) { DirDeleted = DestinationDirectory->DeleteDirectory(); DebugAssert( DirDeleted ); DELETE( DestinationDirectory ); PathToDelete.TruncateBase(); DestinationDirectory = SYSTEM::QueryDirectory( &PathToDelete ); DebugPtrAssert( DestinationDirectory ); } } // // We display the "File not found" message only if there are no // files that match our pattern, regardless of other factors such // as attributes etc. This is just to maintain DOS5 compatibility. // TmpPath.Initialize( SourceDirectory->GetPath() ); TmpPath.AppendBase( FileFilter->GetFileName() ); if ((FindHandle = FindFirstFile( &TmpPath, &FindData )) == INVALID_HANDLE_VALUE ) { DisplayMessage( XCOPY_ERROR_FILE_NOT_FOUND, ERROR_MESSAGE, "%W", FileFilter->GetFileName() ); } FindClose(FindHandle); } DELETE( SourceDirectory ); if ( PartialDirectory != DestinationDirectory ) { DELETE( PartialDirectory ); } DELETE( DestinationDirectory ); DELETE( FileFilter ); DELETE( DirectoryFilter ); return TRUE; } BOOLEAN XCOPY::Traverse ( IN PFSN_DIRECTORY Directory, IN OUT PPATH DestinationPath, IN PFSN_FILTER FileFilter, IN PFSN_FILTER DirectoryFilter, IN BOOLEAN CopyDirectoryStreams ) /*++ Routine Description: Traverses a directory, calling the callback function for each node (directory of file) visited. The traversal may be finished prematurely when the callback function returnes FALSE. The destination path is modified to reflect the directory structure being traversed. Arguments: Directory - Supplies pointer to directory to traverse DestinationPath - Supplies pointer to path to be used with the callback function. FileFilter - Supplies a pointer to the file filter. DirectoryFilter - Supplies a pointer to the directory filter. CopyDirectoryStreams - Specifies to copy directory streams when copying directories. Return Value: BOOLEAN - TRUE if everything traversed FALSE otherwise. --*/ { PFSN_DIRECTORY TargetDirectory = NULL; PWSTRING CurrentPathStr; PWSTRING TargetPathStr; BOOLEAN MemoryOk; PFSN_FILE File; PFSN_DIRECTORY Dir; PWSTRING Name; BOOLEAN Created = FALSE; PCPATH TemplatePath = NULL; HANDLE h; PWSTRING CurrentFileName, PrevFileName; DWORD GetNextError = ERROR_SUCCESS; DebugPtrAssert( Directory ); DebugPtrAssert( DestinationPath ); DebugPtrAssert( FileFilter ); DebugPtrAssert( DirectoryFilter ); // // We only traverse this directory if it is not empty (unless the // empty switch is set). // if ( _EmptySwitch || !Directory->IsEmpty() ) { // // Create the target directory (if we are not copying to a file). // if ( !_TargetIsFile && !_DontCopySwitch ) { // // The target directory may not exist, create the // directory and remember that we might delete it if // no files or subdirectories were created. // Even if the directory exists, it may not have // all the streams/ACLs in it. if (CopyDirectoryStreams) { TemplatePath = Directory->GetPath(); } if (TemplatePath == NULL) { TargetDirectory = SYSTEM::QueryDirectory( DestinationPath ); } if (!TargetDirectory) { TargetDirectory = MakeDirectory( DestinationPath, TemplatePath ); if (TargetDirectory && !_CopyAttrSwitch) { DWORD dwError; // always set the archive bit so that it gets backup TargetDirectory->MakeArchived(&dwError); } Created = TRUE; } if ( !TargetDirectory ) { // // If the Continue Switch is set, we just display an error message and // continue, otherwise we exit with error. // if ( _ContinueSwitch ) { DisplayMessage( XCOPY_ERROR_CREATE_DIRECTORY1, ERROR_MESSAGE, "%W", DestinationPath->GetPathString() ); return TRUE; } else { DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY1, (PWSTRING)DestinationPath->GetPathString(), EXIT_MISC_ERROR ); } } if( !_CopyAttrSwitch ) { TargetDirectory->ResetReadOnlyAttribute(); } } // // Iterate through all files and copy them as needed // MemoryOk = TRUE; h = NULL; CurrentFileName = PrevFileName = NULL; while ( MemoryOk && (( File = (PFSN_FILE)Directory->GetNext( &h, &GetNextError )) != NULL )) { // // Don't know how expensive it is to check for infinite loop below // if (PrevFileName) { CurrentFileName = File->QueryName(); if (PrevFileName && CurrentFileName) { if (CurrentFileName->Strcmp(PrevFileName) == 0) { // something went wrong // GetNext should not return two files of the same name DELETE(File); DELETE(PrevFileName); DELETE(CurrentFileName); if (_ContinueSwitch) { DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE ); break; } else { DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR ); } } } else MemoryOk = FALSE; DELETE(PrevFileName); PrevFileName = CurrentFileName; CurrentFileName = NULL; if (!MemoryOk) break; } else PrevFileName = File->QueryName(); if ( !FileFilter->DoesNodeMatch( (PFSNODE)File ) ) { DELETE(File); continue; } DebugAssert( !File->IsDirectory() ); // If we're supposed to use the short name then convert fsnode. if (_UseShortSwitch && !File->UseAlternateName()) { DELETE(File); MemoryOk = FALSE; continue; } // // Append the name portion of the node to the destination path. // Name = File->QueryName(); DebugPtrAssert( Name ); if ( Name ) { MemoryOk = DestinationPath->AppendBase( Name ); DebugAssert( MemoryOk ); DELETE( Name ); if ( MemoryOk ) { // // Copy the file // if ( !Copier( File, DestinationPath ) ) { DELETE(File); ExitProgram( EXIT_MISC_ERROR ); } // // Restore the destination path // DestinationPath->TruncateBase(); } } else { MemoryOk = FALSE; } DELETE(File); } DELETE(PrevFileName); DELETE(CurrentFileName); if ( MemoryOk && (_ContinueSwitch || (ERROR_SUCCESS == GetNextError) || (ERROR_NO_MORE_FILES == GetNextError))) { // // If recursing, Traverse all the subdirectories // if ( _SubdirSwitch ) { MemoryOk = TRUE; h = NULL; if (Created) { TargetPathStr = TargetDirectory->GetPath()->QueryFullPathString(); MemoryOk = (TargetPathStr != NULL); } else TargetPathStr = NULL; CurrentFileName = PrevFileName = NULL; // // Recurse thru all the subdirectories // while ( MemoryOk && (( Dir = (PFSN_DIRECTORY)Directory->GetNext( &h, &GetNextError )) != NULL )) { // // Don't know how expensive it is to check for infinite loop below // if (PrevFileName) { CurrentFileName = Dir->QueryName(); if (PrevFileName && CurrentFileName) { if (CurrentFileName->Strcmp(PrevFileName) == 0) { // something went wrong // GetNext should not return two files of the same name DELETE(Dir); DELETE(PrevFileName); DELETE(CurrentFileName); if (_ContinueSwitch) { DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE ); break; } else { DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR ); } } } else MemoryOk = FALSE; DELETE(PrevFileName); PrevFileName = CurrentFileName; CurrentFileName = NULL; if (!MemoryOk) break; } else PrevFileName = Dir->QueryName(); if ( !DirectoryFilter->DoesNodeMatch( (PFSNODE)Dir ) ) { DELETE(Dir); continue; } if (_ExclusionList != NULL && IsExcluded( Dir->GetPath() ) ) { DELETE(Dir); continue; } if (Created) { CurrentPathStr = Dir->GetPath()->QueryFullPathString(); if (CurrentPathStr == NULL) { DELETE(Dir); MemoryOk = FALSE; continue; } if (TargetPathStr->Stricmp(CurrentPathStr) == 0) { DELETE(CurrentPathStr); DELETE(Dir); continue; } DELETE(CurrentPathStr); } DebugAssert( Dir->IsDirectory() ); // If we're using short names then convert this fsnode. if (_UseShortSwitch && !Dir->UseAlternateName()) { DELETE(Dir); MemoryOk = FALSE; continue; } // // Append the name portion of the node to the destination path. // Name = Dir->QueryName(); DebugPtrAssert( Name ); if ( Name ) { MemoryOk = DestinationPath->AppendBase( Name ); DebugAssert( MemoryOk ); DELETE( Name ); _CanRemoveEmptyDirectories = (BOOLEAN)!_EmptySwitch; if ( MemoryOk ) { // // Recurse // Traverse( Dir, DestinationPath, FileFilter, DirectoryFilter, TRUE ); // // Restore the destination path // DestinationPath->TruncateBase(); } } else { MemoryOk = FALSE; } DELETE(Dir); } DELETE(PrevFileName); DELETE(CurrentFileName); if (TargetPathStr) DELETE(TargetPathStr); } } if ( !MemoryOk ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } // // If we created this directory but did not copy anything to it, we // have to remove it. // if ( Created && TargetDirectory->IsEmpty() && !_EmptySwitch && !_StructureOnlySwitch) { SYSTEM::RemoveNode( (PFSNODE *)&TargetDirectory, TRUE ); } else { DELETE( TargetDirectory ); } if ((ERROR_NO_MORE_FILES != GetNextError) && (ERROR_SUCCESS != GetNextError)) { // // Some other error when traversing a directory. We will already have // exited whatever loop we were inside due to the NULL file return. // SYSTEM::DisplaySystemError( GetNextError, !_ContinueSwitch); } } return TRUE; } BOOLEAN XCOPY::UpdateTraverse ( IN PFSN_DIRECTORY DestDirectory, IN OUT PPATH SourcePath, IN PFSN_FILTER FileFilter, IN PFSN_FILTER DirectoryFilter, IN BOOLEAN CopyDirectoryStreams ) /*++ Routine Description: Traverse routine for update. Like XCOPY::Traverse, except we traverse the *destination* directory, possibly updating files we find there. The theory being that there will be fewer files in the destination than the source, so we can save time this way. The callback function is invoked on each node (directory or file) visited. The traversal may be finished prematurely when the callback function returns FALSE. Arguments: DestDirectory - Supplies pointer to destination directory SourcePath - Supplies pointer to path to be used with the callback function. FileFilter - Supplies a pointer to the file filter. DirectoryFilter - Supplies a pointer to the directory filter. CopyDirectoryStreams - Specifies to copy directory streams when copying directories. Return Value: BOOLEAN - TRUE if everything traversed FALSE otherwise. --*/ { BOOLEAN MemoryOk; PFSN_FILE File; PFSN_DIRECTORY Dir; PWSTRING Name; BOOLEAN Created = FALSE; PCPATH TemplatePath = NULL; HANDLE h; PWSTRING CurrentFileName, PrevFileName; DWORD GetNextError = ERROR_SUCCESS; DebugPtrAssert( SourcePath ); DebugPtrAssert( FileFilter ); DebugPtrAssert( DirectoryFilter ); // Don't bother to traverse if // destination directory is null if (!DestDirectory) return TRUE; // // We only traverse this directory if it is not empty (unless the // empty switch is set). // if ( _EmptySwitch || !DestDirectory->IsEmpty() ) { // // Iterate through all files and copy them as needed // MemoryOk = TRUE; h = NULL; CurrentFileName = PrevFileName = NULL; while (MemoryOk && ((File = (PFSN_FILE)DestDirectory->GetNext( &h, &GetNextError )) != NULL)) { // // Don't know how expensive it is to check for infinite loop below // if (PrevFileName) { CurrentFileName = File->QueryName(); if (PrevFileName && CurrentFileName) { if (CurrentFileName->Strcmp(PrevFileName) == 0) { // something went wrong // GetNext should not return two files of the same name DELETE(File); DELETE(PrevFileName); DELETE(CurrentFileName); if (_ContinueSwitch) { DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE ); break; } else { DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR ); } } } else MemoryOk = FALSE; DELETE(PrevFileName); PrevFileName = CurrentFileName; CurrentFileName = NULL; if (!MemoryOk) break; } else PrevFileName = File->QueryName(); if ( !FileFilter->DoesNodeMatch( (PFSNODE)File ) ) { DELETE(File); continue; } DebugAssert( !File->IsDirectory() ); // If we're supposed to use the short name then convert fsnode. if (_UseShortSwitch && !File->UseAlternateName()) { DELETE(File); MemoryOk = FALSE; continue; } // // Append the name portion of the node to the destination path. // Name = File->QueryName(); DebugPtrAssert( Name ); if ( Name ) { PFSN_FILE SourceFile; PATH DestinationPath; PATH TmpPath; TmpPath.Initialize(SourcePath); TmpPath.AppendBase(Name); SourceFile = SYSTEM::QueryFile(&TmpPath); DestinationPath.Initialize(DestDirectory->GetPath()); MemoryOk = DestinationPath.AppendBase( Name ); DebugAssert( MemoryOk ); DELETE( Name ); if ( MemoryOk && NULL != SourceFile ) { // // Copy the file // if ( !Copier( SourceFile, &DestinationPath ) ) { DELETE(SourceFile); DELETE(File); ExitProgram( EXIT_MISC_ERROR ); } } DELETE(SourceFile); } else { MemoryOk = FALSE; } DELETE(File); } DELETE(PrevFileName); DELETE(CurrentFileName); if ( MemoryOk && (_ContinueSwitch || (ERROR_SUCCESS == GetNextError) || (ERROR_NO_MORE_FILES == GetNextError))) { // // If recursing, Traverse all the subdirectories // if ( _SubdirSwitch ) { MemoryOk = TRUE; h = NULL; CurrentFileName = PrevFileName = NULL; // // Recurse thru all the subdirectories // while (MemoryOk && ((Dir = (PFSN_DIRECTORY)DestDirectory->GetNext( &h, &GetNextError )) != NULL)) { // // Don't know how expensive it is to check for infinite loop below // if (PrevFileName) { CurrentFileName = Dir->QueryName(); if (PrevFileName && CurrentFileName) { if (CurrentFileName->Strcmp(PrevFileName) == 0) { // something went wrong // GetNext should not return two files of the same name DELETE(Dir); DELETE(PrevFileName); DELETE(CurrentFileName); if (_ContinueSwitch) { DisplayMessage( XCOPY_ERROR_INCOMPLETE_COPY, ERROR_MESSAGE ); break; } else { DisplayMessageAndExit( XCOPY_ERROR_INCOMPLETE_COPY, NULL, EXIT_MISC_ERROR ); } } } else MemoryOk = FALSE; DELETE(PrevFileName); PrevFileName = CurrentFileName; CurrentFileName = NULL; if (!MemoryOk) break; } else PrevFileName = Dir->QueryName(); if ( !DirectoryFilter->DoesNodeMatch( (PFSNODE)Dir ) ) { DELETE(Dir); continue; } DebugAssert( Dir->IsDirectory() ); // If we're using short names then convert this fsnode. if (_UseShortSwitch && !Dir->UseAlternateName()) { DELETE(Dir); MemoryOk = FALSE; continue; } // // Append the name portion of the node to the destination // path. // Name = Dir->QueryName(); DebugPtrAssert( Name ); if ( Name ) { MemoryOk = SourcePath->AppendBase( Name ); DebugAssert( MemoryOk ); DELETE( Name ); _CanRemoveEmptyDirectories = (BOOLEAN)!_EmptySwitch; if ( MemoryOk ) { if( _ExclusionList != NULL && IsExcluded( SourcePath ) ) { SourcePath->TruncateBase(); DELETE(Dir); continue; } // // Recurse // UpdateTraverse( Dir, SourcePath, FileFilter, DirectoryFilter, TRUE ); } SourcePath->TruncateBase(); } else { MemoryOk = FALSE; } DELETE(Dir); } DELETE(PrevFileName); DELETE(CurrentFileName); } } if ( !MemoryOk ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } else if ((ERROR_NO_MORE_FILES != GetNextError) && (ERROR_SUCCESS != GetNextError)) { // // Some other error when traversing a directory. // SYSTEM::DisplaySystemError( GetNextError, !_ContinueSwitch); } } return TRUE; } XCOPY::ProgressCallBack( LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData OPTIONAL ) /*++ Routine Description: Callback routine passed to CopyFileEx. Check to see if the user hit Ctrl-C and return appropriate value to CopyFileEx. Arguments: TotalFileSize - Total size of the file in bytes. TotalBytesTransferred - Total number of bytes transferred. StreamSize - Size of the stream being copied in bytes. StreamBytesTransferred - Number of bytes in current stream transferred. dwStreamNumber - Stream number of the current stream. dwCallBackReason - CALLBACK_CHUNK_FINISHED if a block was transferred, CALLBACK_STREAM_SWITCH if a stream completed copying. hSourceFile - Handle to the source file. hDestinationFile - Handle to the destination file. lpData - Pointer to opaque data that was passed to CopyFileEx. Used in this instance to pass the "this" pointer to an XCOPY object. Return Value: DWORD - PROGRESS_STOP if a Ctrl-C was hit and the copy was restartable, PROGESS_CANCEL otherwise. --*/ { FILETIME LastWriteTime; // // If the file was just created then roll back LastWriteTime a little so a subsequent // xcopy /d /z // will work if the copy was interrupted. // if ( dwStreamNumber == 1 && dwCallbackReason == CALLBACK_STREAM_SWITCH ) { if ( GetFileTime(hSourceFile, NULL, NULL, &LastWriteTime) ) { LastWriteTime.dwLowDateTime -= 1000; SetFileTime(hDestinationFile, NULL, NULL, &LastWriteTime); } } switch (dwCallbackReason) { case PRIVCALLBACK_STREAMS_NOT_SUPPORTED: case PRIVCALLBACK_COMPRESSION_NOT_SUPPORTED: case PRIVCALLBACK_ENCRYPTION_NOT_SUPPORTED: case PRIVCALLBACK_EAS_NOT_SUPPORTED: case PRIVCALLBACK_SPARSE_NOT_SUPPORTED: return PROGRESS_CONTINUE; case PRIVCALLBACK_ENCRYPTION_FAILED: // GetLastError will return ERROR_NOT_SUPPORTED if PROGRESS_STOP is // returned. The message is misleading so display our own error // message and return PROGRESS_CANCEL. ((XCOPY *)lpData)->DisplayMessage(XCOPY_ERROR_ENCRYPTION_FAILED); return PROGRESS_CANCEL; case PRIVCALLBACK_COMPRESSION_FAILED: case PRIVCALLBACK_SPARSE_FAILED: case PRIVCALLBACK_DACL_ACCESS_DENIED: case PRIVCALLBACK_SACL_ACCESS_DENIED: case PRIVCALLBACK_OWNER_GROUP_ACCESS_DENIED: case PRIVCALLBACK_OWNER_GROUP_FAILED: // display whatever GetLastError() contains return PROGRESS_STOP; case PRIVCALLBACK_SECURITY_INFORMATION_NOT_SUPPORTED: // GetLastError will return ERROR_NOT_SUPPORTED if PROGRESS_STOP is // returned. The message is misleading so display our own error // message and return PROGRESS_CANCEL. ((XCOPY *)lpData)->DisplayMessage(XCOPY_ERROR_SECURITY_INFO_NOT_SUPPORTED); return PROGRESS_CANCEL; } // GetPFlagBreak returns a pointer to the flag indicating whether a Ctrl-C was hit if ( *((( XCOPY *) lpData)->_Keyboard->GetPFlagBreak()) ) return ((XCOPY *) lpData)->_RestartableSwitch ? PROGRESS_STOP : PROGRESS_CANCEL; return PROGRESS_CONTINUE; } BOOLEAN XCOPY::Copier ( IN OUT PFSN_FILE File, IN PPATH DestinationPath ) /*++ Routine Description: This is the heart of XCopy. This is the guy who actually does the copying. Arguments: File - Supplies pointer to the source File. DestinationPath - Supplies path of the desired destination. Return Value: BOOLEAN - TRUE if copy successful. FALSE otherwise Notes: --*/ { PATH PathToCopy; PCWSTRING Name; COPY_ERROR CopyError; PFSN_FILE TargetFile = NULL; BOOLEAN Proceed; DWORD Attempts; WCHAR PathBuffer[MAX_PATH + 3]; FSTRING WriteBuffer; FSTRING EndOfLine; DSTRING ErrorMessage; PATH CanonSourcePath; BOOLEAN badCopy; ULONG flags; PTIMEINFO SourceFileTime, TargetFileTime; BOOLEAN TargetFileEncrypted, TargetFileExist; EndOfLine.Initialize((PWSTR) L"\r\n"); PathBuffer[0] = 0; WriteBuffer.Initialize(PathBuffer, MAX_PATH+3); // // Maximum number of attempts to copy a file // #define MAX_ATTEMPTS 3 AbortIfCtrlC(); _CanRemoveEmptyDirectories = FALSE; if( _ExclusionList != NULL && IsExcluded( File->GetPath() ) ) { return TRUE; } if ( _TargetIsFile ) { // // We replace the entire path // PathToCopy.Initialize( _TargetPath->GetPathString() ); PathToCopy.AppendBase( _FileNamePattern ); } else { // // Set the correct target file name. // PathToCopy.Initialize( DestinationPath ); if (!PathToCopy.ModifyName( _FileNamePattern )) { _Message.Set(MSG_COMP_UNABLE_TO_EXPAND); _Message.Display("%W%W", PathToCopy.QueryName(), _FileNamePattern); return FALSE; } } // // If in Update or CopyIfOld mode, determine if the target file // already exists and if it is older than the source file. // TargetFile = SYSTEM::QueryFile( &PathToCopy ); if (TargetFile) { TargetFileEncrypted = TargetFile->IsEncrypted(); TargetFileExist = TRUE; } else TargetFileEncrypted = TargetFileExist = FALSE; if ( _CopyIfOldSwitch || _UpdateSwitch ) { if ( TargetFile ) { // // Target exists. If in CopyIfOld mode, copy only if target // is older. If in Update mode, copy always. // if ( _CopyIfOldSwitch ) { SourceFileTime = File->QueryTimeInfo(); TargetFileTime = TargetFile->QueryTimeInfo(); if (SourceFileTime && TargetFileTime) Proceed = (*SourceFileTime > *TargetFileTime); else { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } DELETE(SourceFileTime); DELETE(TargetFileTime); } else { Proceed = TRUE; } if ( !Proceed ) { DELETE( TargetFile ); return TRUE; } } else if ( _UpdateSwitch ) { // // In update mode but target does not exist. We do not // copy. // return TRUE; } } DELETE(TargetFile); // // If the target is a file, we use that file path. Otherwise // we figure out the correct path for the destination. Then // we do the copy. // Name = File->GetPath()->GetPathString(); // // Make sure that we are not copying to ourselves // CanonSourcePath.Initialize(Name, TRUE); badCopy = (*(CanonSourcePath.GetPathString()) == *(PathToCopy.GetPathString())); if ( (!_PromptSwitch || UserConfirmedCopy( File->GetPath()->GetPathString(), _VerboseSwitch ? PathToCopy.GetPathString() : NULL )) && (badCopy || _OverWriteSwitch || !TargetFileExist || UserConfirmedOverWrite( &PathToCopy )) ) { // // If we are not prompting, we display the file name (unless we // are in silent mode ). // if ( !_PromptSwitch && !_SilentSwitch && !_StructureOnlySwitch ) { if ( _VerboseSwitch ) { DisplayMessage( XCOPY_MESSAGE_VERBOSE_COPY, NORMAL_MESSAGE, "%W%W", Name, PathToCopy.GetPathString() ); } else { WriteBuffer.Resize(0); if (WriteBuffer.Strcat(Name) && WriteBuffer.Strcat(&EndOfLine)) { GetStandardOutput()->WriteString(&WriteBuffer); } else { DisplayMessage( XCOPY_ERROR_PATH_TOO_LONG, ERROR_MESSAGE ); } } } // // Make sure that we are not copying to ourselves // if (badCopy) { DisplayMessageAndExit( XCOPY_ERROR_SELF_COPY, NULL, EXIT_MISC_ERROR ); } // // Copy file (unless we are in display-only mode) // if ( _DontCopySwitch || _StructureOnlySwitch ) { _FilesCopied++; } else { Attempts = 0; while ( TRUE ) { LPPROGRESS_ROUTINE Progress = NULL; PBOOL PCancelFlag = NULL; BOOLEAN bSuccess; // // If copying to floppy, we must determine if there is // enough disk space for the file, and if not then we // must ask for another disk and create all the directory // structure up to the parent directory. // if ( _DisketteCopy ) { if (!CheckTargetSpace( File, &PathToCopy )) return FALSE; } Progress = (LPPROGRESS_ROUTINE) ProgressCallBack; PCancelFlag = _Keyboard->GetPFlagBreak(); flags = (_ReadOnlySwitch ? FSN_FILE_COPY_OVERWRITE_READ_ONLY : 0); flags |= (!_CopyAttrSwitch ? FSN_FILE_COPY_RESET_READ_ONLY : 0); flags |= (_RestartableSwitch ? FSN_FILE_COPY_RESTARTABLE : 0); flags |= (_OwnerSwitch ? FSN_FILE_COPY_COPY_OWNER : 0); flags |= (_AuditSwitch ? FSN_FILE_COPY_COPY_ACL : 0); flags |= (_DecryptSwitch ? FSN_FILE_COPY_ALLOW_DECRYPTED_DESTINATION : 0); bSuccess = File->Copy(&PathToCopy, &CopyError, flags, Progress, (VOID *)this, PCancelFlag); if (bSuccess) { if (!_CopyAttrSwitch && (TargetFile = SYSTEM::QueryFile( &PathToCopy )) ) { DWORD dwError; TargetFile->MakeArchived(&dwError); DELETE(TargetFile); } if ( _ModifySwitch ) { File->ResetArchivedAttribute(); } if( _VerifySwitch ) { // Check that the new file is the same length as // the old file. // if( (TargetFile = SYSTEM::QueryFile( &PathToCopy )) == NULL || TargetFile->QuerySize() != File->QuerySize() ) { DELETE( TargetFile ); DisplayMessage( XCOPY_ERROR_VERIFY_FAILED, ERROR_MESSAGE ); if ( !_ContinueSwitch ) { return FALSE; } break; } DELETE( TargetFile ); } _FilesCopied++; break; } else { // // If the copy was cancelled mid-stream, exit. // AbortIfCtrlC(); if (CopyError == COPY_ERROR_REQUEST_ABORTED) return FALSE; if (CopyError == COPY_ERROR_ACCESS_DENIED && TargetFileExist && TargetFileEncrypted) { Attempts = MAX_ATTEMPTS; } // // In case of error, wait for a little while and try // again, otherwise display the error. // if ( Attempts++ < MAX_ATTEMPTS ) { Sleep( 100 ); } else { switch ( CopyError ) { case COPY_ERROR_ACCESS_DENIED: DisplayMessage( XCOPY_ERROR_ACCESS_DENIED, ERROR_MESSAGE); break; case COPY_ERROR_SHARE_VIOLATION: DisplayMessage( XCOPY_ERROR_SHARING_VIOLATION, ERROR_MESSAGE); break; default: // // At this point we don't know if the copy left a // bogus file on disk. If the target file exist, // we assume that it is bogus so we delete it. // if ((TargetFile = SYSTEM::QueryFile( &PathToCopy )) && !_RestartableSwitch) { TargetFile->DeleteFromDisk( TRUE ); DELETE( TargetFile ); } switch ( CopyError ) { case COPY_ERROR_DISK_FULL: DisplayMessageAndExit( XCOPY_ERROR_DISK_FULL, NULL, EXIT_MISC_ERROR ); break; default: if (SYSTEM::QueryWindowsErrorMessage(CopyError, &ErrorMessage)) { DisplayMessage( XCOPY_ERROR_CANNOT_MAKE, ERROR_MESSAGE, "%W", &ErrorMessage ); } break; } break; } if ( !_ContinueSwitch ) { return FALSE; } break; } } } } } DELETE( TargetFile ); return TRUE; } PFSN_DIRECTORY XCOPY::MakeDirectory ( IN PPATH DestinationPath, IN PCPATH TemplatePath ) /*++ Routine Description: This is the heart of XCopy. This is the guy who actually does the copying. Arguments: DestinationPath - Supplies path of the desired directory TemplatePath - Supplies path of the source directory Return Value: BOOLEAN - TRUE if copy successful. FALSE otherwise. Notes: --*/ { ULONG flags; BOOLEAN bSuccess; PFSN_DIRECTORY rtn; DSTRING ErrorMessage; COPY_ERROR CopyError; flags = (_RestartableSwitch ? FSN_FILE_COPY_RESTARTABLE : 0); flags |= (_OwnerSwitch ? FSN_FILE_COPY_COPY_OWNER : 0); flags |= (_AuditSwitch ? FSN_FILE_COPY_COPY_ACL : 0); rtn = SYSTEM::MakeDirectory( DestinationPath, TemplatePath, &CopyError, (LPPROGRESS_ROUTINE)ProgressCallBack, (VOID *)this, _Keyboard->GetPFlagBreak(), flags ); if (rtn == NULL) { // // If the copy was cancelled mid-stream, exit. // AbortIfCtrlC(); switch ( CopyError ) { case COPY_ERROR_SUCCESS: DisplayMessage( XCOPY_ERROR_UNKNOWN, ERROR_MESSAGE); break; case COPY_ERROR_REQUEST_ABORTED: break; case COPY_ERROR_ACCESS_DENIED: DisplayMessage( XCOPY_ERROR_ACCESS_DENIED, ERROR_MESSAGE); break; case COPY_ERROR_SHARE_VIOLATION: DisplayMessage( XCOPY_ERROR_SHARING_VIOLATION, ERROR_MESSAGE); break; case COPY_ERROR_DISK_FULL: DisplayMessageAndExit( XCOPY_ERROR_DISK_FULL, NULL, EXIT_MISC_ERROR ); break; default: if (SYSTEM::QueryWindowsErrorMessage(CopyError, &ErrorMessage)) DisplayMessage( XCOPY_ERROR_CANNOT_MAKE, ERROR_MESSAGE, "%W", &ErrorMessage ); break; } } return rtn; } BOOLEAN XCOPY::CheckTargetSpace ( IN OUT PFSN_FILE File, IN PPATH DestinationPath ) /*++ Routine Description: Makes sure that there is enought disk space in the target disk. Asks the user to change the disk if necessary. Arguments: File - Supplies pointer to the source File. DestinationPath - Supplies path of the desired destination. Return Value: BOOLEAN - TRUE if OK FALSE otherwise --*/ { PFSN_FILE TargetFile = NULL; BIG_INT TargetSize; PWSTRING TargetDrive; WCHAR Resp; DSTRING TargetRoot; DSTRING Slash; PATH TmpPath; PATH TmpPath1; PFSN_DIRECTORY PartialDirectory = NULL; PFSN_DIRECTORY DestinationDirectory = NULL; BOOLEAN DirDeleted = NULL; PATH PathToDelete; CHNUM CharsInPartialDirectoryPath; BIG_INT FreeSpace; BIG_INT FileSize; if ( TargetFile = SYSTEM::QueryFile( DestinationPath ) ) { TargetSize = TargetFile->QuerySize(); DELETE( TargetFile ); } else { TargetSize = 0; } TargetDrive = DestinationPath->QueryDevice(); FileSize = File->QuerySize(); if ( TargetDrive ) { TargetRoot.Initialize( TargetDrive ); if ( TargetRoot.QueryChAt( TargetRoot.QueryChCount()-1) != (WCHAR)'\\' ) { Slash.Initialize( "\\" ); TargetRoot.Strcat( &Slash ); } while ( TRUE ) { if ( IFS_SYSTEM::QueryFreeDiskSpace( &TargetRoot, &FreeSpace ) ) { FreeSpace = FreeSpace + TargetSize; // DebugPrintTrace(( "Disk Space: %d Needed: %d\n", FreeSpace.GetLowPart(), FileSize.GetLowPart() )); if ( FreeSpace < FileSize ) { // // Not enough free space, ask for another // disk and create the directory structure. // DisplayMessage( XCOPY_MESSAGE_CHANGE_DISK, NORMAL_MESSAGE ); AbortIfCtrlC(); _Keyboard->DisableLineMode(); if ( GetStandardInput()->IsAtEnd() ) { // Insufficient input--treat as CONTROL-C. // Resp = CTRL_C; } else { GetStandardInput()->ReadChar( &Resp ); } _Keyboard->EnableLineMode(); if ( Resp == CTRL_C ) { exit( EXIT_TERMINATED ); } else { GetStandardOutput()->WriteChar( Resp ); GetStandardOutput()->WriteChar( '\r' ); GetStandardOutput()->WriteChar( '\n' ); } // // Create directory structure in target // TmpPath.Initialize( DestinationPath ); TmpPath.TruncateBase(); PartialDirectory = SYSTEM::QueryDirectory( &TmpPath, TRUE ); if (PartialDirectory == NULL ) { if (GetLastError() == COPY_ERROR_REQUEST_ABORTED) { DELETE( TargetDrive ); return FALSE; } continue; } else { if ( *(PartialDirectory->GetPath()->GetPathString()) != *(TmpPath.GetPathString()) ) { TmpPath1.Initialize( &TmpPath ); DestinationDirectory = PartialDirectory->CreateDirectoryPath( &TmpPath1 ); if ( !DestinationDirectory ) { DisplayMessageAndExit( XCOPY_ERROR_CREATE_DIRECTORY, NULL, EXIT_MISC_ERROR ); } } else { DestinationDirectory = PartialDirectory; } } // // If still not enough disk space, remove the directories // that we created and try again // IFS_SYSTEM::QueryFreeDiskSpace( TargetDrive, &FreeSpace ); FreeSpace = FreeSpace + TargetSize; if ( FreeSpace < FileSize ) { if ( PartialDirectory != DestinationDirectory ) { if (!PathToDelete.Initialize( DestinationDirectory->GetPath() )) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } CharsInPartialDirectoryPath = PartialDirectory->GetPath()->GetPathString()->QueryChCount(); while ( PathToDelete.GetPathString()->QueryChCount() > CharsInPartialDirectoryPath ) { DirDeleted = DestinationDirectory->DeleteDirectory(); DebugAssert( DirDeleted ); DELETE( DestinationDirectory ); DestinationDirectory = NULL; PathToDelete.TruncateBase(); DestinationDirectory = SYSTEM::QueryDirectory( &PathToDelete ); DebugPtrAssert( DestinationDirectory ); } } } if ( PartialDirectory != DestinationDirectory ) { DELETE( PartialDirectory ); DELETE( DestinationDirectory ); } else { DELETE( PartialDirectory ); } } else { break; } } else { // // Cannot determine free disk space! // if (GetLastError() == COPY_ERROR_REQUEST_ABORTED) { DELETE( TargetDrive ); return FALSE; } break; } } DELETE( TargetDrive ); } return TRUE; } VOID XCOPY::GetDirectoryAndFilters ( IN PPATH Path, OUT PFSN_DIRECTORY *OutDirectory, OUT PFSN_FILTER *FileFilter, OUT PFSN_FILTER *DirectoryFilter, OUT PBOOLEAN CopyingManyFiles ) /*++ Routine Description: Obtains a directory object and the filename to match Arguments: Path - Supplies pointer to the path OutDirectory - Supplies pointer to pointer to directory FileFilter - Supplies filter for files DirectoryFilter - Supplies filter for directories CopyingManyFiles - Supplies pointer to flag which if TRUE means that we are copying many files Return Value: None. Notes: --*/ { PFSN_DIRECTORY Directory; PFSN_FILE File; PWSTRING Prefix = NULL; PWSTRING FileName = NULL; PATH PrefixPath; PATH TmpPath; FSN_ATTRIBUTE All = (FSN_ATTRIBUTE)0; FSN_ATTRIBUTE Any = (FSN_ATTRIBUTE)0; FSN_ATTRIBUTE None = (FSN_ATTRIBUTE)0; PFSN_FILTER FilFilter; PFSN_FILTER DirFilter; DSTRING Name; // // Create filters // if ( ( (FilFilter = NEW FSN_FILTER) == NULL ) || ( (DirFilter = NEW FSN_FILTER) == NULL ) || !FilFilter->Initialize() || !DirFilter->Initialize() ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } if (( Directory = SYSTEM::QueryDirectory( Path )) != NULL ) { // // Copying a directory. We will want everything in the directory // FilFilter->SetFileName( "*.*" ); *CopyingManyFiles = TRUE; } else { // // The path is not a directory. Get the prefix part (which SHOULD // be a directory, and try to make a directory from it // *CopyingManyFiles = Path->HasWildCard(); if ( !*CopyingManyFiles ) { // // If the path is not a file, then this is an error // if ( !(File = SYSTEM::QueryFile( Path )) ) { if ((FileName = Path->QueryName()) == NULL || !Name.Initialize( FileName )) { DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR ); } DisplayMessageAndExit( XCOPY_ERROR_FILE_NOT_FOUND, &Name, EXIT_MISC_ERROR ); } DELETE( File ); } Prefix = Path->QueryPrefix(); if ( !Prefix ) { // // No prefix, use the drive part. // TmpPath.Initialize( Path, TRUE ); Prefix = TmpPath.QueryDevice(); if (Prefix == NULL) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); return; } } if ( !PrefixPath.Initialize( Prefix, FALSE ) ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } if (( Directory = SYSTEM::QueryDirectory( &PrefixPath )) != NULL ) { // // Directory is ok, set the filter's filename criteria // with the file (pattern) specified. // if ((FileName = Path->QueryName()) == NULL ) { DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR ); } FilFilter->SetFileName( FileName ); } else { // // Something went wrong... // if ((FileName = Path->QueryName()) == NULL || !Name.Initialize( FileName )) { DisplayMessageAndExit( XCOPY_ERROR_INVALID_PATH, NULL, EXIT_MISC_ERROR ); } DisplayMessageAndExit( XCOPY_ERROR_FILE_NOT_FOUND, &Name, EXIT_MISC_ERROR ); } DELETE( Prefix ); DELETE( FileName ); } // // Ok, we have the directory object and the filefilter's path set. // // // Set the file filter attribute criteria // None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_DIRECTORY ); if ( !_HiddenSwitch ) { None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_HIDDEN | FSN_ATTRIBUTE_SYSTEM ); } if (_ArchiveSwitch) { All = (FSN_ATTRIBUTE)(All | FSN_ATTRIBUTE_ARCHIVE); } FilFilter->SetAttributes( All, Any, None ); // // Set the file filter's time criteria // if ( _Date != NULL ) { FilFilter->SetTimeInfo( _Date, FSN_TIME_MODIFIED, (TIME_AT | TIME_AFTER) ); } // // Set the directory filter attribute criteria. // All = (FSN_ATTRIBUTE)0; Any = (FSN_ATTRIBUTE)0; None = (FSN_ATTRIBUTE)0; if ( !_HiddenSwitch ) { None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_HIDDEN | FSN_ATTRIBUTE_SYSTEM ); } if (_SubdirSwitch) { All = (FSN_ATTRIBUTE)(All | FSN_ATTRIBUTE_DIRECTORY); } else { None = (FSN_ATTRIBUTE)(None | FSN_ATTRIBUTE_DIRECTORY); } DirFilter->SetAttributes( All, Any, None ); *FileFilter = FilFilter; *DirectoryFilter = DirFilter; *OutDirectory = Directory; } VOID XCOPY::GetDirectoryAndFilePattern( IN PPATH Path, IN BOOLEAN CopyingManyFiles, OUT PPATH *OutDirectory, OUT PWSTRING *OutFilePattern ) /*++ Routine Description: Gets the path of the destination directory and the pattern that will be used for filename conversion. Arguments: Path - Supplies pointer to the path CopyingManyFiles - Supplies flag which if true means that we are copying many files. OutDirectory - Supplies pointer to pointer to directory path OutFilePattern - Supplies pointer to pointer to file name IsDir ` - Supplies pointer to isdir flag Return Value: None. Notes: --*/ { PPATH Directory; PWSTRING FileName; PWSTRING Prefix = NULL; PWSTRING Name = NULL; BOOLEAN DeletePath = FALSE; PFSN_DIRECTORY TmpDir; PATH TmpPath; DSTRING Slash; DSTRING TmpPath1Str; PATH TmpPath1; if ( !Path ) { // // There is no path, we invent our own // if ( ((Path = NEW PATH) == NULL ) || !Path->Initialize( (LPWSTR)L"*.*", FALSE)) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } DeletePath = TRUE; } TmpDir = SYSTEM::QueryDirectory( Path ); if ( !TmpDir && (Path->HasWildCard() || IsFileName( Path, CopyingManyFiles ))) { // // The path is not a directory, so we use the prefix as a // directory path and the filename becomes the pattern. // if ( !TmpPath.Initialize( Path, TRUE ) || ((Prefix = TmpPath.QueryPrefix()) == NULL) || !Slash.Initialize( "\\" ) || !TmpPath1Str.Initialize( Prefix ) || !TmpPath1.Initialize( &TmpPath1Str, FALSE ) || ((Name = TmpPath.QueryName()) == NULL) || ((Directory = NEW PATH) == NULL) || !Directory->Initialize( &TmpPath1, TRUE ) || ((FileName = NEW DSTRING) == NULL ) || !FileName->Initialize( Name ) ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } DELETE( Prefix ); DELETE( Name ); } else { // // The path specifies a directory, so we use all of it and the // pattern is "*.*" // if ( ((Directory = NEW PATH) == NULL ) || !Directory->Initialize( Path,TRUE ) || ((FileName = NEW DSTRING) == NULL ) || !FileName->Initialize( "*.*" ) ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } DELETE( TmpDir ); } *OutDirectory = Directory; *OutFilePattern = FileName; // // If we created the path, we have to delete it // if ( DeletePath ) { DELETE( Path ); } } BOOL XCOPY::IsCyclicalCopy( IN PPATH PathSrc, IN PPATH PathTrg ) /*++ Routine Description: Determines if there is a cycle between two paths Arguments: PathSrc - Supplies pointer to first path PathTrg - Supplies pointer to second path Return Value: TRUE if there is a cycle, FALSE otherwise --*/ { PATH SrcPath; PATH TrgPath; PARRAY ArraySrc, ArrayTrg; PARRAY_ITERATOR IteratorSrc, IteratorTrg; PWSTRING ComponentSrc, ComponentTrg; BOOLEAN IsCyclical = FALSE; DebugAssert( PathSrc != NULL ); if ( PathTrg != NULL ) { // // Get canonicalized paths for both source and target // SrcPath.Initialize(PathSrc, TRUE ); TrgPath.Initialize(PathTrg, TRUE ); // // Split the paths into their components // ArraySrc = SrcPath.QueryComponentArray(); ArrayTrg = TrgPath.QueryComponentArray(); DebugPtrAssert( ArraySrc ); DebugPtrAssert( ArrayTrg ); if ( !ArraySrc || !ArrayTrg ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } // // Get iterators for the components // IteratorSrc = ( PARRAY_ITERATOR )ArraySrc->QueryIterator(); IteratorTrg = ( PARRAY_ITERATOR )ArrayTrg->QueryIterator(); DebugPtrAssert( IteratorSrc ); DebugPtrAssert( IteratorTrg ); if ( !IteratorSrc || !IteratorTrg ) { DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR ); } // // There is a cycle if all of the source is along the target. // while ( TRUE ) { ComponentSrc = (PWSTRING)IteratorSrc->GetNext(); if ( !ComponentSrc ) { // // The source path is along the target path. This is a // cycle. // IsCyclical = TRUE; break; } ComponentTrg = (PWSTRING)IteratorTrg->GetNext(); if ( !ComponentTrg ) { // // The target path is along the source path. This is no // cycle. // break; } if ( *ComponentSrc != *ComponentTrg ) { // // One path is not along the other. There is no cycle. // break; } } DELETE( IteratorSrc ); DELETE( IteratorTrg ); ArraySrc->DeleteAllMembers(); ArrayTrg->DeleteAllMembers(); DELETE( ArraySrc ); DELETE( ArrayTrg ); } return IsCyclical; } BOOL XCOPY::IsFileName( IN PPATH Path, IN BOOLEAN CopyingManyFiles ) /*++ Routine Description: Figures out if a name refers to a directory or a file. Arguments: Path - Supplies pointer to the path CopyingManyFiles - Supplies flag which if TRUE means that we are copying many files. Return Value: BOOLEAN - TRUE if name refers to file, FALSE otherwise Notes: --*/ { PFSN_DIRECTORY FsnDirectory; PFSN_FILE FsnFile; WCHAR Resp; PWSTRING DirMsg; PWSTRING FilMsg; // // If the path is an existing directory, then this is obviously // not a file. // // if ((FsnDirectory = SYSTEM::QueryDirectory( Path )) != NULL ) { DELETE( FsnDirectory ); return FALSE; } // // If the path ends with a delimiter, then it is a directory. // We remove the delimiter. // if ( Path->EndsWithDelimiter() ) { ((PWSTRING) Path->GetPathString())->Truncate( Path->GetPathString()->QueryChCount() - 1 ); Path->Initialize( Path->GetPathString() ); return FALSE; } // // If the path is an existing file, then it is a file. // if ((FsnFile = SYSTEM::QueryFile( Path )) != NULL ) { DELETE( FsnFile ); return _TargetIsFile = TRUE; } DirMsg = QueryMessageString(XCOPY_RESPONSE_DIRECTORY); FilMsg = QueryMessageString(XCOPY_RESPONSE_FILE); DebugPtrAssert( DirMsg ); DebugPtrAssert( FilMsg ); // // If the path does not exist, we are copying many files, and we are intelligent, // then the target is obviously a directory. // // Otherwise we simply ask the user. // if ( _IntelligentSwitch && CopyingManyFiles ) { _TargetIsFile = FALSE; } else { while ( TRUE ) { DisplayMessage( XCOPY_MESSAGE_FILE_OR_DIRECTORY, NORMAL_MESSAGE, "%W", Path->GetPathString() ); AbortIfCtrlC(); _Keyboard->DisableLineMode(); if( GetStandardInput()->IsAtEnd() ) { // Insufficient input--treat as CONTROL-C. // Resp = CTRL_C; } else { GetStandardInput()->ReadChar( &Resp ); } _Keyboard->EnableLineMode(); if ( Resp == CTRL_C ) { exit( EXIT_TERMINATED ); } else { GetStandardOutput()->WriteChar( Resp ); GetStandardOutput()->WriteChar( '\r' ); GetStandardOutput()->WriteChar( '\n' ); } Resp = (WCHAR)towupper( (wchar_t)Resp ); if ( FilMsg->QueryChAt(0) == Resp ) { _TargetIsFile = TRUE; break; } else if ( DirMsg->QueryChAt(0) == Resp ) { _TargetIsFile = FALSE; break; } } } DELETE( DirMsg ); DELETE( FilMsg ); return _TargetIsFile; } BOOLEAN XCOPY::UserConfirmedCopy ( IN PCWSTRING SourcePath, IN PCWSTRING DestinationPath ) /*++ Routine Description: Gets confirmation from the user about a file to be copied Arguments: SourcePath - Supplies the path to the file to be copied DestinationPath - Supplies the destination path of the file to be created Return Value: BOOLEAN - TRUE if the user confirmed the copy FALSE otherwise --*/ { PWSTRING YesMsg; PWSTRING NoMsg; WCHAR Resp; BOOLEAN Confirmed; YesMsg = QueryMessageString(XCOPY_RESPONSE_YES); NoMsg = QueryMessageString(XCOPY_RESPONSE_NO); DebugPtrAssert( YesMsg ); DebugPtrAssert( NoMsg ); while ( TRUE ) { if (DestinationPath) { DisplayMessage( XCOPY_MESSAGE_CONFIRM3, NORMAL_MESSAGE, "%W%W", SourcePath, DestinationPath ); } else { DisplayMessage( XCOPY_MESSAGE_CONFIRM, NORMAL_MESSAGE, "%W", SourcePath ); } AbortIfCtrlC(); _Keyboard->DisableLineMode(); if( GetStandardInput()->IsAtEnd() ) { // Insufficient input--treat as CONTROL-C. // Resp = NoMsg->QueryChAt( 0 ); break; } else { GetStandardInput()->ReadChar( &Resp ); } _Keyboard->EnableLineMode(); if ( Resp == CTRL_C ) { exit( EXIT_TERMINATED ); } else { GetStandardOutput()->WriteChar( Resp ); GetStandardOutput()->WriteChar( '\r' ); GetStandardOutput()->WriteChar( '\n' ); } Resp = (WCHAR)towupper( (wchar_t)Resp ); if ( YesMsg->QueryChAt( 0 ) == Resp ) { Confirmed = TRUE; break; } else if ( NoMsg->QueryChAt( 0 ) == Resp ) { Confirmed = FALSE; break; } } DELETE( YesMsg ); DELETE( NoMsg ); return Confirmed; } BOOLEAN XCOPY::UserConfirmedOverWrite ( IN PPATH DestinationFile ) /*++ Routine Description: Gets confirmation from the user about the overwriting of an existing file Arguments: FsNode - Supplies pointer to FSNODE of file to be copied Return Value: BOOLEAN - TRUE if the user confirmed the overwrite FALSE otherwise --*/ { PWSTRING YesMsg; PWSTRING NoMsg; PWSTRING AllMsg; WCHAR Resp; BOOLEAN Confirmed; YesMsg = QueryMessageString(XCOPY_RESPONSE_YES); NoMsg = QueryMessageString(XCOPY_RESPONSE_NO); AllMsg = QueryMessageString(XCOPY_RESPONSE_ALL); DebugPtrAssert( YesMsg ); DebugPtrAssert( NoMsg ); DebugPtrAssert( AllMsg ); while ( TRUE ) { DisplayMessage( XCOPY_MESSAGE_CONFIRM2, NORMAL_MESSAGE, "%W", DestinationFile->GetPathString() ); AbortIfCtrlC(); _Keyboard->DisableLineMode(); if( GetStandardInput()->IsAtEnd() ) { // Insufficient input--treat as CONTROL-C. // exit( EXIT_TERMINATED ); break; } else { GetStandardInput()->ReadChar( &Resp ); } _Keyboard->EnableLineMode(); if ( Resp == CTRL_C ) { exit( EXIT_TERMINATED ); } else { GetStandardOutput()->WriteChar( Resp ); GetStandardOutput()->WriteChar( '\r' ); GetStandardOutput()->WriteChar( '\n' ); } Resp = (WCHAR)towupper( (wchar_t)Resp ); if ( YesMsg->QueryChAt( 0 ) == Resp ) { Confirmed = TRUE; break; } else if ( NoMsg->QueryChAt( 0 ) == Resp ) { Confirmed = FALSE; break; } else if ( AllMsg->QueryChAt(0) == Resp ) { Confirmed = _OverWriteSwitch = TRUE; break; } } DELETE( YesMsg ); DELETE( NoMsg ); DELETE( AllMsg ); return Confirmed; } BOOLEAN XCOPY::IsExcluded( IN PCPATH Path ) /*++ Routine Description: This method determines whether the specified path should be excluded from the XCOPY. Arguments: Path -- Supplies the path of the file in question. Return Value: TRUE if this file should be excluded, i.e. if any element of the exclusion list array appears as a substring of this path. --*/ { PWSTRING CurrentString; DSTRING UpcasedPath; DSTRING BackSlash; if( _ExclusionList == NULL ) { return FALSE; } if (!UpcasedPath.Initialize(Path->GetPathString()) || !BackSlash.Initialize(L"\\")) { DebugPrint("XCOPY: Out of memory \n"); return FALSE; } UpcasedPath.Strupr( ); if (!Path-> EndsWithDelimiter() && !UpcasedPath.Strcat(&BackSlash)) { DebugPrint("XCOPY: Out of memory \n"); return FALSE; } _Iterator->Reset(); while ((CurrentString = (PWSTRING)_Iterator->GetNext()) != NULL) { if (UpcasedPath.Strstr(CurrentString) != INVALID_CHNUM) { return TRUE; } } return FALSE; }