mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 18:15:11 +00:00
594 lines
18 KiB
C
594 lines
18 KiB
C
/*
|
|
* PROJECT: ReactOS Command shell
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: base/shell/cmd/replace.c
|
|
* PURPOSE: Implements 'replace' cmd command
|
|
* PROGRAMMERS: Samuel Erdtman (samuel@erdtman.se)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include "precomp.h"
|
|
|
|
#ifdef INCLUDE_CMD_REPLACE
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
enum
|
|
{
|
|
REPLACE_ADD = 0x001, /* /A */
|
|
REPLACE_CONFIRM = 0x002, /* /P */
|
|
REPLACE_READ_ONLY = 0x004, /* /R */
|
|
REPLACE_SUBDIR = 0x008, /* /S */
|
|
REPLACE_DISK = 0x010, /* /W */
|
|
REPLACE_UPDATE = 0x020, /* /U */
|
|
};
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
/* just makes a print out if there is a problem with the switches */
|
|
void invalid_switch(LPTSTR is)
|
|
{
|
|
ConOutResPrintf(STRING_REPLACE_ERROR1,is);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
}
|
|
|
|
/* retrieves the path dependent on the input file name */
|
|
void getPath(TCHAR* out, LPTSTR in)
|
|
{
|
|
if (_tcslen(in) == 2 && in[1] == _T(':'))
|
|
GetRootPath(in,out,MAX_PATH);
|
|
else
|
|
GetFullPathName (in, MAX_PATH, out, NULL);
|
|
}
|
|
|
|
|
|
/* makes the replace */
|
|
INT replace(TCHAR source[MAX_PATH], TCHAR dest[MAX_PATH], DWORD dwFlags, BOOL *doMore)
|
|
{
|
|
TCHAR d[MAX_PATH];
|
|
TCHAR s[MAX_PATH];
|
|
HANDLE hFileSrc, hFileDest;
|
|
DWORD dwAttrib, dwRead, dwWritten;
|
|
LPBYTE buffer;
|
|
BOOL bEof = FALSE;
|
|
FILETIME srcCreationTime, destCreationTime, srcLastAccessTime, destLastAccessTime;
|
|
FILETIME srcLastWriteTime, destLastWriteTime;
|
|
GetPathCase(source, s);
|
|
GetPathCase(dest, d);
|
|
s[0] = _totupper(s[0]);
|
|
d[0] = _totupper(d[0]);
|
|
// ConOutPrintf(_T("old-src: %s\n"), s);
|
|
// ConOutPrintf(_T("old-dest: %s\n"), d);
|
|
// ConOutPrintf(_T("src: %s\n"), source);
|
|
// ConOutPrintf(_T("dest: %s\n"), dest);
|
|
|
|
/* Open up the sourcefile */
|
|
hFileSrc = CreateFile (source, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hFileSrc == INVALID_HANDLE_VALUE)
|
|
{
|
|
ConOutResPrintf(STRING_COPY_ERROR1, source);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get the time from source file to be used in the comparison
|
|
* with dest time if update switch is set.
|
|
*/
|
|
GetFileTime (hFileSrc, &srcCreationTime, &srcLastAccessTime, &srcLastWriteTime);
|
|
|
|
/*
|
|
* Retrieve the source attributes so that they later on
|
|
* can be inserted in to the destination.
|
|
*/
|
|
dwAttrib = GetFileAttributes (source);
|
|
|
|
if (IsExistingFile (dest))
|
|
{
|
|
/*
|
|
* Resets the attributes to avoid problems with read only files,
|
|
* checks for read only has been made earlier.
|
|
*/
|
|
SetFileAttributes(dest,FILE_ATTRIBUTE_NORMAL);
|
|
/*
|
|
* Is the update flas set? The time has to be controled so that
|
|
* only older files are replaced.
|
|
*/
|
|
if (dwFlags & REPLACE_UPDATE)
|
|
{
|
|
/* Read destination time */
|
|
hFileDest = CreateFile(dest, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
|
0, NULL);
|
|
|
|
if (hFileDest == INVALID_HANDLE_VALUE)
|
|
{
|
|
ConOutResPrintf(STRING_COPY_ERROR1, dest);
|
|
CloseHandle (hFileSrc);
|
|
return 0;
|
|
}
|
|
|
|
/* Compare time */
|
|
GetFileTime (hFileDest, &destCreationTime, &destLastAccessTime, &destLastWriteTime);
|
|
if (!((srcLastWriteTime.dwHighDateTime > destLastWriteTime.dwHighDateTime) ||
|
|
(srcLastWriteTime.dwHighDateTime == destLastWriteTime.dwHighDateTime &&
|
|
srcLastWriteTime.dwLowDateTime > destLastWriteTime.dwLowDateTime)))
|
|
{
|
|
CloseHandle (hFileSrc);
|
|
CloseHandle (hFileDest);
|
|
return 0;
|
|
}
|
|
CloseHandle (hFileDest);
|
|
}
|
|
/* Delete the old file */
|
|
DeleteFile (dest);
|
|
}
|
|
|
|
/* Check confirm flag, and take appropriate action */
|
|
if (dwFlags & REPLACE_CONFIRM)
|
|
{
|
|
/* Output depending on add flag */
|
|
if (dwFlags & REPLACE_ADD)
|
|
ConOutResPrintf(STRING_REPLACE_HELP9, dest);
|
|
else
|
|
ConOutResPrintf(STRING_REPLACE_HELP10, dest);
|
|
if ( !FilePromptYNA (0))
|
|
{
|
|
CloseHandle (hFileSrc);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Output depending on add flag */
|
|
if (dwFlags & REPLACE_ADD)
|
|
ConOutResPrintf(STRING_REPLACE_HELP11, dest);
|
|
else
|
|
ConOutResPrintf(STRING_REPLACE_HELP5, dest);
|
|
|
|
/* Make sure source and destination is not the same */
|
|
if (!_tcscmp(s, d))
|
|
{
|
|
ConOutResPaging(TRUE, STRING_REPLACE_ERROR7);
|
|
CloseHandle (hFileSrc);
|
|
*doMore = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
/* Open destination file to write to */
|
|
hFileDest = CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
|
|
if (hFileDest == INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle (hFileSrc);
|
|
ConOutResPaging(TRUE, STRING_REPLACE_ERROR7);
|
|
*doMore = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
/* Get buffer for the copy process */
|
|
buffer = VirtualAlloc(NULL, BUFF_SIZE, MEM_COMMIT, PAGE_READWRITE);
|
|
if (buffer == NULL)
|
|
{
|
|
CloseHandle (hFileDest);
|
|
CloseHandle (hFileSrc);
|
|
ConOutResPaging(TRUE, STRING_ERROR_OUT_OF_MEMORY);
|
|
return 0;
|
|
}
|
|
|
|
/* Put attribute and time to the new destination file */
|
|
SetFileAttributes (dest, dwAttrib);
|
|
SetFileTime (hFileDest, &srcCreationTime, &srcLastAccessTime, &srcLastWriteTime);
|
|
do
|
|
{
|
|
/* Read data from source */
|
|
ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
|
|
|
|
/* Done? */
|
|
if (dwRead == 0)
|
|
break;
|
|
|
|
/* Write to destination file */
|
|
WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
|
|
|
|
/* Done! or ctrl break! */
|
|
if (dwWritten != dwRead || CheckCtrlBreak(BREAK_INPUT))
|
|
{
|
|
ConOutResPuts(STRING_COPY_ERROR3);
|
|
VirtualFree (buffer, 0, MEM_RELEASE);
|
|
CloseHandle (hFileDest);
|
|
CloseHandle (hFileSrc);
|
|
nErrorLevel = 1;
|
|
return 0;
|
|
}
|
|
}
|
|
while (!bEof);
|
|
|
|
/* Return memory and close files */
|
|
VirtualFree (buffer, 0, MEM_RELEASE);
|
|
CloseHandle (hFileDest);
|
|
CloseHandle (hFileSrc);
|
|
|
|
/* Return one file replaced */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Function to iterate over source files and call replace for each of them */
|
|
INT recReplace(DWORD dwFlags,
|
|
TCHAR szSrcPath[MAX_PATH],
|
|
TCHAR szDestPath[MAX_PATH],
|
|
BOOL *doMore)
|
|
{
|
|
TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
|
|
INT filesReplaced=0;
|
|
INT_PTR i;
|
|
DWORD dwAttrib = 0;
|
|
HANDLE hFile;
|
|
WIN32_FIND_DATA findBuffer;
|
|
|
|
/* Get file handle to the sourcefile(s) */
|
|
hFile = FindFirstFile (szSrcPath, &findBuffer);
|
|
|
|
/*
|
|
* Strip the paths back to the folder they are in, so that
|
|
* the different filenames can be added if more than one.
|
|
*/
|
|
for(i = (_tcslen(szSrcPath) - 1); i > -1; i--)
|
|
{
|
|
if (szSrcPath[i] != _T('\\'))
|
|
szSrcPath[i] = _T('\0');
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* Go through all the sourcefiles and copy/replace them */
|
|
do
|
|
{
|
|
if (CheckCtrlBreak(BREAK_INPUT))
|
|
{
|
|
return filesReplaced;
|
|
}
|
|
|
|
/* Problem with file handler */
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
return filesReplaced;
|
|
|
|
/* We do not want to replace any .. . ocr directory */
|
|
if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
|
|
!_tcscmp (findBuffer.cFileName, _T(".."))||
|
|
findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
continue;
|
|
|
|
/* Add filename to destpath */
|
|
_tcscpy(tmpDestPath,szDestPath);
|
|
_tcscat (tmpDestPath, findBuffer.cFileName);
|
|
|
|
dwAttrib = GetFileAttributes(tmpDestPath);
|
|
/* Check add flag */
|
|
if (dwFlags & REPLACE_ADD)
|
|
{
|
|
if (IsExistingFile(tmpDestPath))
|
|
continue;
|
|
else
|
|
dwAttrib = 0;
|
|
}
|
|
else
|
|
{
|
|
if (!IsExistingFile(tmpDestPath))
|
|
continue;
|
|
}
|
|
|
|
/* Check if file is read only, if so check if that should be ignored */
|
|
if (dwAttrib & FILE_ATTRIBUTE_READONLY)
|
|
{
|
|
if (!(dwFlags & REPLACE_READ_ONLY))
|
|
{
|
|
ConOutResPrintf(STRING_REPLACE_ERROR5, tmpDestPath);
|
|
*doMore = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Add filename to sourcepath, insted of wildcards */
|
|
_tcscpy(tmpSrcPath,szSrcPath);
|
|
_tcscat (tmpSrcPath, findBuffer.cFileName);
|
|
|
|
/* Make the replace */
|
|
if (replace(tmpSrcPath,tmpDestPath, dwFlags, doMore))
|
|
{
|
|
filesReplaced++;
|
|
}
|
|
else if (!*doMore)
|
|
{
|
|
/* The file to be replaced was the same as the source */
|
|
filesReplaced = -1;
|
|
break;
|
|
}
|
|
|
|
/* Take next sourcefile if any */
|
|
} while(FindNextFile (hFile, &findBuffer));
|
|
|
|
FindClose(hFile);
|
|
|
|
return filesReplaced;
|
|
}
|
|
|
|
/* If /s switch is specifyed all subdirs has to be considered */
|
|
INT recFindSubDirs(DWORD dwFlags,
|
|
TCHAR szSrcPath[MAX_PATH],
|
|
TCHAR szDestPath[MAX_PATH],
|
|
BOOL *doMore)
|
|
{
|
|
HANDLE hFile;
|
|
WIN32_FIND_DATA findBuffer;
|
|
TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
|
|
INT filesReplaced = 0;
|
|
INT_PTR i;
|
|
|
|
/*
|
|
* Add a wildcard to dest end so the it will be easy to iterate
|
|
* over all the files and directorys in the dest directory.
|
|
*/
|
|
_tcscat(szDestPath, _T("*"));
|
|
|
|
/* Get the first file in the directory */
|
|
hFile = FindFirstFile (szDestPath, &findBuffer);
|
|
|
|
/* Remove the star added earlier to dest path */
|
|
for(i = (_tcslen(szDestPath) - 1); i > -1; i--)
|
|
{
|
|
if (szDestPath[i] != _T('\\'))
|
|
szDestPath[i] = _T('\0');
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* Iterate over all filed directories in the dest dir */
|
|
do
|
|
{
|
|
/* Save the source path so that it will not be wrecked */
|
|
_tcscpy(tmpSrcPath,szSrcPath);
|
|
/* Check for reading problems */
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
ConOutFormatMessage (GetLastError(), tmpSrcPath);
|
|
return filesReplaced;
|
|
}
|
|
|
|
/*
|
|
* Check if the we should enter the dir or if it is a file
|
|
* or . or .. if so thake the next object to process.
|
|
*/
|
|
if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
|
|
!_tcscmp (findBuffer.cFileName, _T(".."))||
|
|
IsExistingFile(findBuffer.cFileName))
|
|
continue;
|
|
/* Add the destpath and the new dir path to tempDestPath */
|
|
_tcscpy(tmpDestPath,szDestPath);
|
|
_tcscat (tmpDestPath, findBuffer.cFileName);
|
|
/* Make sure that we have a directory */
|
|
if (IsExistingDirectory(tmpDestPath))
|
|
{
|
|
/* Add a \ to the end or the path */
|
|
if (szDestPath[_tcslen(tmpDestPath) - 1] != _T('\\'))
|
|
_tcscat(tmpDestPath, _T("\\"));
|
|
/* Call the function to replace files in the new directory */
|
|
filesReplaced += recReplace(dwFlags, tmpSrcPath, tmpDestPath, doMore);
|
|
/* If there were problems break e.g. read-only file */
|
|
if (!*doMore)
|
|
break;
|
|
_tcscpy(tmpSrcPath,szSrcPath);
|
|
/* Control the next level of subdirs */
|
|
filesReplaced += recFindSubDirs(dwFlags,tmpSrcPath,tmpDestPath, doMore);
|
|
if (!*doMore)
|
|
break;
|
|
}
|
|
/* Get the next handle */
|
|
} while(FindNextFile (hFile, &findBuffer));
|
|
|
|
FindClose(hFile);
|
|
|
|
return filesReplaced;
|
|
}
|
|
|
|
INT cmd_replace (LPTSTR param)
|
|
{
|
|
LPTSTR *arg;
|
|
INT argc, i,filesReplaced = 0, nFiles, srcIndex = -1, destIndex = -1;
|
|
DWORD dwFlags = 0;
|
|
TCHAR szDestPath[MAX_PATH], szSrcPath[MAX_PATH], tmpSrcPath[MAX_PATH];
|
|
BOOL doMore = TRUE;
|
|
|
|
/* Help wanted? */
|
|
if (!_tcsncmp (param, _T("/?"), 2))
|
|
{
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP1);
|
|
return 0;
|
|
}
|
|
|
|
/* Divide the argument in to an array of c-strings */
|
|
arg = split (param, &argc, FALSE, FALSE);
|
|
nFiles = argc;
|
|
|
|
/* Read options */
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
if (arg[i][0] == _T('/'))
|
|
{
|
|
if (_tcslen(arg[i]) == 2)
|
|
{
|
|
switch (_totupper(arg[i][1]))
|
|
{
|
|
case _T('A'):
|
|
dwFlags |= REPLACE_ADD;
|
|
break;
|
|
case _T('P'):
|
|
dwFlags |= REPLACE_CONFIRM;
|
|
break;
|
|
case _T('R'):
|
|
dwFlags |= REPLACE_READ_ONLY;
|
|
break;
|
|
case _T('S'):
|
|
dwFlags |= REPLACE_SUBDIR;
|
|
break;
|
|
case _T('W'):
|
|
dwFlags |= REPLACE_DISK;
|
|
break;
|
|
case _T('U'):
|
|
dwFlags |= REPLACE_UPDATE;
|
|
break;
|
|
default:
|
|
invalid_switch(arg[i]);
|
|
freep(arg);
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
invalid_switch(arg[i]);
|
|
freep(arg);
|
|
return 0;
|
|
}
|
|
nFiles--;
|
|
}
|
|
else
|
|
{
|
|
if (srcIndex == -1)
|
|
{
|
|
srcIndex = i;
|
|
}
|
|
else if (destIndex == -1)
|
|
{
|
|
destIndex = i;
|
|
}
|
|
else
|
|
{
|
|
invalid_switch(arg[i]);
|
|
freep(arg);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* See so that at least source is there */
|
|
if (nFiles < 1)
|
|
{
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP2);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
/* Check so that not both update and add switch is added and subdir */
|
|
if ((dwFlags & REPLACE_UPDATE || dwFlags & REPLACE_SUBDIR) && (dwFlags & REPLACE_ADD))
|
|
{
|
|
ConOutResPaging(TRUE,STRING_REPLACE_ERROR4);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP7);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
|
|
/* If we have a destination get the full path */
|
|
if (destIndex != -1)
|
|
{
|
|
if (_tcslen(arg[destIndex]) == 2 && arg[destIndex][1] == ':')
|
|
GetRootPath(arg[destIndex],szDestPath,MAX_PATH);
|
|
else
|
|
{
|
|
/* Check for wildcards in destination directory */
|
|
if (_tcschr (arg[destIndex], _T('*')) != NULL ||
|
|
_tcschr (arg[destIndex], _T('?')) != NULL)
|
|
{
|
|
ConOutResPrintf(STRING_REPLACE_ERROR2,arg[destIndex]);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
getPath(szDestPath, arg[destIndex]);
|
|
/* Make sure that destination exists */
|
|
if (!IsExistingDirectory(szDestPath))
|
|
{
|
|
ConOutResPrintf(STRING_REPLACE_ERROR2, szDestPath);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Dest is current dir */
|
|
GetCurrentDirectory(MAX_PATH,szDestPath);
|
|
}
|
|
|
|
/* Get the full source path */
|
|
if (!(_tcslen(arg[srcIndex]) == 2 && arg[srcIndex][1] == ':'))
|
|
getPath(szSrcPath, arg[srcIndex]);
|
|
else
|
|
_tcscpy(szSrcPath,arg[srcIndex]);
|
|
|
|
/* Source does not have wildcards */
|
|
if (_tcschr (arg[srcIndex], _T('*')) == NULL &&
|
|
_tcschr (arg[srcIndex], _T('?')) == NULL)
|
|
{
|
|
/* Check so that source is not a directory, because that is not allowed */
|
|
if (IsExistingDirectory(szSrcPath))
|
|
{
|
|
ConOutResPrintf(STRING_REPLACE_ERROR6, szSrcPath);
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
/* Check if the file exists */
|
|
if (!IsExistingFile(szSrcPath))
|
|
{
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
}
|
|
/* /w switch is set so wait for any key to be pressed */
|
|
if (dwFlags & REPLACE_DISK)
|
|
{
|
|
msg_pause();
|
|
cgetchar();
|
|
}
|
|
|
|
/* Add an extra \ to the destination path if needed */
|
|
if (szDestPath[_tcslen(szDestPath) - 1] != _T('\\'))
|
|
_tcscat(szDestPath, _T("\\"));
|
|
|
|
/* Save source path */
|
|
_tcscpy(tmpSrcPath,szSrcPath);
|
|
/* Replace in dest dir */
|
|
filesReplaced += recReplace(dwFlags, tmpSrcPath, szDestPath, &doMore);
|
|
/* If subdir switch is set replace in the subdirs to */
|
|
if (dwFlags & REPLACE_SUBDIR && doMore)
|
|
{
|
|
filesReplaced += recFindSubDirs(dwFlags, szSrcPath, szDestPath, &doMore);
|
|
}
|
|
|
|
/* If source == dest write no more */
|
|
if (filesReplaced != -1)
|
|
{
|
|
/* No files replaced */
|
|
if (filesReplaced==0)
|
|
{
|
|
/* Add switch dependent output */
|
|
if (dwFlags & REPLACE_ADD)
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP7);
|
|
else
|
|
ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
|
|
}
|
|
/* Some files replaced */
|
|
else
|
|
{
|
|
/* Add switch dependent output */
|
|
if (dwFlags & REPLACE_ADD)
|
|
ConOutResPrintf(STRING_REPLACE_HELP8, filesReplaced);
|
|
else
|
|
ConOutResPrintf(STRING_REPLACE_HELP4, filesReplaced);
|
|
}
|
|
}
|
|
/* Return memory */
|
|
freep(arg);
|
|
return 1;
|
|
}
|
|
#endif /* INCLUDE_CMD_REPLACE */
|