2 * COPY.C -- copy internal command.
7 * 01-Aug-98 (Rob Lake z63rrl@morgan.ucs.mun.ca)
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
14 * 13-Dec-1998 (Eric Kohl)
15 * Added COPY command to CMD.
17 * 26-Jan-1998 (Eric Kohl)
18 * Replaced CRT io functions by Win32 io functions.
20 * 27-Oct-1998 (Eric Kohl)
21 * Disabled prompting when used in batch mode.
23 * 03-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
24 * Remove all hardcode string to En.rc
26 * 13-Jul-2005 (Brandon Turner <turnerb7@msu.edu>)
27 * Rewrite to clean up copy and support wildcard.
29 * 20-Jul-2005 (Brandon Turner <turnerb7@msu.edu>)
30 * Add touch syntax. "copy arp.exe+,,"
31 * Copy command is now completed.
36 #ifdef INCLUDE_CMD_COPY
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 */
51 copy(TCHAR source
[MAX_PATH
],
57 FILETIME srctime
,NewFileTime
;
65 TCHAR TrueDest
[MAX_PATH
];
66 TCHAR TempSrc
[MAX_PATH
];
68 SYSTEMTIME CurrentTime
;
71 if (CheckCtrlBreak(BREAK_INPUT
))
74 TRACE ("checking mode\n");
78 hFileSrc
= CreateFile (source
, GENERIC_WRITE
, FILE_SHARE_READ
,
79 NULL
, OPEN_EXISTING
, 0, NULL
);
80 if (hFileSrc
== INVALID_HANDLE_VALUE
)
82 ConOutResPrintf(STRING_COPY_ERROR1
, source
);
87 GetSystemTime(&CurrentTime
);
88 SystemTimeToFileTime(&CurrentTime
, &NewFileTime
);
89 if (SetFileTime(hFileSrc
,(LPFILETIME
) NULL
, (LPFILETIME
) NULL
, &NewFileTime
))
91 CloseHandle(hFileSrc
);
98 CloseHandle(hFileSrc
);
103 dwAttrib
= GetFileAttributes (source
);
105 hFileSrc
= CreateFile (source
, GENERIC_READ
, FILE_SHARE_READ
,
106 NULL
, OPEN_EXISTING
, 0, NULL
);
107 if (hFileSrc
== INVALID_HANDLE_VALUE
)
109 ConOutResPrintf(STRING_COPY_ERROR1
, source
);
114 TRACE ("getting time\n");
116 GetFileTime (hFileSrc
, &srctime
, NULL
, NULL
);
118 TRACE ("copy: flags has %s\n",
119 lpdwFlags
& COPY_ASCII
? "ASCII" : "BINARY");
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
)
125 GetEnvironmentVariable(_T("TEMP"),TempSrc
,MAX_PATH
);
126 _tcscat(TempSrc
,_T("\\"));
127 FileName
= _tcsrchr(source
,_T('\\'));
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
136 _tcscat(TempSrc
,_T(".decrypt"));
137 if (!CopyFileEx(source
, TempSrc
, NULL
, NULL
, FALSE
, COPY_FILE_ALLOW_DECRYPTED_DESTINATION
))
139 CloseHandle (hFileSrc
);
143 _tcscpy(source
, TempSrc
);
147 if (lpdwFlags
& COPY_RESTART
)
149 _tcscpy(TrueDest
, dest
);
150 GetEnvironmentVariable(_T("TEMP"),dest
,MAX_PATH
);
151 _tcscat(dest
,_T("\\"));
152 FileName
= _tcsrchr(TrueDest
,_T('\\'));
154 _tcscat(dest
,FileName
);
158 if (!IsExistingFile (dest
))
160 TRACE ("opening/creating\n");
162 CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, 0, NULL
);
166 TRACE ("SetFileAttributes (%s, FILE_ATTRIBUTE_NORMAL);\n", debugstr_aw(dest
));
167 SetFileAttributes (dest
, FILE_ATTRIBUTE_NORMAL
);
169 TRACE ("DeleteFile (%s);\n", debugstr_aw(dest
));
172 hFileDest
= CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, 0, NULL
);
176 LONG lFilePosHigh
= 0;
178 if (!_tcscmp (dest
, source
))
180 CloseHandle (hFileSrc
);
184 TRACE ("opening/appending\n");
185 SetFileAttributes (dest
, FILE_ATTRIBUTE_NORMAL
);
188 CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
, 0, NULL
);
190 /* Move to end of file to start writing */
191 SetFilePointer (hFileDest
, 0, &lFilePosHigh
,FILE_END
);
195 if (hFileDest
== INVALID_HANDLE_VALUE
)
197 CloseHandle (hFileSrc
);
198 ConOutResPuts(STRING_ERROR_PATH_NOT_FOUND
);
203 /* A page-aligned buffer usually give more speed */
204 buffer
= VirtualAlloc(NULL
, BUFF_SIZE
, MEM_COMMIT
, PAGE_READWRITE
);
207 CloseHandle (hFileDest
);
208 CloseHandle (hFileSrc
);
209 ConOutResPuts(STRING_ERROR_OUT_OF_MEMORY
);
216 ReadFile (hFileSrc
, buffer
, BUFF_SIZE
, &dwRead
, NULL
);
217 if (lpdwFlags
& COPY_ASCII
)
219 LPBYTE pEof
= memchr(buffer
, 0x1A, dwRead
);
223 dwRead
= pEof
-buffer
+1;
231 WriteFile (hFileDest
, buffer
, dwRead
, &dwWritten
, NULL
);
232 if (dwWritten
!= dwRead
|| CheckCtrlBreak(BREAK_INPUT
))
234 ConOutResPuts(STRING_COPY_ERROR3
);
236 VirtualFree (buffer
, 0, MEM_RELEASE
);
237 CloseHandle (hFileDest
);
238 CloseHandle (hFileSrc
);
245 TRACE ("setting time\n");
246 SetFileTime (hFileDest
, &srctime
, NULL
, NULL
);
248 if ((lpdwFlags
& COPY_ASCII
) && !bEof
)
250 /* we're dealing with ASCII files! */
252 TRACE ("appending ^Z\n");
253 WriteFile (hFileDest
, buffer
, sizeof(CHAR
), &dwWritten
, NULL
);
256 VirtualFree (buffer
, 0, MEM_RELEASE
);
257 CloseHandle (hFileDest
);
258 CloseHandle (hFileSrc
);
260 TRACE ("setting mode\n");
261 SetFileAttributes (dest
, dwAttrib
);
263 /* Now finish off the copy if needed with CopyFileEx */
264 if (lpdwFlags
& COPY_RESTART
)
266 if (!CopyFileEx(dest
, TrueDest
, NULL
, NULL
, FALSE
, COPY_FILE_RESTARTABLE
))
272 /* Take care of file in the temp folder */
277 if (lpdwFlags
& COPY_DECRYPT
)
284 static INT
CopyOverwrite (LPTSTR fn
)
286 /*ask the user if they want to override*/
288 ConOutResPrintf(STRING_COPY_HELP1
, fn
);
289 res
= FilePromptYNA (0);
293 /* The following lines of copy were written by someone else
294 (most likely Eric Kohl) and it was taken from ren.c */
301 /* build destination file name */
302 while (*pszTarget
!= 0)
304 if (*pszTarget
== _T('*'))
307 while ((*pszSource
!= 0) && (*pszSource
!= *pszTarget
))
309 *pszOutput
++ = *pszSource
++;
312 else if (*pszTarget
== _T('?'))
317 *pszOutput
++ = *pszSource
++;
322 *pszOutput
++ = *pszTarget
++;
331 INT
cmd_copy(LPTSTR param
)
334 INT argc
, i
, nFiles
, nOverwrite
= 0, nSrc
= -1, nDes
= -1;
335 /* this is the path up to the folder of the src and dest ie C:\windows\ */
336 TCHAR szDestPath
[MAX_PATH
];
337 TCHAR szSrcPath
[MAX_PATH
];
339 /* If this is the type of copy where we are adding files */
340 BOOL bAppend
= FALSE
;
341 WIN32_FIND_DATA findBuffer
;
344 /* Pointer to keep track of how far through the append input(file1+file2+file3) we are */
345 TCHAR
* appendPointer
= _T("\0");
346 /* The full path to src and dest. This has drive letter, folders, and filename */
347 TCHAR tmpDestPath
[MAX_PATH
];
348 TCHAR tmpSrcPath
[MAX_PATH
];
349 /* A bool to know whether or not the destination name will be taken from the input */
350 BOOL bSrcName
= FALSE
;
351 /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
353 /* for CMDCOPY env */
357 BOOL bHasWildcard
, bDone
= FALSE
, bMoreFiles
= FALSE
;
358 /* Used for something like "copy c*.exe d*.exe" */
359 BOOL bMultipleSource
= FALSE
, bMultipleDest
= FALSE
;
362 /* Show help/usage info */
363 if (!_tcsncmp(param
, _T("/?"), 2))
365 ConOutResPaging(TRUE
, STRING_COPY_HELP2
);
371 /* Get the env variable value if it exists */
372 evar
= cmd_alloc(512 * sizeof(TCHAR
));
376 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, 512);
380 TCHAR
*old_evar
= evar
;
381 evar
= cmd_realloc(evar
,size
* sizeof(TCHAR
) );
383 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, size
);
391 /* check see if we did get any env variable */
396 /* scan and set the flags */
397 for (t
= 0; t
< size
; t
++)
399 if (_tcsncicmp(_T("/A"),&evar
[t
],2) == 0)
401 dwFlags
|=COPY_ASCII
;
404 else if (_tcsncicmp(_T("/B"),&evar
[t
],2) == 0)
406 dwFlags
|= COPY_BINARY
;
409 else if (_tcsncicmp(_T("/D"),&evar
[t
],2) == 0)
411 dwFlags
|= COPY_DECRYPT
;
414 else if (_tcsncicmp(_T("/V"),&evar
[t
],2) == 0)
416 dwFlags
|= COPY_VERIFY
;
419 else if (_tcsncicmp(_T("/N"),&evar
[t
],2) == 0)
421 dwFlags
|= COPY_SHORTNAME
;
424 else if (_tcsncicmp(_T("/Y"),&evar
[t
],2) == 0)
426 dwFlags
|= COPY_NO_PROMPT
;
429 else if (_tcsncicmp(_T("/-Y"),&evar
[t
],3) == 0)
431 dwFlags
|= COPY_PROMPT
;
434 else if (_tcsncicmp(_T("/Z"),&evar
[t
],2) == 0)
436 dwFlags
|= COPY_PROMPT
;
444 /* Split the user input into array */
445 arg
= split(param
, &argc
, FALSE
, TRUE
);
448 /* Read switches and count files */
449 for (i
= 0; i
< argc
; i
++)
451 if (*arg
[i
] == _T('/'))
453 if (_tcslen(arg
[i
]) >= 2)
455 switch (_totupper(arg
[i
][1]))
458 dwFlags
|= COPY_ASCII
;
462 dwFlags
|= COPY_BINARY
;
466 dwFlags
|= COPY_DECRYPT
;
470 dwFlags
|= COPY_VERIFY
;
474 dwFlags
|= COPY_SHORTNAME
;
478 dwFlags
|= COPY_NO_PROMPT
;
479 dwFlags
&= ~COPY_PROMPT
;
483 if (_tcslen(arg
[i
]) >= 3)
485 if (_totupper(arg
[i
][2]) == _T('Y'))
487 dwFlags
&= ~COPY_NO_PROMPT
;
488 dwFlags
|= COPY_PROMPT
;
495 dwFlags
|= COPY_RESTART
;
500 ConOutResPrintf(STRING_ERROR_INVALID_SWITCH
, _totupper(arg
[i
][1]));
507 /* If it was a switch, subtract from total arguments */
512 /* If it isn't a switch then it is the source or destination */
517 else if (*arg
[i
] == _T('+'))
519 /* Next file should be appended */
525 /* Add this file to the source string
526 this way we can do all checks
527 directly on source string later on */
529 int length
= (_tcslen(arg
[nSrc
]) + _tcslen(arg
[i
]) + 2) * sizeof(TCHAR
);
530 ptr
= cmd_alloc(length
);
533 _tcscpy(ptr
, arg
[nSrc
]);
534 _tcscat(ptr
, _T("|"));
535 _tcscat(ptr
, arg
[i
]);
550 /* keep quiet within batch files */
553 dwFlags
|= COPY_NO_PROMPT
;
554 dwFlags
&= ~COPY_PROMPT
;
559 /* There are not enough files, there has to be at least 1 */
560 ConOutResPuts(STRING_ERROR_REQ_PARAM_MISSING
);
567 /* There are too many file names in command */
568 ConErrResPrintf(STRING_ERROR_TOO_MANY_PARAMETERS
,_T(""));
574 if ((_tcschr(arg
[nSrc
], _T('|')) != NULL
) ||
575 (_tcschr(arg
[nSrc
], _T('*')) != NULL
) ||
576 (_tcschr(arg
[nSrc
], _T('?')) != NULL
) ||
577 IsExistingDirectory(arg
[nSrc
]))
579 bMultipleSource
= TRUE
;
582 /* Reuse the number of files variable */
585 /* Check if no destination argument is passed */
588 /* If no destination was entered then just use
589 the current directory as the destination */
590 GetCurrentDirectory(ARRAYSIZE(szDestPath
), szDestPath
);
594 /* Check if the destination is 'x:' */
595 if ((arg
[nDes
][1] == _T(':')) && (arg
[nDes
][2] == _T('\0')))
597 GetRootPath(arg
[nDes
], szDestPath
, ARRAYSIZE(szDestPath
));
601 /* If the user entered two file names then form the full string path */
602 GetFullPathName(arg
[nDes
], ARRAYSIZE(szDestPath
), szDestPath
, NULL
);
605 /* Make sure there is an ending slash to the path if the dest is a folder */
606 if ((_tcschr(szDestPath
, _T('*')) == NULL
) &&
607 IsExistingDirectory(szDestPath
))
609 bMultipleDest
= TRUE
;
610 if (szDestPath
[_tcslen(szDestPath
) - 1] != _T('\\'))
611 _tcscat(szDestPath
, _T("\\"));
614 /* Check if the destination uses wildcards */
615 if ((_tcschr(arg
[nDes
], _T('*')) != NULL
) ||
616 (_tcschr(arg
[nDes
], _T('?')) != NULL
))
618 bMultipleDest
= TRUE
;
622 if (nDes
!= -1) /* Append files only when there is a destination */
624 if (bMultipleSource
&& !bMultipleDest
)
626 /* We have multiple source files, but not multiple destination
627 files. This means we are appending the source files. */
629 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
630 appendPointer
= arg
[nSrc
];
634 /* Save the name the user entered */
635 UseThisName
= _tcsrchr(szDestPath
,_T('\\'));
638 /* Split the name from the path */
639 *UseThisName
++ = _T('\0');
641 /* Check if the dest path ends with '\*' or '\' */
642 if (((UseThisName
[0] == _T('*')) && (UseThisName
[1] == _T('\0'))) ||
643 (UseThisName
[0] == _T('\0')))
645 /* In this case we will be using the same name as the source file
646 for the destination file because destination is a folder */
653 /* Something's seriously wrong! */
654 UseThisName
= szDestPath
;
659 /* Get the full string of the path to the source file */
660 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
662 /* Reset the source path */
663 szSrcPath
[0] = _T('\0');
665 /* Loop through the source file name and copy all
666 the chars one at a time until we reach the separator */
669 if (appendPointer
[0] == _T('|'))
671 /* Skip the | and go to the next file name */
675 else if (appendPointer
[0] == _T('\0'))
681 _tcsncat(szSrcPath
, appendPointer
, 1);
685 if (_tcschr(arg
[nSrc
], _T(',')) != NULL
)
687 /* Only time there is a , in the source is when they are using touch
688 Cant have a destination and can only have on ,, at the end of the string
689 Cant have more than one file name */
690 szTouch
= _tcsstr(arg
[nSrc
], _T("|"));
691 if (_tcsncmp(szTouch
,_T("|,,\0"), 4) || (nDes
!= -1))
693 ConErrResPrintf(STRING_ERROR_INVALID_PARAM_FORMAT
,arg
[nSrc
]);
705 _tcscpy(szSrcPath
, arg
[nSrc
]);
708 /* "x:" is not a valid source path format. */
709 if ((szSrcPath
[1] == _T(':')) && (szSrcPath
[2] == _T('\0')))
711 ConOutPrintf(_T("%s\n"), szSrcPath
);
712 ConOutFormatMessage(ERROR_FILE_NOT_FOUND
, szSrcPath
);
718 /* From this point on, we can assume that the shortest path is
719 3 letters long and that would be [DriveLetter]:\ */
721 /* Check if the path has a wildcard */
722 bHasWildcard
= (_tcschr(szSrcPath
, _T('*')) != NULL
);
724 /* If there is no * in the path name and it is a folder then we will
725 need to add a wildcard to the pathname so FindFirstFile comes up
726 with all the files in that folder */
727 if (!bHasWildcard
&& IsExistingDirectory(szSrcPath
))
729 /* If it doesnt have a \ at the end already then on needs to be added */
730 if (szSrcPath
[_tcslen(szSrcPath
) - 1] != _T('\\'))
731 _tcscat(szSrcPath
, _T("\\"));
732 _tcscat(szSrcPath
, _T("*"));
736 /* If the path ends with '\' add a wildcard at the end */
737 if (szSrcPath
[_tcslen(szSrcPath
) - 1] == _T('\\'))
739 _tcscat(szSrcPath
, _T("*"));
743 /* Get a list of all the files */
744 hFile
= FindFirstFile(szSrcPath
, &findBuffer
);
746 /* If we could not open the file handle, print out the error */
747 if (hFile
== INVALID_HANDLE_VALUE
)
749 /* only print source name when more than one file */
751 ConOutPrintf(_T("%s\n"), szSrcPath
);
753 ConOutFormatMessage(GetLastError(), szSrcPath
);
759 /* Strip the paths back to the folder they are in */
760 for (i
= (_tcslen(szSrcPath
) - 1); i
> -1; i
--)
762 if (szSrcPath
[i
] != _T('\\'))
763 szSrcPath
[i
] = _T('\0');
771 if (CheckCtrlBreak(BREAK_INPUT
))
778 /* Set the override to yes each new file */
781 /* Ignore the . and .. files */
782 if (!_tcscmp(findBuffer
.cFileName
, _T(".")) ||
783 !_tcscmp(findBuffer
.cFileName
, _T("..")) ||
784 findBuffer
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
789 /* Copy the base folder over to a tmp string */
790 _tcscpy(tmpDestPath
, szDestPath
);
791 _tcscat(tmpDestPath
, _T("\\"));
793 /* Can't put a file into a folder that isn't there */
794 if (_tcscmp(tmpDestPath
, _T("\\\\.\\")) &&
795 !IsExistingDirectory(tmpDestPath
))
798 ConOutFormatMessage(GetLastError(), szSrcPath
);
804 /* Copy over the destination path name */
806 _tcscat(tmpDestPath
, findBuffer
.cFileName
);
809 /* If there is no wildcard, use the name the user entered */
810 if ((_tcschr(UseThisName
, _T('*')) == NULL
) &&
811 (_tcschr(UseThisName
, _T('?')) == NULL
))
813 _tcscat(tmpDestPath
, UseThisName
);
817 TCHAR DoneFile
[MAX_PATH
];
819 BuildFileName(findBuffer
.cFileName
,
824 /* Add the filename to the tmp string path */
825 _tcscat(tmpDestPath
, DoneFile
);
829 /* Build the string path to the source file */
830 _tcscpy(tmpSrcPath
,szSrcPath
);
831 _tcscat (tmpSrcPath
, findBuffer
.cFileName
);
833 /* Check to see if the file is the same file */
834 if (!bTouch
&& !_tcscmp(tmpSrcPath
, tmpDestPath
))
836 ConOutResPrintf(STRING_COPY_ERROR2
);
842 /* only print source name when more than one file */
844 ConOutPrintf(_T("%s\n"), tmpSrcPath
);
846 /* Handle any overriding / prompting that needs to be done */
847 if (((!(dwFlags
& COPY_NO_PROMPT
) && IsExistingFile (tmpDestPath
)) || dwFlags
& COPY_PROMPT
) && !bTouch
)
848 nOverwrite
= CopyOverwrite(tmpDestPath
);
849 if (nOverwrite
== PROMPT_NO
|| nOverwrite
== PROMPT_BREAK
)
851 if (nOverwrite
== PROMPT_ALL
|| (nOverwrite
== PROMPT_YES
&& bAppend
))
852 dwFlags
|= COPY_NO_PROMPT
;
854 /* Tell whether the copy was successful or not */
855 if (copy(tmpSrcPath
,tmpDestPath
, bAppend
, dwFlags
, bTouch
))
861 /* print out the error message */
862 ConOutResPrintf(STRING_COPY_ERROR3
);
863 ConOutFormatMessage (GetLastError(), szSrcPath
);
867 /* Loop through all wildcard files */
868 } while (FindNextFile(hFile
, &findBuffer
));
872 /* Loop through all files in src string with a + */
875 /* print out the number of files copied */
876 ConOutResPrintf(STRING_COPY_FILE
, bAppend
? 1 : nFiles
);
884 #endif /* INCLUDE_CMD_COPY */