last bugfix did forget use tchar * 512 in malloc I did only use 512 in malloc
[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 * sizeof(TCHAR));
278 if (evar==NULL) size = 0;
279
280 size = GetEnvironmentVariable (_T("COPYCMD"), evar, 512);
281 if ((size > 1) && (size > 512))
282 {
283 evar = realloc(evar,size * sizeof(TCHAR) );
284 if (evar!=NULL)
285 {
286 size = GetEnvironmentVariable (_T("COPYCMD"), evar, size);
287 }
288 else
289 {
290 size=0;
291 }
292 }
293
294 /* check see if we did get any env variable */
295 if (size !=0)
296 {
297 int t=0;
298 /* scan and set the flags */
299 for (t=0;t<size;t++)
300 {
301 if (_tcsncicmp(_T("/A"),&evar[t],2)==0)
302 {
303 evar[t]=_T(' ');
304 evar[t+1]=_T(' ');
305 }
306
307 else if (_tcsncicmp(_T("/B"),&evar[t],2)==0)
308 {
309 dwFlags |= COPY_BINARY;
310 evar[t]=_T(' ');
311 evar[t+1]=_T(' ');
312 }
313 else if (_tcsncicmp(_T("/D"),&evar[t],2)==0)
314 {
315 dwFlags |= COPY_DECRYPT;
316 evar[t]=_T(' ');
317 evar[t+1]=_T(' ');
318 }
319
320 else if (_tcsncicmp(_T("/V"),&evar[t],2)==0)
321 {
322 dwFlags |= COPY_VERIFY;
323 evar[t]=_T(' ');
324 evar[t+1]=_T(' ');
325 }
326
327 else if (_tcsncicmp(_T("/N"),&evar[t],2)==0)
328 {
329 dwFlags |= COPY_SHORTNAME;
330 evar[t]=_T(' ');
331 evar[t+1]=_T(' ');
332 }
333
334 else if (_tcsncicmp(_T("/Y"),&evar[t],2)==0)
335 {
336 dwFlags |= COPY_NO_PROMPT;
337 evar[t]=_T(' ');
338 evar[t+1]=_T(' ');
339 }
340
341 else if (_tcsncicmp(_T("/-Y"),&evar[t],3)==0)
342 {
343 dwFlags |= COPY_PROMPT;
344 evar[t]=_T(' ');
345 evar[t+1]=_T(' ');
346 evar[t+2]=_T(' ');
347 }
348
349 else if (_tcsncicmp(_T("/Z"),&evar[t],2)==0)
350 {
351 dwFlags |= COPY_PROMPT;
352 evar[t]=_T(' ');
353 evar[t+1]=_T(' ');
354 }
355 }
356 }
357 free(evar);
358
359
360 /*Split the user input into array*/
361 arg = split (param, &argc, FALSE);
362 nFiles = argc;
363
364
365 /*Read switches and count files*/
366 for (i = 0; i < argc; i++)
367 {
368 if (*arg[i] == _T('/'))
369 {
370 if (_tcslen(arg[i]) >= 2)
371 {
372 switch (_totupper(arg[i][1]))
373 {
374
375 case _T('A'):
376 dwFlags |= COPY_ASCII;
377 break;
378
379 case _T('B'):
380 dwFlags |= COPY_BINARY;
381 break;
382
383 case _T('D'):
384 dwFlags |= COPY_DECRYPT;
385 break;
386
387 case _T('V'):
388 dwFlags |= COPY_VERIFY;
389 break;
390
391 case _T('N'):
392 dwFlags |= COPY_SHORTNAME;
393 break;
394
395 case _T('Y'):
396 dwFlags |= COPY_NO_PROMPT;
397 dwFlags &= ~COPY_PROMPT;
398 break;
399
400 case _T('-'):
401 if(_tcslen(arg[i]) >= 3)
402 if(_totupper(arg[i][2]) == _T('Y'))
403 {
404 dwFlags &= ~COPY_NO_PROMPT;
405 dwFlags |= COPY_PROMPT;
406 }
407
408 break;
409
410 case _T('Z'):
411 dwFlags |= COPY_RESTART;
412 break;
413
414 default:
415 /* invaild switch */
416 error_invalid_switch(_totupper(arg[i][1]));
417 return 1;
418 break;
419 }
420 }
421 /*If it was a switch, subtract from total arguments*/
422 nFiles--;
423 }
424 else
425 {
426 /*if it isnt a switch then it is the source or destination*/
427 if(nSrc == -1)
428 nSrc = i;
429 else if(nDes == -1)
430 nDes = i;
431
432 }
433 }
434
435 if(nFiles < 1)
436 {
437 /* There is not enough files, there has to be at least 1 */
438 error_req_param_missing();
439 return 1;
440 }
441
442 if(nFiles > 2)
443 {
444 /* there is too many file names in command */
445 error_too_many_parameters("");
446 nErrorLevel = 1;
447 return 1;
448 }
449
450 if((nDes != -1) &&
451 ((_tcschr (arg[nSrc], _T('+')) != NULL) ||
452 (_tcschr (arg[nSrc], _T('*')) != NULL && _tcschr (arg[nDes], _T('*')) == NULL) ||
453 (IsExistingDirectory (arg[nSrc]) && (_tcschr (arg[nDes], _T('*')) == NULL && !IsExistingDirectory (arg[nDes])))
454 ))
455 {
456 /* There is a + in the source filename, this means
457 that there is more then one file being put into
458 one file. */
459 bAppend = TRUE;
460 if(_tcschr (arg[nSrc], _T('+')) != NULL)
461 appendPointer = arg[nSrc];
462 }
463
464 /* Reusing the number of files variable */
465 nFiles = 0;
466
467 do
468 {
469
470 /* Set up the string that is the path to the destination */
471 if(nDes != -1)
472 {
473 /* Check to make sure if they entered c:, if they do then GFPN
474 return current directory even though msdn says it will return c:\ */
475 if(_tcslen(arg[nDes]) == 2)
476 {
477 if(arg[nDes][1] == _T(':'))
478 {
479 _tcscpy (szDestPath, arg[nDes]);
480 _tcscat (szDestPath, _T("\\"));
481 }
482 }
483 else
484 /* If the user entered two file names then form the full string path*/
485 GetFullPathName (arg[nDes], MAX_PATH, szDestPath, NULL);
486
487 }
488 else
489 {
490 /* If no destination was entered then just use
491 the current directory as the destination */
492 GetCurrentDirectory (MAX_PATH, szDestPath);
493 }
494
495
496 /* Get the full string of the path to the source file*/
497 if(_tcschr (arg[nSrc], _T('+')) != NULL)
498 {
499
500 _tcscpy(tmpName,_T("\0"));
501 /* Loop through the source file name and copy all
502 the chars one at a time until it gets too + */
503 while(TRUE)
504 {
505
506 if(!_tcsncmp (appendPointer,_T("+"),1) || !_tcsncmp (appendPointer,_T("\0"),1))
507 {
508 /* Now that the pointer is on the + we
509 need to go to the start of the next filename */
510 if(!_tcsncmp (appendPointer,_T("+"),1))
511 appendPointer++;
512 break;
513 }
514 _tcsncat(tmpName,appendPointer,1);
515 appendPointer++;
516 }
517 /* Finish the string off with a null char */
518 _tcsncat(tmpName,_T("\0"),1);
519 /* Check to make sure if they entered c:, if they do then GFPN
520 return current directory even though msdn says it will return c:\ */
521 if(_tcslen(tmpName) == 2)
522 {
523 if(tmpName[1] == _T(':'))
524 {
525 _tcscpy (szSrcPath, tmpName);
526 _tcscat (szSrcPath, _T("\\"));
527 }
528 }
529 else
530 /* Get the full path to first file in the string of file names */
531 GetFullPathName (tmpName, MAX_PATH, szSrcPath, NULL);
532 }
533 else
534 {
535 /* Check to make sure if they entered c:, if they do then GFPN
536 return current directory even though msdn says it will return c:\ */
537 if(_tcslen(arg[nSrc]) == 2)
538 {
539 if(arg[nSrc][1] == _T(':'))
540 {
541 _tcscpy (szSrcPath, arg[nSrc]);
542 _tcscat (szSrcPath, _T("\\"));
543 }
544 }
545 else
546 /* Get the full path of the source file */
547 GetFullPathName (arg[nSrc], MAX_PATH, szSrcPath, NULL);
548
549 }
550
551 /* From this point on, we can assume that the shortest path is 3 letters long
552 and that would be [DriveLetter]:\ */
553
554 /* If there is no * in the path name and it is a folder
555 then we will need to add a wildcard to the pathname
556 so FindFirstFile comes up with all the files in that
557 folder */
558 if(_tcschr (szSrcPath, _T('*')) == NULL &&
559 IsExistingDirectory (szSrcPath))
560 {
561 /* If it doesnt have a \ at the end already then on needs to be added */
562 if(szSrcPath[_tcslen(szSrcPath) - 1] != _T('\\'))
563 _tcscat (szSrcPath, _T("\\"));
564 /* Add a wildcard after the \ */
565 _tcscat (szSrcPath, _T("*"));
566 }
567 /* Make sure there is an ending slash to the path if the dest is a folder */
568 if(_tcschr (szDestPath, _T('*')) == NULL &&
569 IsExistingDirectory(szDestPath))
570 {
571 if(szDestPath[_tcslen(szDestPath) - 1] != _T('\\'))
572 _tcscat (szDestPath, _T("\\"));
573 }
574
575
576 /* Get a list of all the files */
577 hFile = FindFirstFile (szSrcPath, &findBuffer);
578
579
580 /* We need to figure out what the name of the file in the is going to be */
581 if((szDestPath[_tcslen(szDestPath) - 1] == _T('*') && szDestPath[_tcslen(szDestPath) - 2] == _T('\\')) ||
582 szDestPath[_tcslen(szDestPath) - 1] == _T('\\'))
583 {
584 /* In this case we will be using the same name as the source file
585 for the destination file because destination is a folder */
586 bSrcName = TRUE;
587 }
588 else
589 {
590 /* Save the name the user entered */
591 UseThisName = _tcsrchr(szDestPath,_T('\\'));
592 UseThisName++;
593 _tcscpy(PreserveName,UseThisName);
594 }
595
596 /* Strip the paths back to the folder they are in */
597 for(i = (_tcslen(szSrcPath) - 1); i > -1; i--)
598 if(szSrcPath[i] != _T('\\'))
599 szSrcPath[i] = _T('\0');
600 else
601 break;
602
603 for(i = (_tcslen(szDestPath) - 1); i > -1; i--)
604 if(szDestPath[i] != _T('\\'))
605 szDestPath[i] = _T('\0');
606 else
607 break;
608
609
610 do
611 {
612 /* Set the override to yes each new file */
613 nOverwrite = 1;
614
615 /* If it couldnt open the file handle, print out the error */
616 if(hFile == INVALID_HANDLE_VALUE)
617 {
618 ConOutFormatMessage (GetLastError(), szSrcPath);
619 freep (arg);
620 nErrorLevel = 1;
621 return 1;
622 }
623
624 /* Ignore the . and .. files */
625 if(!_tcscmp (findBuffer.cFileName, _T(".")) ||
626 !_tcscmp (findBuffer.cFileName, _T(".."))||
627 findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
628 continue;
629
630 /* Copy the base folder over to a tmp string */
631 _tcscpy(tmpDestPath,szDestPath);
632
633 /* Can't put a file into a folder that isnt there */
634 if(!IsExistingDirectory(szDestPath))
635 {
636 ConOutFormatMessage (GetLastError (), szSrcPath);
637 freep (arg);
638 nErrorLevel = 1;
639 return 1;
640 }
641 /* Copy over the destination path name */
642 if(bSrcName)
643 _tcscat (tmpDestPath, findBuffer.cFileName);
644 else
645 {
646 /* If there is no wildcard you can use the name the user entered */
647 if(_tcschr (PreserveName, _T('*')) == NULL)
648 {
649 _tcscat (tmpDestPath, PreserveName);
650 }
651 else
652 {
653 /* The following lines of copy were written by someone else
654 (most likely Eric Khoul) and it was taken from ren.c */
655 LPTSTR p,q,r;
656 TCHAR DoneFile[MAX_PATH];
657 /* build destination file name */
658 p = findBuffer.cFileName;
659 q = PreserveName;
660 r = DoneFile;
661 while(*q != 0)
662 {
663 if (*q == '*')
664 {
665 q++;
666 while (*p != 0 && *p != *q)
667 {
668 *r = *p;
669 p++;
670 r++;
671 }
672 }
673 else if (*q == '?')
674 {
675 q++;
676 if (*p != 0)
677 {
678 *r = *p;
679 p++;
680 r++;
681 }
682 }
683 else
684 {
685 *r = *q;
686 if (*p != 0)
687 p++;
688 q++;
689 r++;
690 }
691 }
692 *r = 0;
693 /* Add the filename to the tmp string path */
694 _tcscat (tmpDestPath, DoneFile);
695
696 }
697 }
698
699
700 /* Build the string path to the source file */
701 _tcscpy(tmpSrcPath,szSrcPath);
702 _tcscat (tmpSrcPath, findBuffer.cFileName);
703
704 /* Check to see if the file is the same file */
705 if(!_tcscmp (tmpSrcPath, tmpDestPath))
706 continue;
707
708 /* Handle any overriding / prompting that needs to be done */
709 if((!(dwFlags & COPY_NO_PROMPT) && IsExistingFile (tmpDestPath)) || dwFlags & COPY_PROMPT)
710 nOverwrite = Overwrite(tmpDestPath);
711 if(nOverwrite == PROMPT_NO || nOverwrite == PROMPT_BREAK)
712 continue;
713 if(nOverwrite == PROMPT_ALL || (nOverwrite == PROMPT_YES && bAppend))
714 dwFlags |= COPY_NO_PROMPT;
715
716 /* Tell weather the copy was successful or not */
717 if(copy(tmpSrcPath,tmpDestPath, bAppend, dwFlags))
718 {
719 nFiles++;
720 /* only print source name when more then one file */
721 if(_tcschr (arg[nSrc], _T('+')) != NULL || _tcschr (arg[nSrc], _T('*')) != NULL)
722 ConOutPrintf("%s\n",findBuffer.cFileName);
723 //LoadString(CMD_ModuleHandle, STRING_MOVE_ERROR1, szMsg, RC_STRING_MAX_SIZE);
724 }
725 else
726 {
727 /* print out the error message */
728 LoadString(CMD_ModuleHandle, STRING_COPY_ERROR3, szMsg, RC_STRING_MAX_SIZE);
729 ConOutPrintf(szMsg);
730 nErrorLevel = 1;
731 }
732
733 /* Loop through all wildcard files */
734 }while(FindNextFile (hFile, &findBuffer));
735 /* Loop through all files in src string with a + */
736 }while(_tcsncmp (appendPointer,_T("\0"),1));
737
738 /* print out the number of files copied */
739 LoadString(CMD_ModuleHandle, STRING_COPY_FILE, szMsg, RC_STRING_MAX_SIZE);
740 ConOutPrintf(szMsg, nFiles);
741
742 CloseHandle(hFile);
743 freep (arg);
744 return 0;
745 }
746
747
748 #endif /* INCLUDE_CMD_COPY */