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