Forget set some error level and some msg that should have goto ConOut not to ConErr...
[reactos.git] / reactos / subsys / system / cmd / copy.c
1 /*
2 * COPY.C -- copy internal command.
3 *
4 *
5 * History:
6 *
7 * 01-Aug-98 (Rob Lake z63rrl@morgan.ucs.mun.ca)
8 * started
9 *
10 * 13-Aug-1998 (John P. Price)
11 * fixed memory leak problem in copy function.
12 * fixed copy function so it would work with wildcards in the source
13 *
14 * 13-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
15 * Added COPY command to CMD.
16 *
17 * 26-Jan-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
18 * Replaced CRT io functions by Win32 io functions.
19 *
20 * 27-Oct-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
21 * Disabled prompting when used in batch mode.
22 *
23 * 03-Apr-2005 (Magnus Olsen) <magnus@greatlord.com>)
24 * Remove all hardcode string to En.rc
25 *
26 * 13-Jul-2005 (Brandon Turner) <turnerb7@msu.edu>)
27 * Rewrite to clean up copy and support wildcard.
28 */
29
30 #include <precomp.h>
31 #include "resource.h"
32
33 #ifdef INCLUDE_CMD_COPY
34
35 enum
36 {
37 COPY_ASCII = 0x001, /* /A */
38 COPY_DECRYPT = 0x004, /* /D : Not Impleneted */
39 COPY_VERIFY = 0x008, /* /V : Dummy, Never will be Impleneted */
40 COPY_SHORTNAME = 0x010, /* /N : Not Impleneted */
41 COPY_NO_PROMPT = 0x020, /* /Y */
42 COPY_PROMPT = 0x040, /* /-Y */
43 COPY_RESTART = 0x080, /* /Z : Not Impleneted */
44 COPY_BINARY = 0x100, /* /B */
45 };
46
47 #define BUFF_SIZE 16384 /* 16k = max buffer size */
48
49
50 int copy (LPTSTR source, LPTSTR dest, int append, DWORD lpdwFlags)
51 {
52 TCHAR szMsg[RC_STRING_MAX_SIZE];
53 FILETIME srctime;
54 HANDLE hFileSrc;
55 HANDLE hFileDest;
56 LPBYTE buffer;
57 DWORD dwAttrib;
58 DWORD dwRead;
59 DWORD dwWritten;
60 DWORD i;
61 BOOL bEof = FALSE;
62
63 #ifdef _DEBUG
64 DebugPrintf (_T("checking mode\n"));
65 #endif
66
67 dwAttrib = GetFileAttributes (source);
68
69 hFileSrc = CreateFile (source, GENERIC_READ, FILE_SHARE_READ,
70 NULL, OPEN_EXISTING, 0, NULL);
71 if (hFileSrc == INVALID_HANDLE_VALUE)
72 {
73 LoadString(CMD_ModuleHandle, STRING_COPY_ERROR1, szMsg, RC_STRING_MAX_SIZE);
74 ConOutPrintf(szMsg, source);
75 nErrorLevel = 1;
76 return 0;
77 }
78
79 #ifdef _DEBUG
80 DebugPrintf (_T("getting time\n"));
81 #endif
82
83 GetFileTime (hFileSrc, &srctime, NULL, NULL);
84
85 #ifdef _DEBUG
86 DebugPrintf (_T("copy: flags has %s\n"),
87 *lpdwFlags & ASCII ? "ASCII" : "BINARY");
88 #endif
89
90 if (!IsExistingFile (dest))
91 {
92 #ifdef _DEBUG
93 DebugPrintf (_T("opening/creating\n"));
94 #endif
95 hFileDest =
96 CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
97 }
98 else if (!append)
99 {
100 if (!_tcscmp (dest, source))
101 {
102 LoadString(CMD_ModuleHandle, STRING_COPY_ERROR2, szMsg, RC_STRING_MAX_SIZE);
103 ConOutPrintf(szMsg, source);
104
105 CloseHandle (hFileSrc);
106 nErrorLevel = 1;
107 return 0;
108 }
109
110 #ifdef _DEBUG
111 DebugPrintf (_T("SetFileAttributes (%s, FILE_ATTRIBUTE_NORMAL);\n"), dest);
112 #endif
113 SetFileAttributes (dest, FILE_ATTRIBUTE_NORMAL);
114
115 #ifdef _DEBUG
116 DebugPrintf (_T("DeleteFile (%s);\n"), dest);
117 #endif
118 DeleteFile (dest);
119
120 hFileDest = CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
121 }
122 else
123 {
124 LONG lFilePosHigh = 0;
125
126 if (!_tcscmp (dest, source))
127 {
128 CloseHandle (hFileSrc);
129 return 0;
130 }
131
132 #ifdef _DEBUG
133 DebugPrintf (_T("opening/appending\n"));
134 #endif
135 SetFileAttributes (dest, FILE_ATTRIBUTE_NORMAL);
136
137 hFileDest =
138 CreateFile (dest, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
139
140 /* Move to end of file to start writing */
141 SetFilePointer (hFileDest, 0, &lFilePosHigh,FILE_END);
142 }
143
144
145 if (hFileDest == INVALID_HANDLE_VALUE)
146 {
147 CloseHandle (hFileSrc);
148 error_path_not_found ();
149 nErrorLevel = 1;
150 return 0;
151 }
152 buffer = (LPBYTE)malloc (BUFF_SIZE);
153 if (buffer == NULL)
154 {
155 CloseHandle (hFileDest);
156 CloseHandle (hFileSrc);
157 error_out_of_memory ();
158 nErrorLevel = 1;
159 return 0;
160 }
161
162 do
163 {
164 ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
165 if (lpdwFlags & COPY_ASCII)
166 {
167 for (i = 0; i < dwRead; i++)
168 {
169 if (((LPTSTR)buffer)[i] == 0x1A)
170 {
171 bEof = TRUE;
172 break;
173 }
174 }
175 dwRead = i;
176 }
177
178 if (dwRead == 0)
179 break;
180
181 WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
182 if (dwWritten != dwRead)
183 {
184 ConOutResPuts(STRING_COPY_ERROR3);
185
186 free (buffer);
187 CloseHandle (hFileDest);
188 CloseHandle (hFileSrc);
189 nErrorLevel = 1;
190 return 0;
191 }
192 }
193 while (dwRead && !bEof);
194
195 #ifdef _DEBUG
196 DebugPrintf (_T("setting time\n"));
197 #endif
198 SetFileTime (hFileDest, &srctime, NULL, NULL);
199
200 if (lpdwFlags & COPY_ASCII)
201 {
202 ((LPTSTR)buffer)[0] = 0x1A;
203 ((LPTSTR)buffer)[1] = _T('\0');
204 #ifdef _DEBUG
205 DebugPrintf (_T("appending ^Z\n"));
206 #endif
207 WriteFile (hFileDest, buffer, sizeof(TCHAR), &dwWritten, NULL);
208 }
209
210 free (buffer);
211 CloseHandle (hFileDest);
212 CloseHandle (hFileSrc);
213
214 #ifdef _DEBUG
215 DebugPrintf (_T("setting mode\n"));
216 #endif
217 SetFileAttributes (dest, dwAttrib);
218
219 return 1;
220 }
221
222
223 static INT Overwrite (LPTSTR fn)
224 {
225 /*ask the user if they want to override*/
226 TCHAR szMsg[RC_STRING_MAX_SIZE];
227 INT res;
228 LoadString(CMD_ModuleHandle, STRING_COPY_HELP1, szMsg, RC_STRING_MAX_SIZE);
229 ConOutPrintf(szMsg,fn);
230 res = FilePromptYNA ("");
231 return res;
232 }
233
234
235 INT cmd_copy (LPTSTR cmd, LPTSTR param)
236 {
237 TCHAR szMsg[RC_STRING_MAX_SIZE];
238 LPTSTR *arg;
239 INT argc, i, nFiles, nOverwrite = 0, nSrc = -1, nDes = -1;
240 /* this is the path up to the folder of the src and dest ie C:\windows\ */
241 TCHAR szDestPath[MAX_PATH];
242 TCHAR szSrcPath[MAX_PATH];
243 DWORD dwFlags = 0;
244 /* If this is the type of copy where we are adding files */
245 BOOL bAppend = FALSE;
246 WIN32_FIND_DATA findBuffer;
247 HANDLE hFile;
248 /* Used when something like "copy c*.exe d*.exe" during the process of
249 figuring out the new name */
250 TCHAR tmpName[MAX_PATH] = _T("");
251 /* Pointer to keep track of how far through the append input(file1+file2+file3) we are */
252 TCHAR * appendPointer = _T("\0");
253 /* The full path to src and dest. This has drive letter, folders, and filename */
254 TCHAR tmpDestPath[MAX_PATH];
255 TCHAR tmpSrcPath[MAX_PATH];
256 /* A bool on weather or not the destination name will be taking from the input */
257 BOOL bSrcName = FALSE;
258 /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
259 TCHAR * UseThisName;
260 /* Stores the name( i.e. blah.txt or blah*.txt) which later we might need */
261 TCHAR PreserveName[MAX_PATH];
262 /* for CMDCOPY env */
263 TCHAR *evar;
264 int size;
265
266
267 /*Show help/usage info*/
268 if (!_tcsncmp (param, _T("/?"), 2))
269 {
270 ConOutResPaging(TRUE, STRING_COPY_HELP2);
271 return 0;
272 }
273
274 nErrorLevel = 0;
275
276 /* Get the envor value if it exists */
277 evar = malloc(512);
278 size = GetEnvironmentVariable (_T("COPYCMD"), evar, 512);
279 if (size > 512)
280 {
281 evar = realloc(evar,size * sizeof(TCHAR) );
282 if (evar!=NULL)
283 {
284 size = GetEnvironmentVariable (_T("COPYCMD"), evar, size);
285 }
286 }
287 /* check see if we did get any env variable */
288 if (size !=0)
289 {
290 int t=0;
291 /* scan and set the flags */
292 for (t=0;t<size;t++)
293 {
294 if (_tcsncicmp(_T("/A"),&evar[t],2)==0)
295 {
296 evar[t]=_T(' ');
297 evar[t+1]=_T(' ');
298 }
299
300 else if (_tcsncicmp(_T("/B"),&evar[t],2)==0)
301 {
302 dwFlags |= COPY_BINARY;
303 evar[t]=_T(' ');
304 evar[t+1]=_T(' ');
305 }
306 else if (_tcsncicmp(_T("/D"),&evar[t],2)==0)
307 {
308 dwFlags |= COPY_DECRYPT;
309 evar[t]=_T(' ');
310 evar[t+1]=_T(' ');
311 }
312
313 else if (_tcsncicmp(_T("/V"),&evar[t],2)==0)
314 {
315 dwFlags |= COPY_VERIFY;
316 evar[t]=_T(' ');
317 evar[t+1]=_T(' ');
318 }
319
320 else if (_tcsncicmp(_T("/N"),&evar[t],2)==0)
321 {
322 dwFlags |= COPY_SHORTNAME;
323 evar[t]=_T(' ');
324 evar[t+1]=_T(' ');
325 }
326
327 else if (_tcsncicmp(_T("/Y"),&evar[t],2)==0)
328 {
329 dwFlags |= COPY_NO_PROMPT;
330 evar[t]=_T(' ');
331 evar[t+1]=_T(' ');
332 }
333
334 else if (_tcsncicmp(_T("/-Y"),&evar[t],3)==0)
335 {
336 dwFlags |= COPY_PROMPT;
337 evar[t]=_T(' ');
338 evar[t+1]=_T(' ');
339 evar[t+2]=_T(' ');
340 }
341
342 else if (_tcsncicmp(_T("/Z"),&evar[t],2)==0)
343 {
344 dwFlags |= COPY_PROMPT;
345 evar[t]=_T(' ');
346 evar[t+1]=_T(' ');
347 }
348 }
349 }
350 free(evar);
351
352
353 /*Split the user input into array*/
354 arg = split (param, &argc, FALSE);
355 nFiles = argc;
356
357
358 /*Read switches and count files*/
359 for (i = 0; i < argc; i++)
360 {
361 if (*arg[i] == _T('/'))
362 {
363 if (_tcslen(arg[i]) >= 2)
364 {
365 switch (_totupper(arg[i][1]))
366 {
367
368 case _T('A'):
369 dwFlags |= COPY_ASCII;
370 break;
371
372 case _T('B'):
373 dwFlags |= COPY_BINARY;
374 break;
375
376 case _T('D'):
377 dwFlags |= COPY_DECRYPT;
378 break;
379
380 case _T('V'):
381 dwFlags |= COPY_VERIFY;
382 break;
383
384 case _T('N'):
385 dwFlags |= COPY_SHORTNAME;
386 break;
387
388 case _T('Y'):
389 dwFlags |= COPY_NO_PROMPT;
390 dwFlags &= ~COPY_PROMPT;
391 break;
392
393 case _T('-'):
394 if(_tcslen(arg[i]) >= 3)
395 if(_totupper(arg[i][2]) == _T('Y'))
396 {
397 dwFlags &= ~COPY_NO_PROMPT;
398 dwFlags |= COPY_PROMPT;
399 }
400
401 break;
402
403 case _T('Z'):
404 dwFlags |= COPY_RESTART;
405 break;
406
407 default:
408 /* invaild switch */
409 error_invalid_switch(_totupper(arg[i][1]));
410 return 1;
411 break;
412 }
413 }
414 /*If it was a switch, subtract from total arguments*/
415 nFiles--;
416 }
417 else
418 {
419 /*if it isnt a switch then it is the source or destination*/
420 if(nSrc == -1)
421 nSrc = i;
422 else if(nDes == -1)
423 nDes = i;
424
425 }
426 }
427
428 if(nFiles < 1)
429 {
430 /* There is not enough files, there has to be at least 1 */
431 error_req_param_missing();
432 return 1;
433 }
434
435 if(nFiles > 2)
436 {
437 /* there is too many file names in command */
438 error_too_many_parameters("");
439 nErrorLevel = 1;
440 return 1;
441 }
442
443 if((nDes != -1) &&
444 ((_tcschr (arg[nSrc], _T('+')) != NULL) ||
445 (_tcschr (arg[nSrc], _T('*')) != NULL && _tcschr (arg[nDes], _T('*')) == NULL) ||
446 (IsExistingDirectory (arg[nSrc]) && (_tcschr (arg[nDes], _T('*')) == NULL && !IsExistingDirectory (arg[nDes])))
447 ))
448 {
449 /* There is a + in the source filename, this means
450 that there is more then one file being put into
451 one file. */
452 bAppend = TRUE;
453 if(_tcschr (arg[nSrc], _T('+')) != NULL)
454 appendPointer = arg[nSrc];
455 }
456
457 /* Reusing the number of files variable */
458 nFiles = 0;
459
460 do
461 {
462
463 /* Set up the string that is the path to the destination */
464 if(nDes != -1)
465 {
466 /* Check to make sure if they entered c:, if they do then GFPN
467 return current directory even though msdn says it will return c:\ */
468 if(_tcslen(arg[nDes]) == 2)
469 {
470 if(arg[nDes][1] == _T(':'))
471 {
472 _tcscpy (szDestPath, arg[nDes]);
473 _tcscat (szDestPath, _T("\\"));
474 }
475 }
476 else
477 /* If the user entered two file names then form the full string path*/
478 GetFullPathName (arg[nDes], MAX_PATH, szDestPath, NULL);
479
480 }
481 else
482 {
483 /* If no destination was entered then just use
484 the current directory as the destination */
485 GetCurrentDirectory (MAX_PATH, szDestPath);
486 }
487
488
489 /* Get the full string of the path to the source file*/
490 if(_tcschr (arg[nSrc], _T('+')) != NULL)
491 {
492
493 _tcscpy(tmpName,_T("\0"));
494 /* Loop through the source file name and copy all
495 the chars one at a time until it gets too + */
496 while(TRUE)
497 {
498
499 if(!_tcsncmp (appendPointer,_T("+"),1) || !_tcsncmp (appendPointer,_T("\0"),1))
500 {
501 /* Now that the pointer is on the + we
502 need to go to the start of the next filename */
503 if(!_tcsncmp (appendPointer,_T("+"),1))
504 appendPointer++;
505 break;
506 }
507 _tcsncat(tmpName,appendPointer,1);
508 appendPointer++;
509 }
510 /* Finish the string off with a null char */
511 _tcsncat(tmpName,_T("\0"),1);
512 /* Check to make sure if they entered c:, if they do then GFPN
513 return current directory even though msdn says it will return c:\ */
514 if(_tcslen(tmpName) == 2)
515 {
516 if(tmpName[1] == _T(':'))
517 {
518 _tcscpy (szSrcPath, tmpName);
519 _tcscat (szSrcPath, _T("\\"));
520 }
521 }
522 else
523 /* Get the full path to first file in the string of file names */
524 GetFullPathName (tmpName, MAX_PATH, szSrcPath, NULL);
525 }
526 else
527 {
528 /* Check to make sure if they entered c:, if they do then GFPN
529 return current directory even though msdn says it will return c:\ */
530 if(_tcslen(arg[nSrc]) == 2)
531 {
532 if(arg[nSrc][1] == _T(':'))
533 {
534 _tcscpy (szSrcPath, arg[nSrc]);
535 _tcscat (szSrcPath, _T("\\"));
536 }
537 }
538 else
539 /* Get the full path of the source file */
540 GetFullPathName (arg[nSrc], MAX_PATH, szSrcPath, NULL);
541
542 }
543
544 /* From this point on, we can assume that the shortest path is 3 letters long
545 and that would be [DriveLetter]:\ */
546
547 /* If there is no * in the path name and it is a folder
548 then we will need to add a wildcard to the pathname
549 so FindFirstFile comes up with all the files in that
550 folder */
551 if(_tcschr (szSrcPath, _T('*')) == NULL &&
552 IsExistingDirectory (szSrcPath))
553 {
554 /* If it doesnt have a \ at the end already then on needs to be added */
555 if(szSrcPath[_tcslen(szSrcPath) - 1] != _T('\\'))
556 _tcscat (szSrcPath, _T("\\"));
557 /* Add a wildcard after the \ */
558 _tcscat (szSrcPath, _T("*"));
559 }
560 /* Make sure there is an ending slash to the path if the dest is a folder */
561 if(_tcschr (szDestPath, _T('*')) == NULL &&
562 IsExistingDirectory(szDestPath))
563 {
564 if(szDestPath[_tcslen(szDestPath) - 1] != _T('\\'))
565 _tcscat (szDestPath, _T("\\"));
566 }
567
568
569 /* Get a list of all the files */
570 hFile = FindFirstFile (szSrcPath, &findBuffer);
571
572
573 /* We need to figure out what the name of the file in the is going to be */
574 if((szDestPath[_tcslen(szDestPath) - 1] == _T('*') && szDestPath[_tcslen(szDestPath) - 2] == _T('\\')) ||
575 szDestPath[_tcslen(szDestPath) - 1] == _T('\\'))
576 {
577 /* In this case we will be using the same name as the source file
578 for the destination file because destination is a folder */
579 bSrcName = TRUE;
580 }
581 else
582 {
583 /* Save the name the user entered */
584 UseThisName = _tcsrchr(szDestPath,_T('\\'));
585 UseThisName++;
586 _tcscpy(PreserveName,UseThisName);
587 }
588
589 /* Strip the paths back to the folder they are in */
590 for(i = (_tcslen(szSrcPath) - 1); i > -1; i--)
591 if(szSrcPath[i] != _T('\\'))
592 szSrcPath[i] = _T('\0');
593 else
594 break;
595
596 for(i = (_tcslen(szDestPath) - 1); i > -1; i--)
597 if(szDestPath[i] != _T('\\'))
598 szDestPath[i] = _T('\0');
599 else
600 break;
601
602
603 do
604 {
605 /* Set the override to yes each new file */
606 nOverwrite = 1;
607
608 /* If it couldnt open the file handle, print out the error */
609 if(hFile == INVALID_HANDLE_VALUE)
610 {
611 ConOutFormatMessage (GetLastError(), szSrcPath);
612 freep (arg);
613 nErrorLevel = 1;
614 return 1;
615 }
616
617 /* Ignore the . and .. files */
618 if(!_tcscmp (findBuffer.cFileName, _T(".")) ||
619 !_tcscmp (findBuffer.cFileName, _T(".."))||
620 findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
621 continue;
622
623 /* Copy the base folder over to a tmp string */
624 _tcscpy(tmpDestPath,szDestPath);
625
626 /* Can't put a file into a folder that isnt there */
627 if(!IsExistingDirectory(szDestPath))
628 {
629 ConOutFormatMessage (GetLastError (), szSrcPath);
630 freep (arg);
631 nErrorLevel = 1;
632 return 1;
633 }
634 /* Copy over the destination path name */
635 if(bSrcName)
636 _tcscat (tmpDestPath, findBuffer.cFileName);
637 else
638 {
639 /* If there is no wildcard you can use the name the user entered */
640 if(_tcschr (PreserveName, _T('*')) == NULL)
641 {
642 _tcscat (tmpDestPath, PreserveName);
643 }
644 else
645 {
646 /* The following lines of copy were written by someone else
647 (most likely Eric Khoul) and it was taken from ren.c */
648 LPTSTR p,q,r;
649 TCHAR DoneFile[MAX_PATH];
650 /* build destination file name */
651 p = findBuffer.cFileName;
652 q = PreserveName;
653 r = DoneFile;
654 while(*q != 0)
655 {
656 if (*q == '*')
657 {
658 q++;
659 while (*p != 0 && *p != *q)
660 {
661 *r = *p;
662 p++;
663 r++;
664 }
665 }
666 else if (*q == '?')
667 {
668 q++;
669 if (*p != 0)
670 {
671 *r = *p;
672 p++;
673 r++;
674 }
675 }
676 else
677 {
678 *r = *q;
679 if (*p != 0)
680 p++;
681 q++;
682 r++;
683 }
684 }
685 *r = 0;
686 /* Add the filename to the tmp string path */
687 _tcscat (tmpDestPath, DoneFile);
688
689 }
690 }
691
692
693 /* Build the string path to the source file */
694 _tcscpy(tmpSrcPath,szSrcPath);
695 _tcscat (tmpSrcPath, findBuffer.cFileName);
696
697 /* Check to see if the file is the same file */
698 if(!_tcscmp (tmpSrcPath, tmpDestPath))
699 continue;
700
701 /* Handle any overriding / prompting that needs to be done */
702 if((!(dwFlags & COPY_NO_PROMPT) && IsExistingFile (tmpDestPath)) || dwFlags & COPY_PROMPT)
703 nOverwrite = Overwrite(tmpDestPath);
704 if(nOverwrite == PROMPT_NO || nOverwrite == PROMPT_BREAK)
705 continue;
706 if(nOverwrite == PROMPT_ALL || (nOverwrite == PROMPT_YES && bAppend))
707 dwFlags |= COPY_NO_PROMPT;
708
709 /* Tell weather the copy was successful or not */
710 if(copy(tmpSrcPath,tmpDestPath, bAppend, dwFlags))
711 {
712 nFiles++;
713 /* only print source name when more then one file */
714 if(_tcschr (arg[nSrc], _T('+')) != NULL || _tcschr (arg[nSrc], _T('*')) != NULL)
715 ConOutPrintf("%s\n",findBuffer.cFileName);
716 //LoadString(CMD_ModuleHandle, STRING_MOVE_ERROR1, szMsg, RC_STRING_MAX_SIZE);
717 }
718 else
719 {
720 /* print out the error message */
721 LoadString(CMD_ModuleHandle, STRING_COPY_ERROR3, szMsg, RC_STRING_MAX_SIZE);
722 ConOutPrintf(szMsg);
723 nErrorLevel = 1;
724 }
725
726 /* Loop through all wildcard files */
727 }while(FindNextFile (hFile, &findBuffer));
728 /* Loop through all files in src string with a + */
729 }while(_tcsncmp (appendPointer,_T("\0"),1));
730
731 /* print out the number of files copied */
732 LoadString(CMD_ModuleHandle, STRING_COPY_FILE, szMsg, RC_STRING_MAX_SIZE);
733 ConOutPrintf(szMsg, nFiles);
734
735 CloseHandle(hFile);
736 freep (arg);
737 return 0;
738 }
739
740
741 #endif /* INCLUDE_CMD_COPY */