1 /*
2 * PROJECT: ReactOS Replace Command
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Implements 'replace' command
5 * COPYRIGHT: Copyright Samuel Erdtman (samuel@erdtman.se)
6 * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7 */
8
9 #include "replace.h"
10
11 enum
12 {
13 REPLACE_ADD = 0x001, /* /A */
14 REPLACE_CONFIRM = 0x002, /* /P */
15 REPLACE_READ_ONLY = 0x004, /* /R */
16 REPLACE_SUBDIR = 0x008, /* /S */
17 REPLACE_DISK = 0x010, /* /W */
18 REPLACE_UPDATE = 0x020, /* /U */
19 };
20
21 /* just makes a print out if there is a problem with the switches */
invalid_switch(LPTSTR is)22 void invalid_switch(LPTSTR is)
23 {
24 ConOutResPrintf(STRING_REPLACE_ERROR1,is);
25 ConOutResPrintf(STRING_REPLACE_HELP3);
26 }
27
28 /* retrieves the path dependent on the input file name */
getPath(TCHAR * out,LPTSTR in)29 void getPath(TCHAR* out, LPTSTR in)
30 {
31 if (_tcslen(in) == 2 && in[1] == _T(':'))
32 GetRootPath(in,out,MAX_PATH);
33 else
34 GetFullPathName (in, MAX_PATH, out, NULL);
35 }
36
37 /* makes the replace */
replace(TCHAR source[MAX_PATH],TCHAR dest[MAX_PATH],DWORD dwFlags,BOOL * doMore)38 INT replace(TCHAR source[MAX_PATH], TCHAR dest[MAX_PATH], DWORD dwFlags, BOOL *doMore)
39 {
40 TCHAR d[MAX_PATH];
41 TCHAR s[MAX_PATH];
42 HANDLE hFileSrc, hFileDest;
43 DWORD dwAttrib, dwRead, dwWritten;
44 LPBYTE buffer;
45 BOOL bEof = FALSE;
46 FILETIME srcCreationTime, destCreationTime, srcLastAccessTime, destLastAccessTime;
47 FILETIME srcLastWriteTime, destLastWriteTime;
48 GetPathCase(source, s);
49 GetPathCase(dest, d);
50 s[0] = _totupper(s[0]);
51 d[0] = _totupper(d[0]);
52 // ConOutPrintf(_T("old-src: %s\n"), s);
53 // ConOutPrintf(_T("old-dest: %s\n"), d);
54 // ConOutPrintf(_T("src: %s\n"), source);
55 // ConOutPrintf(_T("dest: %s\n"), dest);
56
57 /* Open up the sourcefile */
58 hFileSrc = CreateFile (source, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL);
59 if (hFileSrc == INVALID_HANDLE_VALUE)
60 {
61 ConOutResPrintf(STRING_COPY_ERROR1, source);
62 return 0;
63 }
64
65 /*
66 * Get the time from source file to be used in the comparison
67 * with dest time if update switch is set.
68 */
69 GetFileTime (hFileSrc, &srcCreationTime, &srcLastAccessTime, &srcLastWriteTime);
70
71 /*
72 * Retrieve the source attributes so that they later on
73 * can be inserted in to the destination.
74 */
75 dwAttrib = GetFileAttributes (source);
76
77 if (IsExistingFile (dest))
78 {
79 /*
80 * Resets the attributes to avoid problems with read only files,
81 * checks for read only has been made earlier.
82 */
83 SetFileAttributes(dest,FILE_ATTRIBUTE_NORMAL);
84 /*
85 * Is the update flas set? The time has to be controled so that
86 * only older files are replaced.
87 */
88 if (dwFlags & REPLACE_UPDATE)
89 {
90 /* Read destination time */
91 hFileDest = CreateFile(dest, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
92 0, NULL);
93
94 if (hFileDest == INVALID_HANDLE_VALUE)
95 {
96 ConOutResPrintf(STRING_COPY_ERROR1, dest);
97 CloseHandle (hFileSrc);
98 return 0;
99 }
100
101 /* Compare time */
102 GetFileTime (hFileDest, &destCreationTime, &destLastAccessTime, &destLastWriteTime);
103 if (!((srcLastWriteTime.dwHighDateTime > destLastWriteTime.dwHighDateTime) ||
104 (srcLastWriteTime.dwHighDateTime == destLastWriteTime.dwHighDateTime &&
105 srcLastWriteTime.dwLowDateTime > destLastWriteTime.dwLowDateTime)))
106 {
107 CloseHandle (hFileSrc);
108 CloseHandle (hFileDest);
109 return 0;
110 }
111 CloseHandle (hFileDest);
112 }
113 /* Delete the old file */
114 DeleteFile (dest);
115 }
116
117 /* Check confirm flag, and take appropriate action */
118 if (dwFlags & REPLACE_CONFIRM)
119 {
120 /* Output depending on add flag */
121 if (dwFlags & REPLACE_ADD)
122 ConOutResPrintf(STRING_REPLACE_HELP9, dest);
123 else
124 ConOutResPrintf(STRING_REPLACE_HELP10, dest);
125 if ( !FilePromptYNA (0))
126 {
127 CloseHandle (hFileSrc);
128 return 0;
129 }
130 }
131
132 /* Output depending on add flag */
133 if (dwFlags & REPLACE_ADD)
134 ConOutResPrintf(STRING_REPLACE_HELP11, dest);
135 else
136 ConOutResPrintf(STRING_REPLACE_HELP5, dest);
137
138 /* Make sure source and destination is not the same */
139 if (!_tcscmp(s, d))
140 {
141 ConOutResPrintf(STRING_REPLACE_ERROR7);
142 CloseHandle (hFileSrc);
143 *doMore = FALSE;
144 return 0;
145 }
146
147 /* Open destination file to write to */
148 hFileDest = CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
149 if (hFileDest == INVALID_HANDLE_VALUE)
150 {
151 CloseHandle (hFileSrc);
152 ConOutResPrintf(STRING_REPLACE_ERROR7);
153 *doMore = FALSE;
154 return 0;
155 }
156
157 /* Get buffer for the copy process */
158 buffer = VirtualAlloc(NULL, BUFF_SIZE, MEM_COMMIT, PAGE_READWRITE);
159 if (buffer == NULL)
160 {
161 CloseHandle (hFileDest);
162 CloseHandle (hFileSrc);
163 ConOutResPrintf(STRING_ERROR_OUT_OF_MEMORY);
164 return 0;
165 }
166
167 /* Put attribute and time to the new destination file */
168 SetFileAttributes (dest, dwAttrib);
169 SetFileTime (hFileDest, &srcCreationTime, &srcLastAccessTime, &srcLastWriteTime);
170 do
171 {
172 /* Read data from source */
173 ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
174
175 /* Done? */
176 if (dwRead == 0)
177 break;
178
179 /* Write to destination file */
180 WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
181
182 /* Done! or ctrl break! */
183 if (dwWritten != dwRead || bCtrlBreak)
184 {
185 ConOutResPuts(STRING_COPY_ERROR3);
186 VirtualFree (buffer, 0, MEM_RELEASE);
187 CloseHandle (hFileDest);
188 CloseHandle (hFileSrc);
189 return 0;
190 }
191 }
192 while (!bEof);
193
194 /* Return memory and close files */
195 VirtualFree (buffer, 0, MEM_RELEASE);
196 CloseHandle (hFileDest);
197 CloseHandle (hFileSrc);
198
199 /* Return one file replaced */
200 return 1;
201 }
202
203
204 /* Function to iterate over source files and call replace for each of them */
recReplace(DWORD dwFlags,TCHAR szSrcPath[MAX_PATH],TCHAR szDestPath[MAX_PATH],BOOL * doMore)205 INT recReplace(DWORD dwFlags,
206 TCHAR szSrcPath[MAX_PATH],
207 TCHAR szDestPath[MAX_PATH],
208 BOOL *doMore)
209 {
210 TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
211 INT filesReplaced=0;
212 INT_PTR i;
213 DWORD dwAttrib = 0;
214 HANDLE hFile;
215 WIN32_FIND_DATA findBuffer;
216
217 /* Get file handle to the sourcefile(s) */
218 hFile = FindFirstFile (szSrcPath, &findBuffer);
219
220 /*
221 * Strip the paths back to the folder they are in, so that
222 * the different filenames can be added if more than one.
223 */
224 for(i = (_tcslen(szSrcPath) - 1); i > -1; i--)
225 {
226 if (szSrcPath[i] != _T('\\'))
227 szSrcPath[i] = _T('\0');
228 else
229 break;
230 }
231
232 /* Go through all the sourcefiles and copy/replace them */
233 do
234 {
235 if (bCtrlBreak)
236 return filesReplaced;
237
238 /* Problem with file handler */
239 if (hFile == INVALID_HANDLE_VALUE)
240 return filesReplaced;
241
242 /* We do not want to replace any .. . ocr directory */
243 if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
244 !_tcscmp (findBuffer.cFileName, _T(".."))||
245 findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
246 continue;
247
248 /* Add filename to destpath */
249 _tcscpy(tmpDestPath,szDestPath);
250 _tcscat (tmpDestPath, findBuffer.cFileName);
251
252 dwAttrib = GetFileAttributes(tmpDestPath);
253 /* Check add flag */
254 if (dwFlags & REPLACE_ADD)
255 {
256 if (IsExistingFile(tmpDestPath))
257 continue;
258 else
259 dwAttrib = 0;
260 }
261 else
262 {
263 if (!IsExistingFile(tmpDestPath))
264 continue;
265 }
266
267 /* Check if file is read only, if so check if that should be ignored */
268 if (dwAttrib & FILE_ATTRIBUTE_READONLY)
269 {
270 if (!(dwFlags & REPLACE_READ_ONLY))
271 {
272 ConOutResPrintf(STRING_REPLACE_ERROR5, tmpDestPath);
273 *doMore = FALSE;
274 break;
275 }
276 }
277
278 /* Add filename to sourcepath, insted of wildcards */
279 _tcscpy(tmpSrcPath,szSrcPath);
280 _tcscat (tmpSrcPath, findBuffer.cFileName);
281
282 /* Make the replace */
283 if (replace(tmpSrcPath,tmpDestPath, dwFlags, doMore))
284 {
285 filesReplaced++;
286 }
287 else if (!*doMore)
288 {
289 /* The file to be replaced was the same as the source */
290 filesReplaced = -1;
291 break;
292 }
293
294 /* Take next sourcefile if any */
295 } while(FindNextFile (hFile, &findBuffer));
296
297 FindClose(hFile);
298
299 return filesReplaced;
300 }
301
302 /* If /s switch is specifyed all subdirs has to be considered */
recFindSubDirs(DWORD dwFlags,TCHAR szSrcPath[MAX_PATH],TCHAR szDestPath[MAX_PATH],BOOL * doMore)303 INT recFindSubDirs(DWORD dwFlags,
304 TCHAR szSrcPath[MAX_PATH],
305 TCHAR szDestPath[MAX_PATH],
306 BOOL *doMore)
307 {
308 HANDLE hFile;
309 WIN32_FIND_DATA findBuffer;
310 TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
311 INT filesReplaced = 0;
312 INT_PTR i;
313
314 /*
315 * Add a wildcard to dest end so the it will be easy to iterate
316 * over all the files and directorys in the dest directory.
317 */
318 _tcscat(szDestPath, _T("*"));
319
320 /* Get the first file in the directory */
321 hFile = FindFirstFile (szDestPath, &findBuffer);
322
323 /* Remove the star added earlier to dest path */
324 for(i = (_tcslen(szDestPath) - 1); i > -1; i--)
325 {
326 if (szDestPath[i] != _T('\\'))
327 szDestPath[i] = _T('\0');
328 else
329 break;
330 }
331
332 /* Iterate over all filed directories in the dest dir */
333 do
334 {
335 /* Save the source path so that it will not be wrecked */
336 _tcscpy(tmpSrcPath,szSrcPath);
337 /* Check for reading problems */
338 if (hFile == INVALID_HANDLE_VALUE)
339 {
340 ConOutFormatMessage (GetLastError(), tmpSrcPath);
341 return filesReplaced;
342 }
343
344 /*
345 * Check if the we should enter the dir or if it is a file
346 * or . or .. if so thake the next object to process.
347 */
348 if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
349 !_tcscmp (findBuffer.cFileName, _T(".."))||
350 IsExistingFile(findBuffer.cFileName))
351 continue;
352 /* Add the destpath and the new dir path to tempDestPath */
353 _tcscpy(tmpDestPath,szDestPath);
354 _tcscat (tmpDestPath, findBuffer.cFileName);
355 /* Make sure that we have a directory */
356 if (IsExistingDirectory(tmpDestPath))
357 {
358 /* Add a \ to the end or the path */
359 if (szDestPath[_tcslen(tmpDestPath) - 1] != _T('\\'))
360 _tcscat(tmpDestPath, _T("\\"));
361 /* Call the function to replace files in the new directory */
362 filesReplaced += recReplace(dwFlags, tmpSrcPath, tmpDestPath, doMore);
363 /* If there were problems break e.g. read-only file */
364 if (!*doMore)
365 break;
366 _tcscpy(tmpSrcPath,szSrcPath);
367 /* Control the next level of subdirs */
368 filesReplaced += recFindSubDirs(dwFlags,tmpSrcPath,tmpDestPath, doMore);
369 if (!*doMore)
370 break;
371 }
372 /* Get the next handle */
373 } while(FindNextFile (hFile, &findBuffer));
374
375 FindClose(hFile);
376
377 return filesReplaced;
378 }
379
cmd_replace(INT argc,WCHAR ** argv)380 INT cmd_replace(INT argc, WCHAR **argv)
381 {
382 LPTSTR *arg;
383 INT i, filesReplaced = 0, nFiles, srcIndex = -1, destIndex = -1;
384 DWORD dwFlags = 0;
385 TCHAR szDestPath[MAX_PATH], szSrcPath[MAX_PATH], tmpSrcPath[MAX_PATH];
386 BOOL doMore = TRUE;
387
388 --argc;
389 ++argv;
390
391 /* Help wanted? */
392 if (argc == 1 && !_tcscmp(argv[0], _T("/?")))
393 {
394 ConOutResPrintf(STRING_REPLACE_HELP1);
395 return EXIT_SUCCESS;
396 }
397
398 /* Divide the argument in to an array of c-strings */
399 arg = argv;
400 nFiles = argc;
401
402 /* Read options */
403 for (i = 0; i < argc; i++)
404 {
405 if (arg[i][0] == _T('/'))
406 {
407 if (_tcslen(arg[i]) == 2)
408 {
409 switch (_totupper(arg[i][1]))
410 {
411 case _T('A'):
412 dwFlags |= REPLACE_ADD;
413 break;
414 case _T('P'):
415 dwFlags |= REPLACE_CONFIRM;
416 break;
417 case _T('R'):
418 dwFlags |= REPLACE_READ_ONLY;
419 break;
420 case _T('S'):
421 dwFlags |= REPLACE_SUBDIR;
422 break;
423 case _T('W'):
424 dwFlags |= REPLACE_DISK;
425 break;
426 case _T('U'):
427 dwFlags |= REPLACE_UPDATE;
428 break;
429 default:
430 invalid_switch(arg[i]);
431 return 11; /* Error */
432 }
433 }
434 else
435 {
436 invalid_switch(arg[i]);
437 return 11; /* Error */
438 }
439 nFiles--;
440 }
441 else
442 {
443 if (srcIndex == -1)
444 {
445 srcIndex = i;
446 }
447 else if (destIndex == -1)
448 {
449 destIndex = i;
450 }
451 else
452 {
453 invalid_switch(arg[i]);
454 return 11; /* Error */
455 }
456 }
457 }
458
459 /* See so that at least source is there */
460 if (nFiles < 1)
461 {
462 ConOutResPrintf(STRING_REPLACE_HELP2);
463 ConOutResPrintf(STRING_REPLACE_HELP3);
464 return 11; /* Error */
465 }
466
467 /* Check so that not both update and add switch is added and subdir */
468 if ((dwFlags & REPLACE_UPDATE || dwFlags & REPLACE_SUBDIR) && (dwFlags & REPLACE_ADD))
469 {
470 ConOutResPrintf(STRING_REPLACE_ERROR4);
471 ConOutResPrintf(STRING_REPLACE_HELP7);
472 return 11; /* Error */
473 }
474
475 /* If we have a destination get the full path */
476 if (destIndex != -1)
477 {
478 if (_tcslen(arg[destIndex]) == 2 && arg[destIndex][1] == ':')
479 GetRootPath(arg[destIndex],szDestPath,MAX_PATH);
480 else
481 {
482 /* Check for wildcards in destination directory */
483 if (_tcschr (arg[destIndex], _T('*')) != NULL ||
484 _tcschr (arg[destIndex], _T('?')) != NULL)
485 {
486 ConOutResPrintf(STRING_REPLACE_ERROR2,arg[destIndex]);
487 ConOutResPrintf(STRING_REPLACE_HELP3);
488 return 3; /* Error */
489 }
490 getPath(szDestPath, arg[destIndex]);
491 /* Make sure that destination exists */
492 if (!IsExistingDirectory(szDestPath))
493 {
494 ConOutResPrintf(STRING_REPLACE_ERROR2, szDestPath);
495 ConOutResPrintf(STRING_REPLACE_HELP3);
496 return 3; /* Error */
497 }
498 }
499 }
500 else
501 {
502 /* Dest is current dir */
503 GetCurrentDirectory(MAX_PATH,szDestPath);
504 }
505
506 /* Get the full source path */
507 if (!(_tcslen(arg[srcIndex]) == 2 && arg[srcIndex][1] == ':'))
508 getPath(szSrcPath, arg[srcIndex]);
509 else
510 _tcscpy(szSrcPath,arg[srcIndex]);
511
512 /* Source does not have wildcards */
513 if (_tcschr (arg[srcIndex], _T('*')) == NULL &&
514 _tcschr (arg[srcIndex], _T('?')) == NULL)
515 {
516 /* Check so that source is not a directory, because that is not allowed */
517 if (IsExistingDirectory(szSrcPath))
518 {
519 ConOutResPrintf(STRING_REPLACE_ERROR6, szSrcPath);
520 ConOutResPrintf(STRING_REPLACE_HELP3);
521 return 2; /* Error */
522 }
523 /* Check if the file exists */
524 if (!IsExistingFile(szSrcPath))
525 {
526 ConOutResPrintf(STRING_REPLACE_HELP3);
527 return 2; /* Error */
528 }
529 }
530
531 /* /w switch is set so wait for any key to be pressed */
532 if (dwFlags & REPLACE_DISK)
533 {
534 msg_pause();
535 cgetchar();
536 }
537
538 /* Add an extra \ to the destination path if needed */
539 if (szDestPath[_tcslen(szDestPath) - 1] != _T('\\'))
540 _tcscat(szDestPath, _T("\\"));
541
542 /* Save source path */
543 _tcscpy(tmpSrcPath,szSrcPath);
544 /* Replace in dest dir */
545 filesReplaced += recReplace(dwFlags, tmpSrcPath, szDestPath, &doMore);
546 /* If subdir switch is set replace in the subdirs to */
547 if (dwFlags & REPLACE_SUBDIR && doMore)
548 {
549 filesReplaced += recFindSubDirs(dwFlags, szSrcPath, szDestPath, &doMore);
550 }
551
552 /* If source == dest write no more */
553 if (filesReplaced != -1)
554 {
555 /* No files replaced */
556 if (filesReplaced==0)
557 {
558 /* Add switch dependent output */
559 if (dwFlags & REPLACE_ADD)
560 ConOutResPrintf(STRING_REPLACE_HELP7);
561 else
562 ConOutResPrintf(STRING_REPLACE_HELP3);
563 }
564 /* Some files replaced */
565 else
566 {
567 /* Add switch dependent output */
568 if (dwFlags & REPLACE_ADD)
569 ConOutResPrintf(STRING_REPLACE_HELP8, filesReplaced);
570 else
571 ConOutResPrintf(STRING_REPLACE_HELP4, filesReplaced);
572 }
573 }
574
575 /* Return memory */
576 return EXIT_SUCCESS;
577 }
578
579 static BOOL CALLBACK
CtrlHandlerRoutine(DWORD dwCtrlType)580 CtrlHandlerRoutine(DWORD dwCtrlType)
581 {
582 switch (dwCtrlType)
583 {
584 case CTRL_C_EVENT: /* Ctrl+C */
585 case CTRL_CLOSE_EVENT: /* Closing console? */
586 bCtrlBreak = TRUE;
587 return TRUE; /* Handled */
588
589 default:
590 return FALSE; /* Ignored */
591 }
592 }
593
wmain(int argc,WCHAR ** argvW)594 int wmain(int argc, WCHAR **argvW)
595 {
596 /* Handle Ctrl+C and console closing */
597 SetConsoleCtrlHandler(CtrlHandlerRoutine, TRUE);
598
599 /* Initialize the Console Standard Streams */
600 ConInitStdStreams();
601
602 return cmd_replace(argc, argvW);
603 }
604