Sync with trunk r63283
[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 /* retrives the pathe dependen om 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 probles 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 (hFileSrc == INVALID_HANDLE_VALUE)
104 {
105 ConOutResPrintf(STRING_COPY_ERROR1, dest);
106 return 0;
107 }
108
109 /* Compare time */
110 GetFileTime (hFileDest, &destCreationTime, &destLastAccessTime, &destLastWriteTime);
111 if (!((srcLastWriteTime.dwHighDateTime > destLastWriteTime.dwHighDateTime) ||
112 (srcLastWriteTime.dwHighDateTime == destLastWriteTime.dwHighDateTime &&
113 srcLastWriteTime.dwLowDateTime > destLastWriteTime.dwLowDateTime)))
114 {
115 CloseHandle (hFileSrc);
116 CloseHandle (hFileDest);
117 return 0;
118 }
119 CloseHandle (hFileDest);
120 }
121 /* Delete the old file */
122 DeleteFile (dest);
123 }
124
125 /* Check confirm flag, and take appropriate action */
126 if (dwFlags & REPLACE_CONFIRM)
127 {
128 /* Output depending on add flag */
129 if (dwFlags & REPLACE_ADD)
130 ConOutResPrintf(STRING_REPLACE_HELP9, dest);
131 else
132 ConOutResPrintf(STRING_REPLACE_HELP10, dest);
133 if ( !FilePromptYNA (0))
134 return 0;
135 }
136
137 /* Output depending on add flag */
138 if (dwFlags & REPLACE_ADD)
139 ConOutResPrintf(STRING_REPLACE_HELP11, dest);
140 else
141 ConOutResPrintf(STRING_REPLACE_HELP5, dest);
142
143 /* Make sure source and destination is not the same */
144 if (!_tcscmp(s, d))
145 {
146 ConOutResPaging(TRUE, STRING_REPLACE_ERROR7);
147 CloseHandle (hFileSrc);
148 *doMore = FALSE;
149 return 0;
150 }
151
152 /* Open destination file to write to */
153 hFileDest = CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
154 if (hFileDest == INVALID_HANDLE_VALUE)
155 {
156 CloseHandle (hFileSrc);
157 ConOutResPaging(TRUE, STRING_REPLACE_ERROR7);
158 *doMore = FALSE;
159 return 0;
160 }
161
162 /* Get buffer for the copy process */
163 buffer = VirtualAlloc(NULL, BUFF_SIZE, MEM_COMMIT, PAGE_READWRITE);
164 if (buffer == NULL)
165 {
166 CloseHandle (hFileDest);
167 CloseHandle (hFileSrc);
168 ConOutResPaging(TRUE, STRING_ERROR_OUT_OF_MEMORY);
169 return 0;
170 }
171
172 /* Put attribute and time to the new destination file */
173 SetFileAttributes (dest, dwAttrib);
174 SetFileTime (hFileDest, &srcCreationTime, &srcLastAccessTime, &srcLastWriteTime);
175 do
176 {
177 /* Read data from source */
178 ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
179
180 /* Done? */
181 if (dwRead == 0)
182 break;
183
184 /* Write to destination file */
185 WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
186
187 /* Done! or ctrl break! */
188 if (dwWritten != dwRead || CheckCtrlBreak(BREAK_INPUT))
189 {
190 ConOutResPuts(STRING_COPY_ERROR3);
191 VirtualFree (buffer, 0, MEM_RELEASE);
192 CloseHandle (hFileDest);
193 CloseHandle (hFileSrc);
194 nErrorLevel = 1;
195 return 0;
196 }
197 }
198 while (!bEof);
199
200 /* Return memory and close files */
201 VirtualFree (buffer, 0, MEM_RELEASE);
202 CloseHandle (hFileDest);
203 CloseHandle (hFileSrc);
204
205 /* Return one file replaced */
206 return 1;
207 }
208
209
210 /* Function to iterate over source files and call replace for each of them */
211 INT recReplace(DWORD dwFlags,
212 TCHAR szSrcPath[MAX_PATH],
213 TCHAR szDestPath[MAX_PATH],
214 BOOL *doMore)
215 {
216 TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
217 INT filesReplaced=0;
218 INT_PTR i;
219 DWORD dwAttrib = 0;
220 HANDLE hFile;
221 WIN32_FIND_DATA findBuffer;
222
223 /* Get file handel to the sourcefile(s) */
224 hFile = FindFirstFile (szSrcPath, &findBuffer);
225
226 /*
227 * Strip the paths back to the folder they are in, so that
228 * the different filenames can be added if more than one.
229 */
230 for(i = (_tcslen(szSrcPath) - 1); i > -1; i--)
231 {
232 if (szSrcPath[i] != _T('\\'))
233 szSrcPath[i] = _T('\0');
234 else
235 break;
236 }
237
238 /* Go through all the soursfiles and copy/replace them */
239 do
240 {
241 if (CheckCtrlBreak(BREAK_INPUT))
242 {
243 return filesReplaced;
244 }
245
246 /* Problem with file handler */
247 if (hFile == INVALID_HANDLE_VALUE)
248 return filesReplaced;
249
250 /* We do not want to replace any .. . ocr directory */
251 if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
252 !_tcscmp (findBuffer.cFileName, _T(".."))||
253 findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
254 continue;
255
256 /* Add filename to destpath */
257 _tcscpy(tmpDestPath,szDestPath);
258 _tcscat (tmpDestPath, findBuffer.cFileName);
259
260 dwAttrib = GetFileAttributes(tmpDestPath);
261 /* Check add flag */
262 if (dwFlags & REPLACE_ADD)
263 {
264 if (IsExistingFile(tmpDestPath))
265 continue;
266 else
267 dwAttrib = 0;
268 }
269 else
270 {
271 if (!IsExistingFile(tmpDestPath))
272 continue;
273 }
274
275 /* Check if file is read only, if so check if that should be ignored */
276 if (dwAttrib & FILE_ATTRIBUTE_READONLY)
277 {
278 if (!(dwFlags & REPLACE_READ_ONLY))
279 {
280 ConOutResPrintf(STRING_REPLACE_ERROR5, tmpDestPath);
281 *doMore = FALSE;
282 break;
283 }
284 }
285
286 /* Add filename to sourcepath, insted of wildcards */
287 _tcscpy(tmpSrcPath,szSrcPath);
288 _tcscat (tmpSrcPath, findBuffer.cFileName);
289
290 /* Make the replace */
291 if (replace(tmpSrcPath,tmpDestPath, dwFlags, doMore))
292 {
293 filesReplaced++;
294 }
295 else if (!*doMore)
296 {
297 /* The file to be replaced was the same as the source */
298 filesReplaced = -1;
299 break;
300 }
301
302 /* Take next sourcefile if any */
303 } while(FindNextFile (hFile, &findBuffer));
304
305 return filesReplaced;
306 }
307
308 /* If /s switch is specifyed all subdirs has to be considered */
309 INT recFindSubDirs(DWORD dwFlags,
310 TCHAR szSrcPath[MAX_PATH],
311 TCHAR szDestPath[MAX_PATH],
312 BOOL *doMore)
313 {
314 HANDLE hFile;
315 WIN32_FIND_DATA findBuffer;
316 TCHAR tmpDestPath[MAX_PATH], tmpSrcPath[MAX_PATH];
317 INT filesReplaced = 0;
318 INT_PTR i;
319
320 /*
321 * Add a wildcard to dest end so the it will be easy to iterate
322 * over all the files and directorys in the dest directory.
323 */
324 _tcscat(szDestPath, _T("*"));
325
326 /* Get the first file in the directory */
327 hFile = FindFirstFile (szDestPath, &findBuffer);
328
329 /* Remove the star added earlyer to dest path */
330 for(i = (_tcslen(szDestPath) - 1); i > -1; i--)
331 {
332 if (szDestPath[i] != _T('\\'))
333 szDestPath[i] = _T('\0');
334 else
335 break;
336 }
337
338 /* Iterate over all filed directories in the dest dir */
339 do
340 {
341 /* Save the source path so that it will not be wrecked */
342 _tcscpy(tmpSrcPath,szSrcPath);
343 /* Check for reading problems */
344 if (hFile == INVALID_HANDLE_VALUE)
345 {
346 ConOutFormatMessage (GetLastError(), tmpSrcPath);
347 return filesReplaced;
348 }
349
350 /*
351 * Check if the we should enter the dir or if it is a file
352 * or . or .. if so thake the next object to process.
353 */
354 if (!_tcscmp (findBuffer.cFileName, _T(".")) ||
355 !_tcscmp (findBuffer.cFileName, _T(".."))||
356 IsExistingFile(findBuffer.cFileName))
357 continue;
358 /* Add the destpath and the new dir path to tempDestPath */
359 _tcscpy(tmpDestPath,szDestPath);
360 _tcscat (tmpDestPath, findBuffer.cFileName);
361 /* Make sure that we have a directory */
362 if (IsExistingDirectory(tmpDestPath))
363 {
364 /* Add a \ to the end or the path */
365 if (szDestPath[_tcslen(tmpDestPath) - 1] != _T('\\'))
366 _tcscat(tmpDestPath, _T("\\"));
367 /* Call the function to replace files in the new directory */
368 filesReplaced += recReplace(dwFlags, tmpSrcPath, tmpDestPath, doMore);
369 /* If there were problems break e.g. read-only file */
370 if (!*doMore)
371 break;
372 _tcscpy(tmpSrcPath,szSrcPath);
373 /* Controle the next level of subdirs */
374 filesReplaced += recFindSubDirs(dwFlags,tmpSrcPath,tmpDestPath, doMore);
375 if (!*doMore)
376 break;
377 }
378 /* Get the next handle */
379 } while(FindNextFile (hFile, &findBuffer));
380
381 return filesReplaced;
382 }
383
384 INT cmd_replace (LPTSTR param)
385 {
386 LPTSTR *arg;
387 INT argc, i,filesReplaced = 0, nFiles, srcIndex = -1, destIndex = -1;
388 DWORD dwFlags = 0;
389 TCHAR szDestPath[MAX_PATH], szSrcPath[MAX_PATH], tmpSrcPath[MAX_PATH];
390 BOOL doMore = TRUE;
391
392 /* Help wanted? */
393 if (!_tcsncmp (param, _T("/?"), 2))
394 {
395 ConOutResPaging(TRUE,STRING_REPLACE_HELP1);
396 return 0;
397 }
398
399 /* Divide the argument in to an array of c-strings */
400 arg = split (param, &argc, FALSE, FALSE);
401 nFiles = argc;
402
403 /* Read options */
404 for (i = 0; i < argc; i++)
405 {
406 if (arg[i][0] == _T('/'))
407 {
408 if (_tcslen(arg[i]) == 2)
409 {
410 switch (_totupper(arg[i][1]))
411 {
412 case _T('A'):
413 dwFlags |= REPLACE_ADD;
414 break;
415 case _T('P'):
416 dwFlags |= REPLACE_CONFIRM;
417 break;
418 case _T('R'):
419 dwFlags |= REPLACE_READ_ONLY;
420 break;
421 case _T('S'):
422 dwFlags |= REPLACE_SUBDIR;
423 break;
424 case _T('W'):
425 dwFlags |= REPLACE_DISK;
426 break;
427 case _T('U'):
428 dwFlags |= REPLACE_UPDATE;
429 break;
430 default:
431 invalid_switch(arg[i]);
432 return 0;
433 }
434 }
435 else
436 {
437 invalid_switch(arg[i]);
438 freep(arg);
439 return 0;
440 }
441 nFiles--;
442 }
443 else
444 {
445 if (srcIndex == -1)
446 {
447 srcIndex = i;
448 }
449 else if (destIndex == -1)
450 {
451 destIndex = i;
452 }
453 else
454 {
455 invalid_switch(arg[i]);
456 freep(arg);
457 return 0;
458 }
459 }
460 }
461
462 /* See so that at least source is there */
463 if (nFiles < 1)
464 {
465 ConOutResPaging(TRUE,STRING_REPLACE_HELP2);
466 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
467 freep(arg);
468 return 1;
469 }
470 /* Check so that not both update and add switch is added and subdir */
471 if ((dwFlags & REPLACE_UPDATE || dwFlags & REPLACE_SUBDIR) && (dwFlags & REPLACE_ADD))
472 {
473 ConOutResPaging(TRUE,STRING_REPLACE_ERROR4);
474 ConOutResPaging(TRUE,STRING_REPLACE_HELP7);
475 freep(arg);
476 return 1;
477 }
478
479 /* If we have a destination get the full path */
480 if (destIndex != -1)
481 {
482 if (_tcslen(arg[destIndex]) == 2 && arg[destIndex][1] == ':')
483 GetRootPath(arg[destIndex],szDestPath,MAX_PATH);
484 else
485 {
486 /* Check for wildcards in destination directory */
487 if (_tcschr (arg[destIndex], _T('*')) != NULL ||
488 _tcschr (arg[destIndex], _T('?')) != NULL)
489 {
490 ConOutResPrintf(STRING_REPLACE_ERROR2,arg[destIndex]);
491 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
492 freep(arg);
493 return 1;
494 }
495 getPath(szDestPath, arg[destIndex]);
496 /* Make sure that destination exists */
497 if (!IsExistingDirectory(szDestPath))
498 {
499 ConOutResPrintf(STRING_REPLACE_ERROR2, szDestPath);
500 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
501 freep(arg);
502 return 1;
503 }
504 }
505 }
506 else
507 {
508 /* Dest is current dir */
509 GetCurrentDirectory(MAX_PATH,szDestPath);
510 }
511
512 /* Get the full source path */
513 if (!(_tcslen(arg[srcIndex]) == 2 && arg[srcIndex][1] == ':'))
514 getPath(szSrcPath, arg[srcIndex]);
515 else
516 _tcscpy(szSrcPath,arg[srcIndex]);
517
518 /* Source does not have wildcards */
519 if (_tcschr (arg[srcIndex], _T('*')) == NULL &&
520 _tcschr (arg[srcIndex], _T('?')) == NULL)
521 {
522 /* Check so that source is not a directory, because that is not allowed */
523 if (IsExistingDirectory(szSrcPath))
524 {
525 ConOutResPrintf(STRING_REPLACE_ERROR6, szSrcPath);
526 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
527 freep(arg);
528 return 1;
529 }
530 /* Check if the file exists */
531 if (!IsExistingFile(szSrcPath))
532 {
533 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
534 freep(arg);
535 return 1;
536 }
537 }
538 /* /w switch is set so wait for any key to be pressed */
539 if (dwFlags & REPLACE_DISK)
540 {
541 msg_pause();
542 cgetchar();
543 }
544
545 /* Add an extra \ to the destination path if needed */
546 if (szDestPath[_tcslen(szDestPath) - 1] != _T('\\'))
547 _tcscat(szDestPath, _T("\\"));
548
549 /* Save source path */
550 _tcscpy(tmpSrcPath,szSrcPath);
551 /* Replace in dest dir */
552 filesReplaced += recReplace(dwFlags, tmpSrcPath, szDestPath, &doMore);
553 /* If subdir switch is set replace in the subdirs to */
554 if (dwFlags & REPLACE_SUBDIR && doMore)
555 {
556 filesReplaced += recFindSubDirs(dwFlags, szSrcPath, szDestPath, &doMore);
557 }
558
559 /* If source == dest write no more */
560 if (filesReplaced != -1)
561 {
562 /* No files replaced */
563 if (filesReplaced==0)
564 {
565 /* Add switch dependent output */
566 if (dwFlags & REPLACE_ADD)
567 ConOutResPaging(TRUE,STRING_REPLACE_HELP7);
568 else
569 ConOutResPaging(TRUE,STRING_REPLACE_HELP3);
570 }
571 /* Some files replaced */
572 else
573 {
574 /* Add switch dependent output */
575 if (dwFlags & REPLACE_ADD)
576 ConOutResPrintf(STRING_REPLACE_HELP8, filesReplaced);
577 else
578 ConOutResPrintf(STRING_REPLACE_HELP4, filesReplaced);
579 }
580 }
581 /* Return memory */
582 freep(arg);
583 return 1;
584 }
585 #endif /* INCLUDE_CMD_REPLACE */