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