NT4/private/utils/xcopy/argument.cxx

876 lines
26 KiB
C++
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
Argument
Abstract:
Argument processing for the XCopy directory copy utility
Author:
Ramon Juan San Andres (ramonsa) 01-May-1991
Notes:
The arguments accepted by the XCopy utility are:
Source directory.- Source path.
Dest. directory.- Destination path.
Archive switch.- Copy files that have their archive bit set
Date.- Copy files modified on or after the specifiec
date.
Empty switch.- Copy directories even if empty. Subdir switch
must also be set.
Modify switch.- Same as Archive switch, but turns off archive
bit in the source file after copying.
Prompt switch.- Prompts before copying each file.
Subdir switch.- Copies also subdirectories, unless they are empty.
(Empty directories are copied if the Empty switch
is set).
Verify switch.- Verifies each copy.
Wait switch.- Wait before starting to copy the files.
Revision History:
--*/
#include "ulib.hxx"
#include "arg.hxx"
#include "arrayit.hxx"
#include "dir.hxx"
#include "xcopy.hxx"
#include "stringar.hxx"
#include "file.hxx"
#include "filestrm.hxx"
//
// Switch characters. Used for maintaining DOS5 compatibility when
// displaying error messages
//
#define SWITCH_CHARACTERS "dDaAeEmMpPsSvVwW?"
//
// Static variables
//
PARRAY LexArray;
PPATH_ARGUMENT FirstPathArgument = NULL;
PPATH_ARGUMENT FirstQuotedPathArgument = NULL;
PPATH_ARGUMENT SecondPathArgument = NULL;
PPATH_ARGUMENT SecondQuotedPathArgument = NULL;
PFLAG_ARGUMENT ArchiveArgument = NULL;
PTIMEINFO_ARGUMENT DateArgument = NULL;
PFLAG_ARGUMENT EmptyArgument = NULL;
PFLAG_ARGUMENT ModifyArgument = NULL;
PFLAG_ARGUMENT PromptArgument = NULL;
PFLAG_ARGUMENT SubdirArgument = NULL;
PFLAG_ARGUMENT VerifyArgument = NULL;
PFLAG_ARGUMENT WaitArgument = NULL;
PFLAG_ARGUMENT HelpArgument = NULL;
PFLAG_ARGUMENT ContinueArgument = NULL;
PFLAG_ARGUMENT IntelligentArgument = NULL;
PFLAG_ARGUMENT VerboseArgument = NULL;
PFLAG_ARGUMENT OldArgument = NULL;
PFLAG_ARGUMENT HiddenArgument = NULL;
PFLAG_ARGUMENT ReadOnlyArgument = NULL;
PFLAG_ARGUMENT SilentArgument = NULL;
PFLAG_ARGUMENT NoCopyArgument = NULL;
PFLAG_ARGUMENT StructureArgument = NULL;
PFLAG_ARGUMENT UpdateArgument = NULL;
PFLAG_ARGUMENT CopyAttrArgument = NULL;
PFLAG_ARGUMENT UseShortArgument = NULL;
PFLAG_ARGUMENT RestartableArgument = NULL;
PSTRING_ARGUMENT ExclusionListArgument = NULL;
PSTRING_ARGUMENT InvalidSwitchArgument = NULL;
BOOLEAN HelpSwitch;
//
// Prototypes
//
VOID
XCOPY::SetArguments(
)
/*++
Routine Description:
Obtains the arguments for the XCopy utility
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
PATH_ARGUMENT LocalFirstPathArgument;
PATH_ARGUMENT LocalFirstQuotedPathArgument;
PATH_ARGUMENT LocalSecondPathArgument;
PATH_ARGUMENT LocalSecondQuotedPathArgument;
FLAG_ARGUMENT LocalArchiveArgument;
TIMEINFO_ARGUMENT LocalDateArgument;
FLAG_ARGUMENT LocalOldArgument;
FLAG_ARGUMENT LocalEmptyArgument;
FLAG_ARGUMENT LocalModifyArgument;
FLAG_ARGUMENT LocalPromptArgument;
FLAG_ARGUMENT LocalSubdirArgument;
FLAG_ARGUMENT LocalVerifyArgument;
FLAG_ARGUMENT LocalWaitArgument;
FLAG_ARGUMENT LocalHelpArgument;
FLAG_ARGUMENT LocalContinueArgument;
FLAG_ARGUMENT LocalIntelligentArgument;
FLAG_ARGUMENT LocalVerboseArgument;
FLAG_ARGUMENT LocalHiddenArgument;
FLAG_ARGUMENT LocalReadOnlyArgument;
FLAG_ARGUMENT LocalSilentArgument;
FLAG_ARGUMENT LocalNoCopyArgument;
FLAG_ARGUMENT LocalStructureArgument;
FLAG_ARGUMENT LocalUpdateArgument;
FLAG_ARGUMENT LocalCopyAttrArgument;
FLAG_ARGUMENT LocalUseShortArgument;
FLAG_ARGUMENT LocalRestartableArgument;
STRING_ARGUMENT LocalExclusionListArgument;
STRING_ARGUMENT LocalInvalidSwitchArgument;
ARRAY LocalLexArray;
//
// Set the static global pointers
//
FirstPathArgument = &LocalFirstPathArgument;
FirstQuotedPathArgument = &LocalFirstQuotedPathArgument;
SecondPathArgument = &LocalSecondPathArgument;
SecondQuotedPathArgument = &LocalSecondQuotedPathArgument;
ArchiveArgument = &LocalArchiveArgument;
DateArgument = &LocalDateArgument;
OldArgument = &LocalOldArgument;
EmptyArgument = &LocalEmptyArgument;
ModifyArgument = &LocalModifyArgument;
PromptArgument = &LocalPromptArgument;
SubdirArgument = &LocalSubdirArgument;
VerifyArgument = &LocalVerifyArgument;
WaitArgument = &LocalWaitArgument;
HelpArgument = &LocalHelpArgument;
ContinueArgument = &LocalContinueArgument;
IntelligentArgument = &LocalIntelligentArgument;
VerboseArgument = &LocalVerboseArgument;
HiddenArgument = &LocalHiddenArgument;
ReadOnlyArgument = &LocalReadOnlyArgument;
SilentArgument = &LocalSilentArgument;
NoCopyArgument = &LocalNoCopyArgument;
StructureArgument = &LocalStructureArgument;
UpdateArgument = &LocalUpdateArgument;
CopyAttrArgument = &LocalCopyAttrArgument;
UseShortArgument = &LocalUseShortArgument;
ExclusionListArgument = &LocalExclusionListArgument;
InvalidSwitchArgument = &LocalInvalidSwitchArgument;
LexArray = &LocalLexArray;
RestartableArgument = &LocalRestartableArgument;
//
// Parse the arguments
//
GetArgumentsCmd();
//
// Verify the arguments
//
CheckArgumentConsistency();
LocalLexArray.DeleteAllMembers();
}
VOID
GetSourceAndDestinationPath(
IN OUT PPATH_ARGUMENT FirstPathArgument,
IN OUT PPATH_ARGUMENT FirstQuotedPathArgument,
IN OUT PPATH_ARGUMENT SecondPathArgument,
IN OUT PPATH_ARGUMENT SecondQuotedPathArgument,
IN OUT PARGUMENT_LEXEMIZER ArgLex,
OUT PPATH* SourcePath,
OUT PPATH* DestinationPath
)
/*++
Routine Description:
This routine computes the Source and Destination path from
the given list of arguments.
Arguments:
FirstPathArgument - Supplies the first unquoted path argument.
FirstQuotedPathArgument - Supplies the first quoted path argument.
SecondPathArgument - Supplies the second unquoted path argument.
SecondQuotedPathArgument - Supplies the second quoted path argument.
ArgLex - Supplies the argument lexemizer.
SourcePath - Returns the source path.
DestinationPath - Returns the destination path.
Return Value:
None.
--*/
{
BOOLEAN f, qf, s, qs;
PPATH_ARGUMENT source, destination;
ULONG i;
PWSTRING string, qstring;
f = FirstPathArgument->IsValueSet();
qf = FirstQuotedPathArgument->IsValueSet();
s = SecondPathArgument->IsValueSet();
qs = SecondQuotedPathArgument->IsValueSet();
source = NULL;
destination = NULL;
*SourcePath = NULL;
*DestinationPath = NULL;
if (f && !qf && s && !qs) {
source = FirstPathArgument;
destination = SecondPathArgument;
} else if (!f && qf && !s && qs) {
source = FirstQuotedPathArgument;
destination = SecondQuotedPathArgument;
} else if (f && qf && !s && !qs) {
string = FirstPathArgument->GetLexeme();
qstring = FirstQuotedPathArgument->GetLexeme();
for (i = 0; i < ArgLex->QueryLexemeCount(); i++) {
if (!ArgLex->GetLexemeAt(i)->Strcmp(string)) {
source = FirstPathArgument;
destination = FirstQuotedPathArgument;
break;
}
if (!ArgLex->GetLexemeAt(i)->Strcmp(qstring)) {
source = FirstQuotedPathArgument;
destination = FirstPathArgument;
break;
}
}
} else if (f && !qf && !s && !qs) {
source = FirstPathArgument;
} else if (!f && qf && !s && !qs) {
source = FirstQuotedPathArgument;
}
if (source) {
if (!(*SourcePath = NEW PATH) ||
!(*SourcePath)->Initialize(source->GetPath(),
VerboseArgument->IsValueSet())) {
*SourcePath = NULL;
}
}
if (destination) {
if (!(*DestinationPath = NEW PATH) ||
!(*DestinationPath)->Initialize(destination->GetPath(),
VerboseArgument->IsValueSet())) {
*DestinationPath = NULL;
}
}
}
VOID
XCOPY::GetArgumentsCmd(
)
/*++
Routine Description:
Obtains the arguments from the Command line
Arguments:
None.
Return Value:
None.
--*/
{
ARRAY ArgArray;
PATH_ARGUMENT ProgramNameArgument;
DSTRING CmdLine;
DSTRING InvalidParms;
WCHAR Ch;
PWSTRING InvalidSwitch;
PARGUMENT_LEXEMIZER ArgLex;
//
// Prepare for parsing
//
if (//
// Initialize the arguments
//
!(CmdLine.Initialize( GetCommandLine() )) ||
!(ArgArray.Initialize( 15, 15 )) ||
!(ProgramNameArgument.Initialize( "*" )) ||
!(FirstPathArgument->Initialize( "*", FALSE )) ||
!(FirstQuotedPathArgument->Initialize( "\"*\"", FALSE )) ||
!(SecondPathArgument->Initialize( "*", FALSE)) ||
!(SecondQuotedPathArgument->Initialize( "\"*\"", FALSE)) ||
!(ArchiveArgument->Initialize( "/A" )) ||
!(DateArgument->Initialize( "/D:*" )) ||
!(OldArgument->Initialize( "/D" )) ||
!(EmptyArgument->Initialize( "/E" )) ||
!(ModifyArgument->Initialize( "/M" )) ||
!(PromptArgument->Initialize( "/P" )) ||
!(SubdirArgument->Initialize( "/S" )) ||
!(VerifyArgument->Initialize( "/V" )) ||
!(WaitArgument->Initialize( "/W" )) ||
!(HelpArgument->Initialize( "/?" )) ||
!(ContinueArgument->Initialize( "/C" )) ||
!(IntelligentArgument->Initialize( "/I" )) ||
!(VerboseArgument->Initialize( "/F" )) ||
!(HiddenArgument->Initialize( "/H" )) ||
!(ReadOnlyArgument->Initialize( "/R" )) ||
!(SilentArgument->Initialize( "/Q" )) ||
!(NoCopyArgument->Initialize( "/L" )) ||
!(StructureArgument->Initialize( "/T" )) ||
!(UpdateArgument->Initialize( "/U" )) ||
!(CopyAttrArgument->Initialize( "/K" )) ||
!(UseShortArgument->Initialize( "/N" )) ||
!(RestartableArgument->Initialize( "/Z" )) ||
!(ExclusionListArgument->Initialize("/EXCLUDE:*")) ||
!(InvalidSwitchArgument->Initialize( "/*" )) ||
//
// Put the arguments in the argument array
//
!(ArgArray.Put( &ProgramNameArgument )) ||
!(ArgArray.Put( ArchiveArgument )) ||
!(ArgArray.Put( DateArgument )) ||
!(ArgArray.Put( OldArgument )) ||
!(ArgArray.Put( EmptyArgument )) ||
!(ArgArray.Put( ModifyArgument )) ||
!(ArgArray.Put( PromptArgument )) ||
!(ArgArray.Put( SubdirArgument )) ||
!(ArgArray.Put( VerifyArgument )) ||
!(ArgArray.Put( WaitArgument )) ||
!(ArgArray.Put( HelpArgument )) ||
!(ArgArray.Put( ContinueArgument )) ||
!(ArgArray.Put( IntelligentArgument )) ||
!(ArgArray.Put( VerboseArgument )) ||
!(ArgArray.Put( HiddenArgument )) ||
!(ArgArray.Put( ReadOnlyArgument )) ||
!(ArgArray.Put( SilentArgument )) ||
!(ArgArray.Put( RestartableArgument )) ||
!(ArgArray.Put( NoCopyArgument )) ||
!(ArgArray.Put( StructureArgument )) ||
!(ArgArray.Put( UpdateArgument )) ||
!(ArgArray.Put( CopyAttrArgument )) ||
!(ArgArray.Put( UseShortArgument )) ||
!(ArgArray.Put( ExclusionListArgument )) ||
!(ArgArray.Put( InvalidSwitchArgument )) ||
!(ArgArray.Put( FirstQuotedPathArgument )) ||
!(ArgArray.Put( SecondQuotedPathArgument )) ||
!(ArgArray.Put( FirstPathArgument )) ||
!(ArgArray.Put( SecondPathArgument ))
) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR);
}
//
// Parse the arguments
//
ArgLex = ParseArguments( &CmdLine, &ArgArray );
if ( InvalidSwitchArgument->IsValueSet() ) {
InvalidSwitch = InvalidSwitchArgument->GetString();
InvalidParms.Initialize( SWITCH_CHARACTERS );
Ch = InvalidSwitch->QueryChAt(0);
if ( Ch == 'd' || Ch == 'D' ) {
Ch = InvalidSwitch->QueryChAt(1);
if ( Ch == INVALID_CHAR ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_NUMBER_PARAMETERS,
NULL,
EXIT_MISC_ERROR );
} else if ( Ch != ':' || InvalidSwitch->QueryChCount() == 2 ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_SWITCH_SWITCH,
InvalidSwitchArgument->GetLexeme(),
EXIT_MISC_ERROR );
}
} else if ( Ch == '/' ) {
Ch = InvalidSwitch->QueryChAt(1);
if ( Ch == ':' && InvalidSwitchArgument->GetString()->QueryChAt(2) == INVALID_CHAR ) {
InvalidSwitchArgument->GetLexeme()->Truncate(1);
}
}
Ch = InvalidSwitch->QueryChAt(0);
if ( InvalidParms.Strchr( Ch ) != INVALID_CHNUM ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_PARAMETER,
InvalidSwitchArgument->GetLexeme(),
EXIT_MISC_ERROR );
} else {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_SWITCH_SWITCH,
InvalidSwitchArgument->GetLexeme(),
EXIT_MISC_ERROR );
}
}
//
// Set the switches
//
_EmptySwitch = EmptyArgument->QueryFlag();
_ModifySwitch = ModifyArgument->QueryFlag();
//
// ModifySwitch implies ArchiveSwitch
//
if ( _ModifySwitch ) {
_ArchiveSwitch = TRUE;
} else {
_ArchiveSwitch = ArchiveArgument->QueryFlag();
}
//
// Set the switches
//
_PromptSwitch = PromptArgument->QueryFlag();
_SubdirSwitch = SubdirArgument->QueryFlag();
_VerifySwitch = VerifyArgument->QueryFlag();
_WaitSwitch = WaitArgument->QueryFlag();
_ContinueSwitch = ContinueArgument->QueryFlag();
_IntelligentSwitch = IntelligentArgument->QueryFlag();
_CopyIfOldSwitch = OldArgument->QueryFlag();
_VerboseSwitch = VerboseArgument->QueryFlag();
_HiddenSwitch = HiddenArgument->QueryFlag();
_ReadOnlySwitch = ReadOnlyArgument->QueryFlag();
_SilentSwitch = SilentArgument->QueryFlag();
_DontCopySwitch = NoCopyArgument->QueryFlag();
_StructureOnlySwitch= StructureArgument->QueryFlag();
_UpdateSwitch = UpdateArgument->QueryFlag();
_CopyAttrSwitch = CopyAttrArgument->QueryFlag();
_UseShortSwitch = UseShortArgument->QueryFlag();
_RestartableSwitch = RestartableArgument->QueryFlag();
HelpSwitch = HelpArgument->QueryFlag();
//
// Set the source and destination paths. Argument checking is
// done somewhere else, so it is ok. to set the source path to
// NULL here.
//
GetSourceAndDestinationPath(FirstPathArgument,
FirstQuotedPathArgument,
SecondPathArgument,
SecondQuotedPathArgument,
ArgLex,
&_SourcePath,
&_DestinationPath);
DELETE(ArgLex);
//
// Set the date argument
//
if ( DateArgument->IsValueSet() ) {
if ((_Date = NEW TIMEINFO) == NULL ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
}
_Date->Initialize( DateArgument->GetTimeInfo() );
} else {
_Date = NULL;
}
if( ExclusionListArgument->IsValueSet() ) {
InitializeExclusionList( ExclusionListArgument->GetString() );
}
}
PARGUMENT_LEXEMIZER
XCOPY::ParseArguments(
IN PWSTRING CmdLine,
OUT PARRAY ArgArray
)
/*++
Routine Description:
Parses a group of arguments
Arguments:
CmdLine - Supplies pointer to a command line to parse
ArgArray - Supplies pointer to array of arguments
Return Value:
Returns the argument lexemizer used which then needs to be freed
by the client.
Notes:
--*/
{
PARGUMENT_LEXEMIZER ArgLex;
//
// Initialize lexeme array and the lexemizer.
//
if ( !(ArgLex = NEW ARGUMENT_LEXEMIZER) ||
!(LexArray->Initialize( 9, 9 )) ||
!(ArgLex->Initialize( LexArray )) ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY,
NULL,
EXIT_MISC_ERROR );
}
//
// Set our parsing preferences
//
ArgLex->PutMultipleSwitch( "/?AMDPSEVWCIFHRQLKTUNZ" );
ArgLex->PutSwitches( "/" );
ArgLex->SetCaseSensitive( FALSE );
ArgLex->PutSeparators( " \t" );
ArgLex->PutStartQuotes( "\"" );
ArgLex->PutEndQuotes( "\"" );
ArgLex->SetAllowSwitchGlomming( TRUE );
ArgLex->SetNoSpcBetweenDstAndSwitch( TRUE );
//
// Parse the arguments
//
if ( !(ArgLex->PrepareToParse( CmdLine ))) {
DisplayMessageAndExit( XCOPY_ERROR_PARSE,
NULL,
EXIT_MISC_ERROR );
}
if ( !ArgLex->DoParsing( ArgArray ) ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_NUMBER_PARAMETERS,
NULL,
EXIT_MISC_ERROR );
}
return ArgLex;
}
VOID
XCOPY::CheckArgumentConsistency (
)
/*++
Routine Description:
Checks the consistency of the arguments
Arguments:
none
Return Value:
none
Notes:
--*/
{
PFSN_DIRECTORY DirSrc;
PFSN_DIRECTORY DirDst;
PWSTRING DevSrc;
PWSTRING DevDst;
PATH PathSrc, PathSrc1;
PATH PathDst, PathDst1;
DSTRING Slash;
if ( HelpSwitch ) {
//
// Help requested
//
Usage();
DisplayMessageAndExit( 0,
NULL,
EXIT_NORMAL );
}
//
// Make sure that we have a source path
//
if ( _SourcePath == NULL ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_NUMBER_PARAMETERS,
NULL,
EXIT_MISC_ERROR );
}
//
// The empty switch implies Subdir switch (note that DOS
// requires Subdir switch explicitly, but we are not that
// brain-damaged).
//
if ( _EmptySwitch ) {
_SubdirSwitch = TRUE;
}
//
// The StructureOnly switch imples the subdir switch
//
if ( _StructureOnlySwitch ) {
_SubdirSwitch = TRUE;
}
//
// If destination path is null, then the destination path is the
// current directory
//
if ( _DestinationPath == NULL ) {
if ( ((_DestinationPath = NEW PATH) == NULL ) ||
!_DestinationPath->Initialize( (LPWSTR)L".", TRUE ) ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
}
}
_DestinationPath->TruncateNameAtColon();
if ( !PathSrc1.Initialize( _SourcePath, TRUE ) ||
!PathDst1.Initialize( _DestinationPath, TRUE ) ||
!(DevSrc = PathSrc1.QueryDevice()) ||
!(DevDst = PathDst1.QueryDevice()) ||
!PathSrc.Initialize( DevSrc ) ||
!PathDst.Initialize( DevDst ) ||
!Slash.Initialize( "\\" ) ||
!PathSrc.AppendBase( &Slash ) ||
!PathDst.AppendBase( &Slash ) ||
!(DirSrc = SYSTEM::QueryDirectory( &PathSrc )) ||
!(DirDst = SYSTEM::QueryDirectory( &PathDst )) ) {
DisplayMessageAndExit( XCOPY_ERROR_INVALID_DRIVE, NULL, EXIT_MISC_ERROR );
}
DELETE( DevSrc );
DELETE( DevDst );
DELETE( DirSrc );
DELETE( DirDst );
}
BOOLEAN
XCOPY::AddToExclusionList(
IN PWSTRING ExclusionListFileName
)
/*++
Routine Description:
This method adds the contents of the specified file to
the exclusion list.
Arguments:
ExclusionListFileName -- Supplies the name of a file which
contains the exclusion list.
Return Value:
TRUE upon successful completion.
--*/
{
PATH ExclusionPath;
PDSTRING String;
PFSN_FILE File;
PFILE_STREAM Stream;
CHNUM Position;
DebugPtrAssert( ExclusionListFileName );
if( !ExclusionPath.Initialize( ExclusionListFileName ) ||
(File = SYSTEM::QueryFile( &ExclusionPath )) == NULL ||
(Stream = File->QueryStream( READ_ACCESS )) == NULL ) {
DisplayMessageAndExit( MSG_COMP_UNABLE_TO_READ,
ExclusionListFileName,
EXIT_MISC_ERROR );
}
while( !Stream->IsAtEnd() &&
(String = NEW DSTRING) != NULL &&
Stream->ReadLine ( String ) ) {
if( String->QueryChCount() == 0 ) {
continue;
}
// Convert the string to upper-case and remove
// trailing whitespace (blanks and tabs).
//
String->Strupr();
Position = String->QueryChCount() - 1;
while( Position != 0 &&
(String->QueryChAt( Position ) == ' ' ||
String->QueryChAt( Position ) == '\t') ) {
Position -= 1;
}
if( String->QueryChAt( Position ) != ' ' &&
String->QueryChAt( Position ) != '\t' ) {
Position++;
}
if( Position != String->QueryChCount() ) {
String->Truncate( Position );
}
if( String->QueryChCount() != 0 && !_ExclusionList->Put( String ) ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY,
NULL,
EXIT_MISC_ERROR );
}
}
DELETE( Stream );
DELETE( File );
return TRUE;
}
BOOLEAN
XCOPY::InitializeExclusionList(
IN PWSTRING ListOfFiles
)
/*++
Routine Description:
This method reads the exclusion list and initializes the
exclusion list array.
Arguments:
ListOfFiles -- Supplies a string containing a list of file
names, separated by '+' (e.g. file1+file2+file3)
Return Value:
TRUE upon successful completion.
--*/
{
DSTRING CurrentName;
CHNUM LastPosition, Position;
DebugPtrAssert( ListOfFiles );
if( (_ExclusionList = NEW STRING_ARRAY) == NULL ||
!_ExclusionList->Initialize() ||
(_Iterator = _ExclusionList->QueryIterator()) == NULL ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY, NULL, EXIT_MISC_ERROR );
}
LastPosition = 0;
while( LastPosition != ListOfFiles->QueryChCount() ) {
Position = ListOfFiles->Strchr( '+', LastPosition );
if( Position == INVALID_CHNUM ) {
Position = ListOfFiles->QueryChCount();
}
if( Position != LastPosition ) {
if( !CurrentName.Initialize( ListOfFiles,
LastPosition,
Position - LastPosition ) ) {
DisplayMessageAndExit( XCOPY_ERROR_NO_MEMORY,
NULL,
EXIT_MISC_ERROR );
}
AddToExclusionList( &CurrentName );
}
// Advance past any separators.
//
while( Position < ListOfFiles->QueryChCount() &&
ListOfFiles->QueryChAt( Position ) == '+' ) {
Position += 1;
}
LastPosition = Position;
}
return TRUE;
}