xref: /reactos/base/shell/cmd/copy.c (revision 10e7643c)
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)
15  *        Added COPY command to CMD.
16  *
17  *    26-Jan-1998 (Eric Kohl)
18  *        Replaced CRT io functions by Win32 io functions.
19  *
20  *    27-Oct-1998 (Eric Kohl)
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  *    20-Jul-2005 (Brandon Turner <turnerb7@msu.edu>)
30  *        Add touch syntax.  "copy arp.exe+,,"
31  *        Copy command is now completed.
32  */
33 
34 #include "precomp.h"
35 
36 #ifdef INCLUDE_CMD_COPY
37 
38 enum
39 {
40     COPY_ASCII       = 0x001,   /* /A  */
41     COPY_DECRYPT     = 0x004,   /* /D  */
42     COPY_VERIFY      = 0x008,   /* /V  : Dummy, Never will be Implemented */
43     COPY_SHORTNAME   = 0x010,   /* /N  : Dummy, Never will be Implemented */
44     COPY_NO_PROMPT   = 0x020,   /* /Y  */
45     COPY_PROMPT      = 0x040,   /* /-Y */
46     COPY_RESTART     = 0x080,   /* /Z  */
47     COPY_BINARY      = 0x100,   /* /B  */
48 };
49 
50 INT
51 copy(TCHAR source[MAX_PATH],
52      TCHAR dest[MAX_PATH],
53      INT append,
54      DWORD lpdwFlags,
55      BOOL bTouch)
56 {
57     FILETIME srctime,NewFileTime;
58     HANDLE hFileSrc;
59     HANDLE hFileDest;
60     LPBYTE buffer;
61     DWORD  dwAttrib;
62     DWORD  dwRead;
63     DWORD  dwWritten;
64     BOOL   bEof = FALSE;
65     TCHAR TrueDest[MAX_PATH];
66     TCHAR TempSrc[MAX_PATH];
67     TCHAR * FileName;
68     SYSTEMTIME CurrentTime;
69 
70     /* Check Breaker */
71     if (CheckCtrlBreak(BREAK_INPUT))
72         return 0;
73 
74     TRACE ("checking mode\n");
75 
76     if (bTouch)
77     {
78         hFileSrc = CreateFile (source, GENERIC_WRITE, FILE_SHARE_READ,
79             NULL, OPEN_EXISTING, 0, NULL);
80         if (hFileSrc == INVALID_HANDLE_VALUE)
81         {
82             ConOutResPrintf(STRING_COPY_ERROR1, source);
83             nErrorLevel = 1;
84             return 0;
85         }
86 
87         GetSystemTime(&CurrentTime);
88         SystemTimeToFileTime(&CurrentTime, &NewFileTime);
89         if (SetFileTime(hFileSrc,(LPFILETIME) NULL, (LPFILETIME) NULL, &NewFileTime))
90         {
91             CloseHandle(hFileSrc);
92             nErrorLevel = 1;
93             return 1;
94 
95         }
96         else
97         {
98             CloseHandle(hFileSrc);
99             return 0;
100         }
101     }
102 
103     dwAttrib = GetFileAttributes (source);
104 
105     hFileSrc = CreateFile (source, GENERIC_READ, FILE_SHARE_READ,
106         NULL, OPEN_EXISTING, 0, NULL);
107     if (hFileSrc == INVALID_HANDLE_VALUE)
108     {
109         ConOutResPrintf(STRING_COPY_ERROR1, source);
110         nErrorLevel = 1;
111         return 0;
112     }
113 
114     TRACE ("getting time\n");
115 
116     GetFileTime (hFileSrc, &srctime, NULL, NULL);
117 
118     TRACE ("copy: flags has %s\n",
119         lpdwFlags & COPY_ASCII ? "ASCII" : "BINARY");
120 
121     /* Check to see if /D or /Z are true, if so we need a middle
122        man to copy the file too to allow us to use CopyFileEx later */
123     if (lpdwFlags & COPY_DECRYPT)
124     {
125         GetEnvironmentVariable(_T("TEMP"),TempSrc,MAX_PATH);
126         _tcscat(TempSrc,_T("\\"));
127         FileName = _tcsrchr(source,_T('\\'));
128         FileName++;
129         _tcscat(TempSrc,FileName);
130         /* This is needed to be on the end to prevent an error
131            if the user did "copy /D /Z foo bar then it would be copied
132            too %TEMP%\foo here and when %TEMP%\foo when it sets it up
133            for COPY_RESTART, this would mean it is copying to itself
134            which would error when it tried to open the handles for ReadFile
135            and WriteFile */
136         _tcscat(TempSrc,_T(".decrypt"));
137         if (!CopyFileEx(source, TempSrc, NULL, NULL, FALSE, COPY_FILE_ALLOW_DECRYPTED_DESTINATION))
138         {
139             CloseHandle (hFileSrc);
140             nErrorLevel = 1;
141             return 0;
142         }
143         _tcscpy(source, TempSrc);
144     }
145 
146 
147     if (lpdwFlags & COPY_RESTART)
148     {
149         _tcscpy(TrueDest, dest);
150         GetEnvironmentVariable(_T("TEMP"),dest,MAX_PATH);
151         _tcscat(dest,_T("\\"));
152         FileName = _tcsrchr(TrueDest,_T('\\'));
153         FileName++;
154         _tcscat(dest,FileName);
155     }
156 
157 
158     if (!IsExistingFile (dest))
159     {
160         TRACE ("opening/creating\n");
161         hFileDest =
162             CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
163     }
164     else if (!append)
165     {
166         TRACE ("SetFileAttributes (%s, FILE_ATTRIBUTE_NORMAL);\n", debugstr_aw(dest));
167         SetFileAttributes (dest, FILE_ATTRIBUTE_NORMAL);
168 
169         TRACE ("DeleteFile (%s);\n", debugstr_aw(dest));
170         DeleteFile (dest);
171 
172         hFileDest =	CreateFile (dest, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
173     }
174     else
175     {
176         LONG lFilePosHigh = 0;
177 
178         if (!_tcscmp (dest, source))
179         {
180             CloseHandle (hFileSrc);
181             return 0;
182         }
183 
184         TRACE ("opening/appending\n");
185         SetFileAttributes (dest, FILE_ATTRIBUTE_NORMAL);
186 
187         hFileDest =
188             CreateFile (dest, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
189 
190         /* Move to end of file to start writing */
191         SetFilePointer (hFileDest, 0, &lFilePosHigh,FILE_END);
192     }
193 
194 
195     if (hFileDest == INVALID_HANDLE_VALUE)
196     {
197         CloseHandle (hFileSrc);
198         ConOutResPuts(STRING_ERROR_PATH_NOT_FOUND);
199         nErrorLevel = 1;
200         return 0;
201     }
202 
203     /* A page-aligned buffer usually give more speed */
204     buffer = VirtualAlloc(NULL, BUFF_SIZE, MEM_COMMIT, PAGE_READWRITE);
205     if (buffer == NULL)
206     {
207         CloseHandle (hFileDest);
208         CloseHandle (hFileSrc);
209         ConOutResPuts(STRING_ERROR_OUT_OF_MEMORY);
210         nErrorLevel = 1;
211         return 0;
212     }
213 
214     do
215     {
216         ReadFile (hFileSrc, buffer, BUFF_SIZE, &dwRead, NULL);
217         if (lpdwFlags & COPY_ASCII)
218         {
219             LPBYTE pEof = memchr(buffer, 0x1A, dwRead);
220             if (pEof != NULL)
221             {
222                 bEof = TRUE;
223                 dwRead = pEof-buffer+1;
224                 break;
225             }
226         }
227 
228         if (dwRead == 0)
229             break;
230 
231         WriteFile (hFileDest, buffer, dwRead, &dwWritten, NULL);
232         if (dwWritten != dwRead || CheckCtrlBreak(BREAK_INPUT))
233         {
234             ConOutResPuts(STRING_COPY_ERROR3);
235 
236             VirtualFree (buffer, 0, MEM_RELEASE);
237             CloseHandle (hFileDest);
238             CloseHandle (hFileSrc);
239             nErrorLevel = 1;
240             return 0;
241         }
242     }
243     while (!bEof);
244 
245     TRACE ("setting time\n");
246     SetFileTime (hFileDest, &srctime, NULL, NULL);
247 
248     if ((lpdwFlags & COPY_ASCII) && !bEof)
249     {
250         /* we're dealing with ASCII files! */
251         buffer[0] = 0x1A;
252         TRACE ("appending ^Z\n");
253         WriteFile (hFileDest, buffer, sizeof(CHAR), &dwWritten, NULL);
254     }
255 
256     VirtualFree (buffer, 0, MEM_RELEASE);
257     CloseHandle (hFileDest);
258     CloseHandle (hFileSrc);
259 
260     TRACE ("setting mode\n");
261     /* For MS-DOS backwards-compatibility, always remove the read-only attribute */
262     SetFileAttributes (dest, dwAttrib & ~FILE_ATTRIBUTE_READONLY);
263 
264     /* Now finish off the copy if needed with CopyFileEx */
265     if (lpdwFlags & COPY_RESTART)
266     {
267         if (!CopyFileEx(dest, TrueDest, NULL, NULL, FALSE, COPY_FILE_RESTARTABLE))
268         {
269             nErrorLevel = 1;
270             DeleteFile(dest);
271             return 0;
272         }
273         /* Take care of file in the temp folder */
274         DeleteFile(dest);
275 
276     }
277 
278     if (lpdwFlags & COPY_DECRYPT)
279         DeleteFile(TempSrc);
280 
281     return 1;
282 }
283 
284 
285 static INT CopyOverwrite (LPTSTR fn)
286 {
287     /*ask the user if they want to override*/
288     INT res;
289     ConOutResPrintf(STRING_COPY_HELP1, fn);
290     res = FilePromptYNA (0);
291     return res;
292 }
293 
294 /* The following lines of copy were written by someone else
295 (most likely Eric Kohl) and it was taken from ren.c */
296 static void
297 BuildFileName(
298     LPTSTR pszSource,
299     LPTSTR pszTarget,
300     LPTSTR pszOutput)
301 {
302     /* build destination file name */
303     while (*pszTarget != 0)
304     {
305         if (*pszTarget == _T('*'))
306         {
307             pszTarget++;
308             while ((*pszSource != 0) && (*pszSource != *pszTarget))
309             {
310                 *pszOutput++ = *pszSource++;
311             }
312         }
313         else if (*pszTarget == _T('?'))
314         {
315             pszTarget++;
316             if (*pszSource != 0)
317             {
318                 *pszOutput++ = *pszSource++;
319             }
320         }
321         else
322         {
323             *pszOutput++ = *pszTarget++;
324             if (*pszSource != 0)
325                 pszSource++;
326         }
327     }
328 
329     *pszOutput = 0;
330 }
331 
332 INT cmd_copy(LPTSTR param)
333 {
334     LPTSTR *arg;
335     INT argc, i, nFiles, nOverwrite = 0, nSrc = -1, nDes = -1;
336     /* this is the path up to the folder of the src and dest ie C:\windows\ */
337     TCHAR szDestPath[MAX_PATH];
338     TCHAR szSrcPath[MAX_PATH];
339     DWORD dwFlags = 0;
340     /* If this is the type of copy where we are adding files */
341     BOOL bAppend = FALSE;
342     WIN32_FIND_DATA findBuffer;
343     HANDLE hFile = NULL;
344     BOOL bTouch = FALSE;
345     /* Pointer to keep track of how far through the append input(file1+file2+file3) we are */
346     TCHAR  * appendPointer = _T("\0");
347     /* The full path to src and dest.  This has drive letter, folders, and filename */
348     TCHAR tmpDestPath[MAX_PATH];
349     TCHAR tmpSrcPath[MAX_PATH];
350     /* A bool to know whether or not the destination name will be taken from the input */
351     BOOL bSrcName = FALSE;
352     /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
353     TCHAR * UseThisName;
354     /* for CMDCOPY env */
355     TCHAR *evar;
356     int size;
357     TCHAR * szTouch;
358     BOOL bHasWildcard, bDone = FALSE, bMoreFiles = FALSE;
359     /* Used for something like "copy c*.exe d*.exe" */
360     BOOL bMultipleSource = FALSE, bMultipleDest = FALSE;
361 
362 
363     /* Show help/usage info */
364     if (!_tcsncmp(param, _T("/?"), 2))
365     {
366         ConOutResPaging(TRUE, STRING_COPY_HELP2);
367         return 0;
368     }
369 
370     nErrorLevel = 0;
371 
372     /* Get the env variable value if it exists */
373     evar = cmd_alloc(512 * sizeof(TCHAR));
374     if (evar == NULL)
375         size = 0;
376     else
377         size = GetEnvironmentVariable (_T("COPYCMD"), evar, 512);
378 
379     if (size > 512)
380     {
381         TCHAR *old_evar = evar;
382         evar = cmd_realloc(evar,size * sizeof(TCHAR) );
383         if (evar!=NULL)
384             size = GetEnvironmentVariable (_T("COPYCMD"), evar, size);
385         else
386         {
387             size=0;
388             evar = old_evar;
389         }
390     }
391 
392     /* check see if we did get any env variable */
393     if (size != 0)
394     {
395         int t = 0;
396 
397         /* scan and set the flags */
398         for (t = 0; t < size; t++)
399         {
400             if (_tcsncicmp(_T("/A"),&evar[t],2) == 0)
401             {
402                 dwFlags |=COPY_ASCII;
403                 t++;
404             }
405             else if (_tcsncicmp(_T("/B"),&evar[t],2) == 0)
406             {
407                 dwFlags |= COPY_BINARY;
408                 t++;
409             }
410             else if (_tcsncicmp(_T("/D"),&evar[t],2) == 0)
411             {
412                 dwFlags |= COPY_DECRYPT;
413                 t++;
414             }
415             else if (_tcsncicmp(_T("/V"),&evar[t],2) == 0)
416             {
417                 dwFlags |= COPY_VERIFY;
418                 t++;
419             }
420             else if (_tcsncicmp(_T("/N"),&evar[t],2) == 0)
421             {
422                 dwFlags |= COPY_SHORTNAME;
423                 t++;
424             }
425             else if (_tcsncicmp(_T("/Y"),&evar[t],2) == 0)
426             {
427                 dwFlags |= COPY_NO_PROMPT;
428                 t++;
429             }
430             else if (_tcsncicmp(_T("/-Y"),&evar[t],3) == 0)
431             {
432                 dwFlags |= COPY_PROMPT;
433                 t+=2;
434             }
435             else if (_tcsncicmp(_T("/Z"),&evar[t],2) == 0)
436             {
437                 dwFlags |= COPY_PROMPT;
438                 t++;
439             }
440         }
441     }
442     cmd_free(evar);
443 
444 
445     /* Split the user input into array */
446     arg = split(param, &argc, FALSE, TRUE);
447     nFiles = argc;
448 
449     /* Read switches and count files */
450     for (i = 0; i < argc; i++)
451     {
452         if (*arg[i] == _T('/'))
453         {
454             if (_tcslen(arg[i]) >= 2)
455             {
456                 switch (_totupper(arg[i][1]))
457                 {
458                     case _T('A'):
459                         dwFlags |= COPY_ASCII;
460                         break;
461 
462                     case _T('B'):
463                         dwFlags |= COPY_BINARY;
464                         break;
465 
466                     case _T('D'):
467                         dwFlags |= COPY_DECRYPT;
468                         break;
469 
470                     case _T('V'):
471                         dwFlags |= COPY_VERIFY;
472                         break;
473 
474                     case _T('N'):
475                         dwFlags |= COPY_SHORTNAME;
476                         break;
477 
478                     case _T('Y'):
479                         dwFlags |= COPY_NO_PROMPT;
480                         dwFlags &= ~COPY_PROMPT;
481                         break;
482 
483                     case _T('-'):
484                         if (_tcslen(arg[i]) >= 3)
485                         {
486                             if (_totupper(arg[i][2]) == _T('Y'))
487                             {
488                                 dwFlags &= ~COPY_NO_PROMPT;
489                                 dwFlags |= COPY_PROMPT;
490                             }
491                         }
492 
493                         break;
494 
495                     case _T('Z'):
496                         dwFlags |= COPY_RESTART;
497                         break;
498 
499                     default:
500                         /* Invalid switch */
501                         ConOutResPrintf(STRING_ERROR_INVALID_SWITCH, _totupper(arg[i][1]));
502                         nErrorLevel = 1;
503                         freep (arg);
504                         return 1;
505                         break;
506                 }
507             }
508             /* If it was a switch, subtract from total arguments */
509             nFiles--;
510         }
511         else
512         {
513             /* If it isn't a switch then it is the source or destination */
514             if (nSrc == -1)
515             {
516                 nSrc = i;
517             }
518             else if (*arg[i] == _T('+'))
519             {
520                 /* Next file should be appended */
521                 bMoreFiles = TRUE;
522                 nFiles -= 1;
523             }
524             else if (bMoreFiles)
525             {
526                 /* Add this file to the source string
527                    this way we can do all checks
528                     directly on source string later on */
529                 TCHAR * ptr;
530                 int length = (_tcslen(arg[nSrc]) + _tcslen(arg[i]) + 2) * sizeof(TCHAR);
531                 ptr = cmd_alloc(length);
532                 if (ptr)
533                 {
534                     _tcscpy(ptr, arg[nSrc]);
535                     _tcscat(ptr, _T("|"));
536                     _tcscat(ptr, arg[i]);
537                     cmd_free(arg[nSrc]);
538                     arg[nSrc] = ptr;
539                     nFiles -= 1;
540                 }
541 
542                 bMoreFiles = FALSE;
543             }
544             else if (nDes == -1)
545             {
546                 nDes = i;
547             }
548         }
549     }
550 
551     /* keep quiet within batch files */
552     if (bc != NULL)
553     {
554         dwFlags |= COPY_NO_PROMPT;
555         dwFlags &= ~COPY_PROMPT;
556     }
557 
558     if (nFiles < 1)
559     {
560         /* There are not enough files, there has to be at least 1 */
561         ConOutResPuts(STRING_ERROR_REQ_PARAM_MISSING);
562         freep(arg);
563         return 1;
564     }
565 
566     if (nFiles > 2)
567     {
568         /* There are too many file names in command */
569         ConErrResPrintf(STRING_ERROR_TOO_MANY_PARAMETERS,_T(""));
570         nErrorLevel = 1;
571         freep(arg);
572         return 1;
573     }
574 
575     if ((_tcschr(arg[nSrc], _T('|')) != NULL) ||
576         (_tcschr(arg[nSrc], _T('*')) != NULL) ||
577         (_tcschr(arg[nSrc], _T('?')) != NULL) ||
578         IsExistingDirectory(arg[nSrc]))
579     {
580         bMultipleSource = TRUE;
581     }
582 
583     /* Reuse the number of files variable */
584     nFiles = 0;
585 
586     /* Check if no destination argument is passed */
587     if (nDes == -1)
588     {
589         /* If no destination was entered then just use
590         the current directory as the destination */
591         GetCurrentDirectory(ARRAYSIZE(szDestPath), szDestPath);
592     }
593     else
594     {
595         /* Check if the destination is 'x:' */
596         if ((arg[nDes][1] == _T(':')) && (arg[nDes][2] == _T('\0')))
597         {
598             GetRootPath(arg[nDes], szDestPath, ARRAYSIZE(szDestPath));
599         }
600         else
601         {
602             /* If the user entered two file names then form the full string path */
603             GetFullPathName(arg[nDes], ARRAYSIZE(szDestPath), szDestPath, NULL);
604         }
605 
606         /* Make sure there is an ending slash to the path if the dest is a folder */
607         if ((_tcschr(szDestPath, _T('*')) == NULL) &&
608             IsExistingDirectory(szDestPath))
609         {
610             bMultipleDest = TRUE;
611             if (szDestPath[_tcslen(szDestPath) -  1] != _T('\\'))
612                 _tcscat(szDestPath, _T("\\"));
613         }
614 
615         /* Check if the destination uses wildcards */
616         if ((_tcschr(arg[nDes], _T('*')) != NULL) ||
617             (_tcschr(arg[nDes], _T('?')) != NULL))
618         {
619             bMultipleDest = TRUE;
620         }
621     }
622 
623     if (nDes != -1) /* Append files only when there is a destination */
624     {
625         if (bMultipleSource && !bMultipleDest)
626         {
627             /* We have multiple source files, but not multiple destination
628                files. This means we are appending the source files. */
629             bAppend = TRUE;
630             if (_tcschr(arg[nSrc], _T('|')) != NULL)
631                 appendPointer = arg[nSrc];
632         }
633     }
634 
635     /* Save the name the user entered */
636     UseThisName = _tcsrchr(szDestPath,_T('\\'));
637     if (UseThisName)
638     {
639         /* Split the name from the path */
640         *UseThisName++ = _T('\0');
641 
642         /* Check if the dest path ends with '\*' or '\' */
643         if (((UseThisName[0] == _T('*')) && (UseThisName[1] == _T('\0'))) ||
644             (UseThisName[0] == _T('\0')))
645         {
646             /* In this case we will be using the same name as the source file
647             for the destination file because destination is a folder */
648             bSrcName = TRUE;
649             UseThisName = NULL;
650         }
651     }
652     else
653     {
654         /* Something's seriously wrong! */
655         UseThisName = szDestPath;
656     }
657 
658     do
659     {
660         /* Get the full string of the path to the source file */
661         if (_tcschr(arg[nSrc], _T('|')) != NULL)
662         {
663             /* Reset the source path */
664             szSrcPath[0] = _T('\0');
665 
666             /* Loop through the source file name and copy all
667                the chars one at a time until we reach the separator */
668             while(TRUE)
669             {
670                 if (appendPointer[0] == _T('|'))
671                 {
672                     /* Skip the | and go to the next file name */
673                     appendPointer++;
674                     break;
675                 }
676                 else if (appendPointer[0] == _T('\0'))
677                 {
678                     bDone = TRUE;
679                     break;
680                 }
681 
682                 _tcsncat(szSrcPath, appendPointer, 1);
683                 appendPointer++;
684             }
685 
686             if (_tcschr(arg[nSrc], _T(',')) != NULL)
687             {
688                 /* Only time there is a , in the source is when they are using touch
689                    Cant have a destination and can only have on ,, at the end of the string
690                    Cant have more than one file name */
691                 szTouch = _tcsstr(arg[nSrc], _T("|"));
692                 if (_tcsncmp(szTouch,_T("|,,\0"), 4) || (nDes != -1))
693                 {
694                     ConErrResPrintf(STRING_ERROR_INVALID_PARAM_FORMAT,arg[nSrc]);
695                     nErrorLevel = 1;
696                     freep (arg);
697                     return 1;
698                 }
699                 bTouch = TRUE;
700                 bDone = TRUE;
701             }
702         }
703         else
704         {
705             bDone = TRUE;
706             _tcscpy(szSrcPath, arg[nSrc]);
707         }
708 
709         /* "x:" is not a valid source path format. */
710         if ((szSrcPath[1] == _T(':')) && (szSrcPath[2] == _T('\0')))
711         {
712             ConOutPrintf(_T("%s\n"), szSrcPath);
713             ConOutFormatMessage(ERROR_FILE_NOT_FOUND, szSrcPath);
714             nErrorLevel = 1;
715             break;
716         }
717 
718 
719         /* From this point on, we can assume that the shortest path is
720            3 letters long and that would be [DriveLetter]:\ */
721 
722         /* Check if the path has a wildcard */
723         bHasWildcard = (_tcschr(szSrcPath, _T('*')) != NULL);
724 
725         /* If there is no * in the path name and it is a folder then we will
726            need to add a wildcard to the pathname so FindFirstFile comes up
727            with all the files in that folder */
728         if (!bHasWildcard && IsExistingDirectory(szSrcPath))
729         {
730             /* If it doesnt have a \ at the end already then on needs to be added */
731             if (szSrcPath[_tcslen(szSrcPath) -  1] != _T('\\'))
732                 _tcscat(szSrcPath, _T("\\"));
733             _tcscat(szSrcPath, _T("*"));
734             bHasWildcard = TRUE;
735         }
736 
737         /* If the path ends with '\' add a wildcard at the end */
738         if (szSrcPath[_tcslen(szSrcPath) -  1] == _T('\\'))
739         {
740             _tcscat(szSrcPath, _T("*"));
741             bHasWildcard = TRUE;
742         }
743 
744         /* Get a list of all the files */
745         hFile = FindFirstFile(szSrcPath, &findBuffer);
746 
747         /* If we could not open the file handle, print out the error */
748         if (hFile == INVALID_HANDLE_VALUE)
749         {
750             /* only print source name when more than one file */
751             if (bMultipleSource)
752                 ConOutPrintf(_T("%s\n"), szSrcPath);
753 
754             ConOutFormatMessage(GetLastError(), szSrcPath);
755             freep(arg);
756             nErrorLevel = 1;
757             return 1;
758         }
759 
760         /* Strip the paths back to the folder they are in */
761         for (i = (_tcslen(szSrcPath) -  1); i > -1; i--)
762         {
763             if (szSrcPath[i] != _T('\\'))
764                 szSrcPath[i] = _T('\0');
765             else
766                 break;
767         }
768 
769         do
770         {
771             /* Check Breaker */
772             if (CheckCtrlBreak(BREAK_INPUT))
773             {
774                 FindClose(hFile);
775                 freep(arg);
776                 return 1;
777             }
778 
779             /* Set the override to yes each new file */
780             nOverwrite = 1;
781 
782             /* Ignore the . and .. files */
783             if (!_tcscmp(findBuffer.cFileName, _T("."))  ||
784                 !_tcscmp(findBuffer.cFileName, _T("..")) ||
785                 findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
786             {
787                 continue;
788             }
789 
790             /* Copy the base folder over to a tmp string */
791             _tcscpy(tmpDestPath, szDestPath);
792             _tcscat(tmpDestPath, _T("\\"));
793 
794             /* Can't put a file into a folder that isn't there */
795             if (_tcscmp(tmpDestPath, _T("\\\\.\\")) &&
796                 !IsExistingDirectory(tmpDestPath))
797             {
798                 FindClose(hFile);
799                 ConOutFormatMessage(GetLastError(), szSrcPath);
800                 freep(arg);
801                 nErrorLevel = 1;
802                 return 1;
803             }
804 
805             /* Copy over the destination path name */
806             if (bSrcName)
807                 _tcscat(tmpDestPath, findBuffer.cFileName);
808             else
809             {
810                 /* If there is no wildcard, use the name the user entered */
811                 if ((_tcschr(UseThisName, _T('*')) == NULL) &&
812                     (_tcschr(UseThisName, _T('?')) == NULL))
813                 {
814                     _tcscat(tmpDestPath, UseThisName);
815                 }
816                 else
817                 {
818                     TCHAR DoneFile[MAX_PATH];
819 
820                     BuildFileName(findBuffer.cFileName,
821                                   UseThisName,
822                                   DoneFile);
823 
824 
825                     /* Add the filename to the tmp string path */
826                     _tcscat(tmpDestPath, DoneFile);
827                 }
828             }
829 
830             /* Build the string path to the source file */
831             _tcscpy(tmpSrcPath,szSrcPath);
832             _tcscat (tmpSrcPath, findBuffer.cFileName);
833 
834             /* Check to see if the file is the same file */
835             if (!bTouch && !_tcscmp(tmpSrcPath, tmpDestPath))
836             {
837                 ConOutResPrintf(STRING_COPY_ERROR2);
838 
839                 nErrorLevel = 1;
840                 break;
841             }
842 
843             /* only print source name when more than one file */
844             if (bMultipleSource)
845                 ConOutPrintf(_T("%s\n"), tmpSrcPath);
846 
847             /* Handle any overriding / prompting that needs to be done */
848             if (((!(dwFlags & COPY_NO_PROMPT) && IsExistingFile (tmpDestPath)) || dwFlags & COPY_PROMPT) && !bTouch)
849                 nOverwrite = CopyOverwrite(tmpDestPath);
850             if (nOverwrite == PROMPT_NO || nOverwrite == PROMPT_BREAK)
851                 continue;
852             if (nOverwrite == PROMPT_ALL || (nOverwrite == PROMPT_YES && bAppend))
853                 dwFlags |= COPY_NO_PROMPT;
854 
855             /* Tell whether the copy was successful or not */
856             if (copy(tmpSrcPath,tmpDestPath, bAppend, dwFlags, bTouch))
857             {
858                 nFiles++;
859             }
860             else
861             {
862                 /* print out the error message */
863                 ConOutResPrintf(STRING_COPY_ERROR3);
864                 ConOutFormatMessage (GetLastError(), szSrcPath);
865                 nErrorLevel = 1;
866             }
867 
868         /* Loop through all wildcard files */
869         } while (FindNextFile(hFile, &findBuffer));
870 
871         FindClose(hFile);
872 
873     /* Loop through all files in src string with a + */
874     } while (!bDone);
875 
876     /* print out the number of files copied */
877     ConOutResPrintf(STRING_COPY_FILE, bAppend ? 1 : nFiles);
878 
879     if (arg != NULL)
880         freep(arg);
881 
882     return 0;
883 }
884 
885 #endif /* INCLUDE_CMD_COPY */
886