windows-nt/Source/XPSP1/NT/base/fs/utils/replace/replace.cxx
2020-09-26 16:20:57 +08:00

961 lines
21 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1990-2000 Microsoft Corporation
Module Name:
Replace
Abstract:
Replace utility
Author:
Ramon Juan San Andres (ramonsa) 01-May-1991
Revision History:
--*/
#include "ulib.hxx"
#include "arrayit.hxx"
#include "dir.hxx"
#include "filter.hxx"
#include "file.hxx"
#include "fsnode.hxx"
#include "stream.hxx"
#include "substrng.hxx"
#include "system.hxx"
#include "replace.hxx"
//
// Pattern that matches all files in a directory
//
#define MATCH_ALL_FILES "*.*"
#define CTRL_C (WCHAR)3
//
// Size of buffers to hold path strings
//
#define INITIAL_PATHSTRING_BUFFER_SIZE MAX_PATH
VOID __cdecl
main (
)
/*++
Routine Description:
Main function of the Replace utility
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
//
// Initialize stuff
//
DEFINE_CLASS_DESCRIPTOR( REPLACE );
//
// Now do the replacement
//
{
REPLACE Replace;
//
// Initialize the Replace object.
//
Replace.Initialize();
//
// Do our thing
//
Replace.DoReplace();
}
}
DEFINE_CONSTRUCTOR( REPLACE, PROGRAM );
BOOLEAN
REPLACE::Initialize (
)
/*++
Routine Description:
Initializes the REPLACE object
Arguments:
None.
Return Value:
TRUE if initialized, FALSE otherwise
Notes:
--*/
{
//
// Initialize program object
//
PROGRAM::Initialize( REPLACE_MESSAGE_USAGE );
//
// Allocate global structures and initialize them
//
InitializeThings();
//
// Parse the arguments
//
SetArguments();
return TRUE;
}
REPLACE::~REPLACE (
)
/*++
Routine Description:
Destructs a REPLACE object
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
//
// Deallocate the global structures previously allocated
//
DeallocateThings();
//
// Exit without error
//
DisplayMessageAndExit( 0, NULL, EXIT_NORMAL );
}
VOID
REPLACE::InitializeThings (
)
/*++
Routine Description:
Initializes the global variables that need initialization
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
//
// Initialize the path string buffers
//
_PathString1 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE );
_PathString2 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE );
_PathString1Size = _PathString2Size = INITIAL_PATHSTRING_BUFFER_SIZE;
_Keyboard = NEW KEYBOARD;
if ( !_PathString1 || !_PathString2 || !_Keyboard ) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
}
//
// initialize the keyboard and set ctrl-c handling
//
_Keyboard->Initialize();
_Keyboard->EnableBreakHandling();
//
// Initialize our data
//
_SourcePath = NULL;
_DestinationPath = NULL;
_FilesAdded = 0;
_FilesReplaced = 0;
_SourceDirectory = NULL;
_Pattern = NULL;
_FilesInSrc = NULL;
_AddSwitch = FALSE; // use by DisplayMessageAndExit before any
// of those boolean _*Switch is being initialized
}
VOID
REPLACE::DeallocateThings (
)
/*++
Routine Description:
Deallocates the stuff that was initialized in InitializeThings()
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
DELETE( _FilesInSrc );
DELETE( _SourceDirectory );
DELETE( _Pattern );
DELETE( _Keyboard );
FREE( _PathString1 );
FREE( _PathString2 );
}
BOOLEAN
REPLACE::DoReplace (
)
/*++
Routine Description:
This is the function that performs the Replace.
Arguments:
None.
Return Value:
TRUE
Notes:
--*/
{
PFSN_DIRECTORY DestinationDirectory;
FSN_FILTER Filter;
WCHAR Char;
//
// Get the source directory object and the pattern that we will use
// for file matching.
//
GetDirectoryAndPattern( _SourcePath, &_SourceDirectory, &_Pattern );
DebugPtrAssert( _SourceDirectory );
DebugPtrAssert( _Pattern );
//
// Get the destination directory
//
GetDirectory( _DestinationPath, &DestinationDirectory );
DebugPtrAssert( DestinationDirectory );
//
// Wait if requested
//
if ( _WaitSwitch ) {
DisplayMessage( REPLACE_MESSAGE_PRESS_ANY_KEY );
AbortIfCtrlC();
//
// All input is in raw mode.
//
_Keyboard->DisableLineMode();
GetStandardInput()->ReadChar( &Char );
_Keyboard->EnableLineMode();
GetStandardOutput()->WriteChar( Char );
GetStandardOutput()->WriteChar( (WCHAR)'\r');
GetStandardOutput()->WriteChar( (WCHAR)'\n');
//
// Check for ctrl-c
//
if ( Char == CTRL_C ) {
exit ( EXIT_PATH_NOT_FOUND );
}
}
//
// Get an array containing all the files in the source directory
// that match the pattern.
//
// This is so that Replacer() does not have to get the same
// information over and over when the Subdir switch is set.
//
_FilesInSrc = GetFileArray( _SourceDirectory, _Pattern );
DebugPtrAssert( _FilesInSrc );
if ( _SubdirSwitch ) {
//
// First, replace the files in the directory specified
//
Replacer( this, DestinationDirectory, NULL );
Filter.Initialize();
Filter.SetAttributes( (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
//
// Now traverse the destination directory, calling the
// replacer function for each subdirectory.
//
DestinationDirectory->Traverse( this,
&Filter,
NULL,
REPLACE::Replacer );
} else {
//
// Call the replace function, which takes care of replacements
//
Replacer(this, DestinationDirectory, NULL );
}
DELETE( DestinationDirectory );
return TRUE;
}
VOID
REPLACE::GetDirectoryAndPattern(
IN PPATH Path,
OUT PFSN_DIRECTORY *Directory,
OUT PWSTRING *Pattern
)
/*++
Routine Description:
Given a path, this function obtains a directory object and a pattern.
Normally, the pattern is the filename portion of the path, but if the
entire path refers to a directory, then the pattern is "*.*"
Arguments:
Path - Supplies pointer to path
Directory - Supplies pointer to pointer to directory
Pattern - Supplies pointer to pointer to pattern
Return Value:
TRUE
Notes:
--*/
{
PATH TmpPath;
PWSTRING Name;
PFSN_DIRECTORY Dir;
PWSTRING Ptrn;
DebugAssert( Path );
DebugAssert( Directory );
DebugAssert( Pattern );
//
// If the name passed is a directory, it is an error.
// Otherwise, split the path into Directory and Pattern
// portions.
//
Dir = SYSTEM::QueryDirectory( Path );
if ( Dir ||
(Name = Path->QueryName()) == NULL ||
(Ptrn = Name->QueryString()) == NULL ) {
DisplayMessageAndExit( REPLACE_ERROR_NO_FILES_FOUND,
Path->GetPathString(),
EXIT_FILE_NOT_FOUND );
} else {
// We're finished with Name.
//
DELETE( Name );
//
// Get the directory
//
TmpPath.Initialize( Path, TRUE );
TmpPath.TruncateBase();
Dir = SYSTEM::QueryDirectory( &TmpPath );
if ( !Dir ) {
DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND,
Path->GetPathString(),
EXIT_PATH_NOT_FOUND );
}
*Directory = Dir;
*Pattern = Ptrn;
}
}
VOID
REPLACE::GetDirectory(
IN PCPATH Path,
OUT PFSN_DIRECTORY *Directory
)
/*++
Routine Description:
Makes a directory out of a path.
Arguments:
Path - Supplies pointer to path
Directory - Supplies pointer to pointer to directory
Return Value:
TRUE
Notes:
--*/
{
PFSN_DIRECTORY Dir;
if ( !(Dir = SYSTEM::QueryDirectory( Path )) ) {
DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND,
Path->GetPathString(),
EXIT_PATH_NOT_FOUND );
}
*Directory = Dir;
}
PARRAY
REPLACE::GetFileArray(
IN PFSN_DIRECTORY Directory,
IN PWSTRING Pattern
)
/*++
Routine Description:
Gets an array of those files in a directory matching a pattern.
Arguments:
Directory - Supplies pointer to directory
Pattern - Supplies pointer to pattern
Return Value:
Pointer to the array of files
Notes:
--*/
{
PARRAY Array;
FSN_FILTER Filter;
DebugPtrAssert( Directory );
DebugPtrAssert( Pattern );
Filter.Initialize();
Filter.SetFileName( Pattern );
Filter.SetAttributes( (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
Array = Directory->QueryFsnodeArray( &Filter );
DebugPtrAssert( Array );
return Array;
}
BOOLEAN
REPLACE::Replacer (
IN PVOID This,
IN OUT PFSNODE DirectoryNode,
IN PPATH DummyPath
)
/*++
Routine Description:
This is the heart of Replace. Given a destination directory, it
performs the replacement/additions according to the global switches
and the SourceDirectory and Pattern.
Arguments:
This - Supplies pointer to the REPLACE object
Node - Supplies pointer to the directory node.
DummyPath - Required by FSN_DIRECTORY::Traverse(), must be
NULL.
Return Value:
BOOLEAN - TRUE if operation successful.
FALSE otherwise
Notes:
--*/
{
DebugAssert( DummyPath == NULL );
DebugAssert( DirectoryNode->IsDirectory() );
((PREPLACE)This)->AbortIfCtrlC();
if ( ((PREPLACE)This)->_AddSwitch ) {
return ((PREPLACE)This)->AddFiles( (PFSN_DIRECTORY)DirectoryNode );
} else {
return ((PREPLACE)This)->ReplaceFiles( (PFSN_DIRECTORY)DirectoryNode );
}
}
BOOLEAN
REPLACE::AddFiles (
IN OUT PFSN_DIRECTORY DestinationDirectory
)
/*++
Routine Description:
Adds those files from the SourceDirectory that match Pattern to the
DestinationDirectory. The array of files is already in the
FilesInSrc array.
Arguments:
DestinationDirectory - Supplies pointer to destination
directory.
Return Value:
BOOLEAN - TRUE if operation successful.
FALSE otherwise
Notes:
--*/
{
PARRAY_ITERATOR Iterator;
PFSN_FILE File;
PFSN_FILE FileToCreate;
PATH DestinationPath;
PWSTRING Name;
DebugPtrAssert( DestinationDirectory );
DebugPtrAssert( _FilesInSrc );
//
// Get destination path
//
DestinationPath.Initialize( DestinationDirectory->GetPath() );
//
// Obtain an iterator for going thru the files
//
Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( );
if (Iterator == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
//
// For each file in the array, see if it exists in the destination
// directory, and if it does not, then copy it.
//
while ( File = (PFSN_FILE)Iterator->GetNext() ) {
DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
Name = File->QueryName();
if (Name == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
//
// Form the path in the target file
//
DestinationPath.AppendBase( Name );
DELETE( Name );
//
// See if the file exists
//
FileToCreate = SYSTEM::QueryFile( &DestinationPath );
//
// If the file does not exist, then it has to be added
//
if ( !FileToCreate ) {
if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_ADD_YES_NO, &DestinationPath ) ) {
DisplayMessage( REPLACE_MESSAGE_ADDING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
CopyTheFile( File->GetPath(), &DestinationPath );
_FilesAdded++;
}
}
DELETE( FileToCreate );
//
// Set the destination path back to what it originally was
// ( i.e. directory specification, no file ).
//
DestinationPath.TruncateBase();
}
DELETE( Iterator );
return TRUE;
}
BOOLEAN
REPLACE::ReplaceFiles (
IN OUT PFSN_DIRECTORY DestinationDirectory
)
/*++
Routine Description:
Replaces those files in the DestinationDirectory that match Pattern
by the corresponding files in SourceDirectory.
Arguments:
DestinationDirectory - Supplies pointer to destination
directory.
Return Value:
BOOLEAN - TRUE if operation successful.
FALSE otherwise
Notes:
--*/
{
PARRAY_ITERATOR Iterator;
PFSN_FILE File;
PFSN_FILE FileToReplace;
PATH DestinationPath;
PWSTRING Name;
PTIMEINFO TimeSrc;
PTIMEINFO TimeDst;
BOOLEAN Proceed = TRUE;
DebugPtrAssert( DestinationDirectory );
DebugPtrAssert( _FilesInSrc );
//
// Get destination path
//
DestinationPath.Initialize( DestinationDirectory->GetPath() );
//
// Obtain an iterator for going thru the files
//
Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( );
if (Iterator == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
//
// For each file in the array, see if it exists in the destination
// directory, and if it does, replace it
//
while ( File = (PFSN_FILE)Iterator->GetNext() ) {
AbortIfCtrlC();
DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
Name = File->QueryName();
if (Name == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
//
// Form the path in the target file
//
DestinationPath.AppendBase( Name );
DELETE( Name );
//
// See if the file exists
//
FileToReplace = SYSTEM::QueryFile( &DestinationPath );
if ( FileToReplace ) {
//
// If the CompareTime switch is set, then we only proceed if
// the destination file is older than the source file.
//
if ( _CompareTimeSwitch ) {
TimeSrc = File->QueryTimeInfo();
TimeDst = FileToReplace->QueryTimeInfo();
if (TimeSrc == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
if (TimeDst == NULL) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
return FALSE; // help lint
}
Proceed = *TimeDst < *TimeSrc;
DELETE( TimeSrc );
DELETE( TimeDst );
}
if ( Proceed ) {
//
// We replace the file if it is NOT read-only
// (unless the ReadOnly switch is set )
//
if ( _ReadOnlySwitch || !(FileToReplace->IsReadOnly()) ) {
if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_REPLACE_YES_NO, &DestinationPath ) ) {
DisplayMessage( REPLACE_MESSAGE_REPLACING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
//
// If the file is read-only, we reset the read-only attribute
// before copying.
//
if ( FileToReplace->IsReadOnly() ) {
FileToReplace->ResetReadOnlyAttribute();
}
CopyTheFile( File->GetPath(), &DestinationPath );
_FilesReplaced++;
}
} else {
//
// The file is read-only but the ReadOnly flag was
// not set, we error out.
//
DisplayMessageAndExit( REPLACE_ERROR_ACCESS_DENIED,
DestinationPath.GetPathString(),
EXIT_ACCESS_DENIED );
}
}
}
DELETE( FileToReplace );
//
// Set the destination path back to what it originally was
// ( i.e. directory specification, no file name part ).
//
DestinationPath.TruncateBase();
}
DELETE( Iterator );
return TRUE;
}
BOOLEAN
REPLACE::Prompt (
IN MSGID MessageId,
IN PCPATH Path
)
/*++
Routine Description:
Gets confirmation from the user about a file to be added/replaced
Arguments:
MessageId - Supplies the Id of the message to use for prompting
Path - Supplies path to use as parameter for the message.
Return Value:
BOOLEAN - TRUE if the user confirmed the add/replace
FALSE otherwise
--*/
{
DisplayMessage( MessageId, NORMAL_MESSAGE, "%W", Path->GetPathString() );
return _Message.IsYesResponse();
}
BOOLEAN
REPLACE::CopyTheFile (
IN PCPATH SrcPath,
IN PCPATH DstPath
)
/*++
Routine Description:
Copies a file
Arguments:
SrcPath - Supplies path of source file
DstFile - Supplies path of destination file
Return Value:
BOOLEAN - TRUE
--*/
{
ULONG Size;
DebugPtrAssert( SrcPath );
DebugPtrAssert( DstPath );
//
// Make sure that the buffers are big enough to hold the
// paths
//
Size = (SrcPath->GetPathString()->QueryChCount() + 1) * 2;
if ( Size > _PathString1Size ) {
_PathString1 = (LPWSTR)REALLOC( _PathString1, (unsigned int)Size );
DebugPtrAssert( _PathString1 );
_PathString1Size = Size;
}
Size = (DstPath->GetPathString()->QueryChCount() + 1) * 2;
if ( Size > _PathString2Size ) {
_PathString2 = (LPWSTR)REALLOC( _PathString2, (unsigned int)Size );
DebugPtrAssert( _PathString2 );
_PathString2Size = Size;
}
if ( !_PathString1 || !_PathString2 ) {
DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
}
//
// Convert the paths to LPWSTR so that we can call CopyFile()
//
SrcPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString1, _PathString1Size/sizeof(WCHAR) );
DstPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString2, _PathString2Size/sizeof(WCHAR) );
//
// Now do the copy
//
if ( !CopyFile( _PathString1, _PathString2, FALSE ) ) {
ExitWithError( GetLastError() );
}
return TRUE;
}