mirror of https://github.com/tongzx/nt5src
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
2933 lines
80 KiB
/*++
|
|
|
|
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 <ctype.h>
|
|
#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;
|
|
}
|
|
|