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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 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