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 Impleneted */
43 COPY_SHORTNAME
= 0x010, /* /N : Dummy, Never will be Impleneted */
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 /* Used when something like "copy c*.exe d*.exe" during the process of
345 figuring out the new name */
346 /* Pointer to keep track of how far through the append input(file1+file2+file3) we are */
347 TCHAR
* appendPointer
= _T("\0");
348 /* The full path to src and dest. This has drive letter, folders, and filename */
349 TCHAR tmpDestPath
[MAX_PATH
];
350 TCHAR tmpSrcPath
[MAX_PATH
];
351 /* A bool on weather or not the destination name will be taking from the input */
352 BOOL bSrcName
= FALSE
;
353 /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
355 /* for CMDCOPY env */
359 BOOL bHasWildcard
, bDone
= FALSE
, bMoreFiles
= FALSE
;
360 BOOL bMultipleSource
= FALSE
, bMultipleDest
= FALSE
;
363 /* Show help/usage info */
364 if (!_tcsncmp(param
, _T("/?"), 2))
366 ConOutResPaging(TRUE
, STRING_COPY_HELP2
);
372 /* Get the envor value if it exists */
373 evar
= cmd_alloc(512 * sizeof(TCHAR
));
377 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, 512);
381 TCHAR
*old_evar
= evar
;
382 evar
= cmd_realloc(evar
,size
* sizeof(TCHAR
) );
384 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, size
);
392 /* check see if we did get any env variable */
397 /* scan and set the flags */
398 for (t
= 0; t
< size
; t
++)
400 if (_tcsncicmp(_T("/A"),&evar
[t
],2) == 0)
402 dwFlags
|=COPY_ASCII
;
405 else if (_tcsncicmp(_T("/B"),&evar
[t
],2) == 0)
407 dwFlags
|= COPY_BINARY
;
410 else if (_tcsncicmp(_T("/D"),&evar
[t
],2) == 0)
412 dwFlags
|= COPY_DECRYPT
;
415 else if (_tcsncicmp(_T("/V"),&evar
[t
],2) == 0)
417 dwFlags
|= COPY_VERIFY
;
420 else if (_tcsncicmp(_T("/N"),&evar
[t
],2) == 0)
422 dwFlags
|= COPY_SHORTNAME
;
425 else if (_tcsncicmp(_T("/Y"),&evar
[t
],2) == 0)
427 dwFlags
|= COPY_NO_PROMPT
;
430 else if (_tcsncicmp(_T("/-Y"),&evar
[t
],3) == 0)
432 dwFlags
|= COPY_PROMPT
;
435 else if (_tcsncicmp(_T("/Z"),&evar
[t
],2) == 0)
437 dwFlags
|= COPY_PROMPT
;
445 /* Split the user input into array */
446 arg
= split(param
, &argc
, FALSE
, TRUE
);
449 /* Read switches and count files */
450 for (i
= 0; i
< argc
; i
++)
452 if (*arg
[i
] == _T('/'))
454 if (_tcslen(arg
[i
]) >= 2)
456 switch (_totupper(arg
[i
][1]))
459 dwFlags
|= COPY_ASCII
;
463 dwFlags
|= COPY_BINARY
;
467 dwFlags
|= COPY_DECRYPT
;
471 dwFlags
|= COPY_VERIFY
;
475 dwFlags
|= COPY_SHORTNAME
;
479 dwFlags
|= COPY_NO_PROMPT
;
480 dwFlags
&= ~COPY_PROMPT
;
484 if (_tcslen(arg
[i
]) >= 3)
485 if (_totupper(arg
[i
][2]) == _T('Y'))
487 dwFlags
&= ~COPY_NO_PROMPT
;
488 dwFlags
|= COPY_PROMPT
;
494 dwFlags
|= COPY_RESTART
;
499 ConOutResPrintf(STRING_ERROR_INVALID_SWITCH
, _totupper(arg
[i
][1]));
506 /* If it was a switch, subtract from total arguments */
511 /* If it isn't a switch then it is the source or destination */
516 else if (*arg
[i
] == _T('+'))
518 /* Next file should be appended */
524 /* Add this file to the source string
525 this way we can do all checks
526 directly on source string later on */
528 int length
= (_tcslen(arg
[nSrc
]) + _tcslen(arg
[i
]) + 2) * sizeof(TCHAR
);
529 ptr
= cmd_alloc(length
);
532 _tcscpy(ptr
, arg
[nSrc
]);
533 _tcscat(ptr
, _T("|"));
534 _tcscat(ptr
, arg
[i
]);
549 /* keep quiet within batch files */
552 dwFlags
|= COPY_NO_PROMPT
;
553 dwFlags
&= ~COPY_PROMPT
;
558 /* There are not enough files, there has to be at least 1 */
559 ConOutResPuts(STRING_ERROR_REQ_PARAM_MISSING
);
566 /* There are too many file names in command */
567 ConErrResPrintf(STRING_ERROR_TOO_MANY_PARAMETERS
,_T(""));
573 if ((_tcschr(arg
[nSrc
], _T('|')) != NULL
) ||
574 (_tcschr(arg
[nSrc
], _T('*')) != NULL
) ||
575 (_tcschr(arg
[nSrc
], _T('?')) != NULL
) ||
576 IsExistingDirectory(arg
[nSrc
]))
578 bMultipleSource
= TRUE
;
581 /* Reusing the number of files variable */
584 /* Check if no destination argument is passed */
587 /* If no destination was entered then just use
588 the current directory as the destination */
589 GetCurrentDirectory(MAX_PATH
, szDestPath
);
593 /* Check if the destination is 'x:' */
594 if ((arg
[nDes
][1] == _T(':')) && (arg
[nDes
][2] == _T('\0')))
596 GetRootPath(arg
[nDes
], szDestPath
, MAX_PATH
);
600 /* If the user entered two file names then form the full string path */
601 GetFullPathName(arg
[nDes
], MAX_PATH
, szDestPath
, NULL
);
604 /* Make sure there is an ending slash to the path if the dest is a folder */
605 if ((_tcschr(szDestPath
, _T('*')) == NULL
) &&
606 IsExistingDirectory(szDestPath
))
608 bMultipleDest
= TRUE
;
609 if (szDestPath
[_tcslen(szDestPath
) - 1] != _T('\\'))
610 _tcscat(szDestPath
, _T("\\"));
613 /* Check if the destination uses wildcards */
614 if ((_tcschr(arg
[nDes
], _T('*')) != NULL
) ||
615 (_tcschr(arg
[nDes
], _T('?')) != NULL
))
617 bMultipleDest
= TRUE
;
621 if (nDes
!= -1) /* you can only append files when there is a destination */
623 if (bMultipleSource
&& !bMultipleDest
)
625 /* We have multiple source files, but not multiple destination
626 files. This means we are appending the soruce files. */
628 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
629 appendPointer
= arg
[nSrc
];
633 /* Save the name the user entered */
634 UseThisName
= _tcsrchr(szDestPath
,_T('\\'));
637 /* Split the name from the path */
638 *UseThisName
++ = _T('\0');
640 /* Check if the dest path ends with '\*' or '\' */
641 if (((UseThisName
[0] == _T('*')) && (UseThisName
[1] == _T('\0'))) ||
642 (UseThisName
[0] == _T('\0')))
644 /* In this case we will be using the same name as the source file
645 for the destination file because destination is a folder */
652 /* Something's seriously wrong! */
653 UseThisName
= szDestPath
;
658 /* Get the full string of the path to the source file */
659 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
661 /* Reset the source path */
662 szSrcPath
[0] = _T('\0');
664 /* Loop through the source file name and copy all
665 the chars one at a time until it gets too + */
668 if (appendPointer
[0] == _T('|'))
670 /* Skip the | and go to the next file name */
674 else if (appendPointer
[0] == _T('\0'))
680 _tcsncat(szSrcPath
, appendPointer
, 1);
684 if (_tcschr(arg
[nSrc
], _T(',')) != NULL
)
686 /* Only time there is a , in the source is when they are using touch
687 Cant have a destination and can only have on ,, at the end of the string
688 Cant have more then one file name */
689 szTouch
= _tcsstr(arg
[nSrc
], _T("|"));
690 if (_tcsncmp(szTouch
,_T("|,,\0"), 4) || (nDes
!= -1))
692 ConErrResPrintf(STRING_ERROR_INVALID_PARAM_FORMAT
,arg
[nSrc
]);
704 _tcscpy(szSrcPath
, arg
[nSrc
]);
707 /* "x:" is not a valid source path format. */
708 if ((szSrcPath
[1] == _T(':')) && (szSrcPath
[2] == _T('\0')))
710 ConOutPrintf(_T("%s\n"), szSrcPath
);
711 ConOutFormatMessage(ERROR_FILE_NOT_FOUND
, szSrcPath
);
717 /* From this point on, we can assume that the shortest path is 3 letters long
718 and that would be [DriveLetter]:\ */
720 /* Check if the path has a wildcard */
721 bHasWildcard
= (_tcschr(szSrcPath
, _T('*')) != NULL
);
723 /* If there is no * in the path name and it is a folder then we will
724 need to add a wildcard to the pathname so FindFirstFile comes up
725 with all the files in that folder */
726 if (!bHasWildcard
&& IsExistingDirectory(szSrcPath
))
728 /* If it doesnt have a \ at the end already then on needs to be added */
729 if (szSrcPath
[_tcslen(szSrcPath
) - 1] != _T('\\'))
730 _tcscat(szSrcPath
, _T("\\"));
731 _tcscat(szSrcPath
, _T("*"));
735 /* If the path ends with '\' add a wildcard at the end */
736 if (szSrcPath
[_tcslen(szSrcPath
) - 1] == _T('\\'))
738 _tcscat(szSrcPath
, _T("*"));
742 /* Get a list of all the files */
743 hFile
= FindFirstFile(szSrcPath
, &findBuffer
);
745 /* If it couldnt open the file handle, print out the error */
746 if (hFile
== INVALID_HANDLE_VALUE
)
748 /* only print source name when more then one file */
750 ConOutPrintf(_T("%s\n"), szSrcPath
);
752 ConOutFormatMessage(GetLastError(), szSrcPath
);
758 /* Strip the paths back to the folder they are in */
759 for (i
= (_tcslen(szSrcPath
) - 1); i
> -1; i
--)
760 if (szSrcPath
[i
] != _T('\\'))
761 szSrcPath
[i
] = _T('\0');
768 if (CheckCtrlBreak(BREAK_INPUT
))
775 /* Set the override to yes each new file */
778 /* Ignore the . and .. files */
779 if (!_tcscmp(findBuffer
.cFileName
, _T(".")) ||
780 !_tcscmp(findBuffer
.cFileName
, _T("..")) ||
781 findBuffer
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
786 /* Copy the base folder over to a tmp string */
787 _tcscpy(tmpDestPath
, szDestPath
);
788 _tcscat(tmpDestPath
, _T("\\"));
790 /* Can't put a file into a folder that isn't there */
791 if (_tcscmp(tmpDestPath
, _T("\\\\.\\")) &&
792 !IsExistingDirectory(tmpDestPath
))
795 ConOutFormatMessage(GetLastError(), szSrcPath
);
801 /* Copy over the destination path name */
803 _tcscat(tmpDestPath
, findBuffer
.cFileName
);
806 /* If there is no wildcard you can use the name the user entered */
807 if ((_tcschr(UseThisName
, _T('*')) == NULL
) &&
808 (_tcschr(UseThisName
, _T('?')) == NULL
))
810 _tcscat(tmpDestPath
, UseThisName
);
814 TCHAR DoneFile
[MAX_PATH
];
816 BuildFileName(findBuffer
.cFileName
,
821 /* Add the filename to the tmp string path */
822 _tcscat(tmpDestPath
, DoneFile
);
826 /* Build the string path to the source file */
827 _tcscpy(tmpSrcPath
,szSrcPath
);
828 _tcscat (tmpSrcPath
, findBuffer
.cFileName
);
830 /* Check to see if the file is the same file */
831 if (!bTouch
&& !_tcscmp(tmpSrcPath
, tmpDestPath
))
833 ConOutResPrintf(STRING_COPY_ERROR2
);
839 /* only print source name when more then one file */
841 ConOutPrintf(_T("%s\n"), tmpSrcPath
);
843 /* Handle any overriding / prompting that needs to be done */
844 if (((!(dwFlags
& COPY_NO_PROMPT
) && IsExistingFile (tmpDestPath
)) || dwFlags
& COPY_PROMPT
) && !bTouch
)
845 nOverwrite
= CopyOverwrite(tmpDestPath
);
846 if (nOverwrite
== PROMPT_NO
|| nOverwrite
== PROMPT_BREAK
)
848 if (nOverwrite
== PROMPT_ALL
|| (nOverwrite
== PROMPT_YES
&& bAppend
))
849 dwFlags
|= COPY_NO_PROMPT
;
851 /* Tell weather the copy was successful or not */
852 if (copy(tmpSrcPath
,tmpDestPath
, bAppend
, dwFlags
, bTouch
))
855 //LoadString(CMD_ModuleHandle, STRING_MOVE_ERROR1, szMsg, RC_STRING_MAX_SIZE);
859 /* print out the error message */
860 ConOutResPrintf(STRING_COPY_ERROR3
);
861 ConOutFormatMessage (GetLastError(), szSrcPath
);
865 /* Loop through all wildcard files */
866 } while (FindNextFile(hFile
, &findBuffer
));
868 /* Loop through all files in src string with a + */
871 /* print out the number of files copied */
872 ConOutResPrintf(STRING_COPY_FILE
, bAppend
? 1 : nFiles
);
874 if (hFile
) FindClose(hFile
);
882 #endif /* INCLUDE_CMD_COPY */