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
))
142 _tcscpy(source
, TempSrc
);
146 if(lpdwFlags
& COPY_RESTART
)
148 _tcscpy(TrueDest
, dest
);
149 GetEnvironmentVariable(_T("TEMP"),dest
,MAX_PATH
);
150 _tcscat(dest
,_T("\\"));
151 FileName
= _tcsrchr(TrueDest
,_T('\\'));
153 _tcscat(dest
,FileName
);
157 if (!IsExistingFile (dest
))
159 TRACE ("opening/creating\n");
161 CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, 0, NULL
);
165 TRACE ("SetFileAttributes (%s, FILE_ATTRIBUTE_NORMAL);\n", debugstr_aw(dest
));
166 SetFileAttributes (dest
, FILE_ATTRIBUTE_NORMAL
);
168 TRACE ("DeleteFile (%s);\n", debugstr_aw(dest
));
171 hFileDest
= CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, 0, NULL
);
175 LONG lFilePosHigh
= 0;
177 if (!_tcscmp (dest
, source
))
179 CloseHandle (hFileSrc
);
183 TRACE ("opening/appending\n");
184 SetFileAttributes (dest
, FILE_ATTRIBUTE_NORMAL
);
187 CreateFile (dest
, GENERIC_WRITE
, 0, NULL
, OPEN_EXISTING
, 0, NULL
);
189 /* Move to end of file to start writing */
190 SetFilePointer (hFileDest
, 0, &lFilePosHigh
,FILE_END
);
194 if (hFileDest
== INVALID_HANDLE_VALUE
)
196 CloseHandle (hFileSrc
);
197 ConOutResPuts(STRING_ERROR_PATH_NOT_FOUND
);
202 /* A page-aligned buffer usually give more speed */
203 buffer
= (LPBYTE
)VirtualAlloc(NULL
, BUFF_SIZE
, MEM_COMMIT
, PAGE_READWRITE
);
206 CloseHandle (hFileDest
);
207 CloseHandle (hFileSrc
);
208 ConOutResPuts(STRING_ERROR_OUT_OF_MEMORY
);
215 ReadFile (hFileSrc
, buffer
, BUFF_SIZE
, &dwRead
, NULL
);
216 if (lpdwFlags
& COPY_ASCII
)
218 LPBYTE pEof
= memchr(buffer
, 0x1A, dwRead
);
222 dwRead
= pEof
-buffer
+1;
230 WriteFile (hFileDest
, buffer
, dwRead
, &dwWritten
, NULL
);
231 if (dwWritten
!= dwRead
|| CheckCtrlBreak(BREAK_INPUT
))
233 ConOutResPuts(STRING_COPY_ERROR3
);
235 VirtualFree (buffer
, 0, MEM_RELEASE
);
236 CloseHandle (hFileDest
);
237 CloseHandle (hFileSrc
);
244 TRACE ("setting time\n");
245 SetFileTime (hFileDest
, &srctime
, NULL
, NULL
);
247 if ((lpdwFlags
& COPY_ASCII
) && !bEof
)
249 /* we're dealing with ASCII files! */
251 TRACE ("appending ^Z\n");
252 WriteFile (hFileDest
, buffer
, sizeof(CHAR
), &dwWritten
, NULL
);
255 VirtualFree (buffer
, 0, MEM_RELEASE
);
256 CloseHandle (hFileDest
);
257 CloseHandle (hFileSrc
);
259 TRACE ("setting mode\n");
260 SetFileAttributes (dest
, dwAttrib
);
262 /* Now finish off the copy if needed with CopyFileEx */
263 if(lpdwFlags
& COPY_RESTART
)
265 if(!CopyFileEx(dest
, TrueDest
, NULL
, NULL
, FALSE
, COPY_FILE_RESTARTABLE
))
271 /* Take care of file in the temp folder */
276 if(lpdwFlags
& COPY_DECRYPT
)
283 static INT
CopyOverwrite (LPTSTR fn
)
285 /*ask the user if they want to override*/
287 ConOutResPrintf(STRING_COPY_HELP1
, fn
);
288 res
= FilePromptYNA (0);
292 /* The following lines of copy were written by someone else
293 (most likely Eric Kohl) and it was taken from ren.c */
300 /* build destination file name */
301 while (*pszTarget
!= 0)
303 if (*pszTarget
== _T('*'))
306 while ((*pszSource
!= 0) && (*pszSource
!= *pszTarget
))
308 *pszOutput
++ = *pszSource
++;
311 else if (*pszTarget
== _T('?'))
316 *pszOutput
++ = *pszSource
++;
321 *pszOutput
++ = *pszTarget
++;
330 INT
cmd_copy(LPTSTR param
)
333 INT argc
, i
, nFiles
, nOverwrite
= 0, nSrc
= -1, nDes
= -1;
334 /* this is the path up to the folder of the src and dest ie C:\windows\ */
335 TCHAR szDestPath
[MAX_PATH
];
336 TCHAR szSrcPath
[MAX_PATH
];
338 /* If this is the type of copy where we are adding files */
339 BOOL bAppend
= FALSE
;
340 WIN32_FIND_DATA findBuffer
;
343 /* Used when something like "copy c*.exe d*.exe" during the process of
344 figuring out the new name */
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 on weather or not the destination name will be taking from the input */
351 BOOL bSrcName
= FALSE
;
352 /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
354 /* for CMDCOPY env */
358 BOOL bHasWildcard
, bDone
= FALSE
, bMoreFiles
= FALSE
;
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 envor value if it exists */
372 evar
= cmd_alloc(512 * sizeof(TCHAR
));
376 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, 512);
380 evar
= cmd_realloc(evar
,size
* sizeof(TCHAR
) );
382 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, size
);
387 /* check see if we did get any env variable */
392 /* scan and set the flags */
393 for (t
= 0; t
< size
; t
++)
395 if (_tcsncicmp(_T("/A"),&evar
[t
],2) == 0)
397 dwFlags
|=COPY_ASCII
;
400 else if (_tcsncicmp(_T("/B"),&evar
[t
],2) == 0)
402 dwFlags
|= COPY_BINARY
;
405 else if (_tcsncicmp(_T("/D"),&evar
[t
],2) == 0)
407 dwFlags
|= COPY_DECRYPT
;
410 else if (_tcsncicmp(_T("/V"),&evar
[t
],2) == 0)
412 dwFlags
|= COPY_VERIFY
;
415 else if (_tcsncicmp(_T("/N"),&evar
[t
],2) == 0)
417 dwFlags
|= COPY_SHORTNAME
;
420 else if (_tcsncicmp(_T("/Y"),&evar
[t
],2) == 0)
422 dwFlags
|= COPY_NO_PROMPT
;
425 else if (_tcsncicmp(_T("/-Y"),&evar
[t
],3) == 0)
427 dwFlags
|= COPY_PROMPT
;
430 else if (_tcsncicmp(_T("/Z"),&evar
[t
],2) == 0)
432 dwFlags
|= COPY_PROMPT
;
440 /* Split the user input into array */
441 arg
= split(param
, &argc
, FALSE
, TRUE
);
444 /* Read switches and count files */
445 for (i
= 0; i
< argc
; i
++)
447 if (*arg
[i
] == _T('/'))
449 if (_tcslen(arg
[i
]) >= 2)
451 switch (_totupper(arg
[i
][1]))
454 dwFlags
|= COPY_ASCII
;
458 dwFlags
|= COPY_BINARY
;
462 dwFlags
|= COPY_DECRYPT
;
466 dwFlags
|= COPY_VERIFY
;
470 dwFlags
|= COPY_SHORTNAME
;
474 dwFlags
|= COPY_NO_PROMPT
;
475 dwFlags
&= ~COPY_PROMPT
;
479 if(_tcslen(arg
[i
]) >= 3)
480 if(_totupper(arg
[i
][2]) == _T('Y'))
482 dwFlags
&= ~COPY_NO_PROMPT
;
483 dwFlags
|= COPY_PROMPT
;
489 dwFlags
|= COPY_RESTART
;
494 ConOutResPrintf(STRING_ERROR_INVALID_SWITCH
, _totupper(arg
[i
][1]));
501 /* If it was a switch, subtract from total arguments */
506 /* If it isn't a switch then it is the source or destination */
511 else if (*arg
[i
] == _T('+'))
513 /* Next file should be appended */
519 /* Add this file to the source string
520 this way we can do all checks
521 directly on source string later on */
523 int length
= (_tcslen(arg
[nSrc
]) + _tcslen(arg
[i
]) + 2) * sizeof(TCHAR
);
524 ptr
= cmd_alloc(length
);
527 _tcscpy(ptr
, arg
[nSrc
]);
528 _tcscat(ptr
, _T("|"));
529 _tcscat(ptr
, arg
[i
]);
544 /* keep quiet within batch files */
547 dwFlags
|= COPY_NO_PROMPT
;
548 dwFlags
&= ~COPY_PROMPT
;
553 /* There are not enough files, there has to be at least 1 */
554 ConOutResPuts(STRING_ERROR_REQ_PARAM_MISSING
);
561 /* There are too many file names in command */
562 ConErrResPrintf(STRING_ERROR_TOO_MANY_PARAMETERS
,_T(""));
568 if ((_tcschr(arg
[nSrc
], _T('|')) != NULL
) ||
569 (_tcschr(arg
[nSrc
], _T('*')) != NULL
) ||
570 (_tcschr(arg
[nSrc
], _T('?')) != NULL
) ||
571 IsExistingDirectory(arg
[nSrc
]))
573 bMultipleSource
= TRUE
;
576 /* Reusing the number of files variable */
579 /* Check if no destination argument is passed */
582 /* If no destination was entered then just use
583 the current directory as the destination */
584 GetCurrentDirectory(MAX_PATH
, szDestPath
);
588 /* Check if the destination is 'x:' */
589 if ((arg
[nDes
][1] == _T(':')) && (arg
[nDes
][2] == _T('\0')))
591 GetRootPath(arg
[nDes
], szDestPath
, MAX_PATH
);
595 /* If the user entered two file names then form the full string path */
596 GetFullPathName(arg
[nDes
], MAX_PATH
, szDestPath
, NULL
);
599 /* Make sure there is an ending slash to the path if the dest is a folder */
600 if ((_tcschr(szDestPath
, _T('*')) == NULL
) &&
601 IsExistingDirectory(szDestPath
))
603 bMultipleDest
= TRUE
;
604 if (szDestPath
[_tcslen(szDestPath
) - 1] != _T('\\'))
605 _tcscat(szDestPath
, _T("\\"));
608 /* Check if the destination uses wildcards */
609 if ((_tcschr(arg
[nDes
], _T('*')) != NULL
) ||
610 (_tcschr(arg
[nDes
], _T('?')) != NULL
))
612 bMultipleDest
= TRUE
;
616 if (nDes
!= -1) /* you can only append files when there is a destination */
618 if (bMultipleSource
&& !bMultipleDest
)
620 /* We have multiple source files, but not multiple destination
621 files. This means we are appending the soruce files. */
623 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
624 appendPointer
= arg
[nSrc
];
628 /* Save the name the user entered */
629 UseThisName
= _tcsrchr(szDestPath
,_T('\\'));
632 /* Split the name from the path */
633 *UseThisName
++ = _T('\0');
635 /* Check if the dest path ends with '\*' or '\' */
636 if (((UseThisName
[0] == _T('*')) && (UseThisName
[1] == _T('\0'))) ||
637 (UseThisName
[0] == _T('\0')))
639 /* In this case we will be using the same name as the source file
640 for the destination file because destination is a folder */
647 /* Something's seriously wrong! */
648 UseThisName
= szDestPath
;
653 /* Get the full string of the path to the source file */
654 if (_tcschr(arg
[nSrc
], _T('|')) != NULL
)
656 /* Reset the source path */
657 szSrcPath
[0] = _T('\0');
659 /* Loop through the source file name and copy all
660 the chars one at a time until it gets too + */
663 if (appendPointer
[0] == _T('|'))
665 /* Skip the | and go to the next file name */
669 else if (appendPointer
[0] == _T('\0'))
675 _tcsncat(szSrcPath
, appendPointer
, 1);
679 if (_tcschr(arg
[nSrc
], _T(',')) != NULL
)
681 /* Only time there is a , in the source is when they are using touch
682 Cant have a destination and can only have on ,, at the end of the string
683 Cant have more then one file name */
684 szTouch
= _tcsstr(arg
[nSrc
], _T("|"));
685 if (_tcsncmp(szTouch
,_T("|,,\0"), 4) || (nDes
!= -1))
687 ConErrResPrintf(STRING_ERROR_INVALID_PARAM_FORMAT
,arg
[nSrc
]);
699 _tcscpy(szSrcPath
, arg
[nSrc
]);
702 /* "x:" is not a valid source path format. */
703 if ((szSrcPath
[1] == _T(':')) && (szSrcPath
[2] == _T('\0')))
705 ConOutPrintf(_T("%s\n"), szSrcPath
);
706 ConOutFormatMessage(ERROR_FILE_NOT_FOUND
, szSrcPath
);
712 /* From this point on, we can assume that the shortest path is 3 letters long
713 and that would be [DriveLetter]:\ */
715 /* Check if the path has a wildcard */
716 bHasWildcard
= (_tcschr(szSrcPath
, _T('*')) != NULL
);
718 /* If there is no * in the path name and it is a folder then we will
719 need to add a wildcard to the pathname so FindFirstFile comes up
720 with all the files in that folder */
721 if (!bHasWildcard
&& IsExistingDirectory(szSrcPath
))
723 /* If it doesnt have a \ at the end already then on needs to be added */
724 if (szSrcPath
[_tcslen(szSrcPath
) - 1] != _T('\\'))
725 _tcscat(szSrcPath
, _T("\\"));
726 _tcscat(szSrcPath
, _T("*"));
730 /* If the path ends with '\' add a wildcard at the end */
731 if (szSrcPath
[_tcslen(szSrcPath
) - 1] == _T('\\'))
733 _tcscat(szSrcPath
, _T("*"));
737 /* Get a list of all the files */
738 hFile
= FindFirstFile(szSrcPath
, &findBuffer
);
740 /* If it couldnt open the file handle, print out the error */
741 if (hFile
== INVALID_HANDLE_VALUE
)
743 /* only print source name when more then one file */
745 ConOutPrintf(_T("%s\n"), szSrcPath
);
747 ConOutFormatMessage(GetLastError(), szSrcPath
);
753 /* Strip the paths back to the folder they are in */
754 for (i
= (_tcslen(szSrcPath
) - 1); i
> -1; i
--)
755 if (szSrcPath
[i
] != _T('\\'))
756 szSrcPath
[i
] = _T('\0');
763 if (CheckCtrlBreak(BREAK_INPUT
))
769 /* Set the override to yes each new file */
772 /* Ignore the . and .. files */
773 if (!_tcscmp(findBuffer
.cFileName
, _T(".")) ||
774 !_tcscmp(findBuffer
.cFileName
, _T("..")) ||
775 findBuffer
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
780 /* Copy the base folder over to a tmp string */
781 _tcscpy(tmpDestPath
, szDestPath
);
782 _tcscat(tmpDestPath
, _T("\\"));
784 /* Can't put a file into a folder that isnt there */
785 if (_tcscmp(tmpDestPath
, _T("\\\\.\\")) &&
786 !IsExistingDirectory(tmpDestPath
))
788 ConOutFormatMessage(GetLastError(), szSrcPath
);
794 /* Copy over the destination path name */
796 _tcscat(tmpDestPath
, findBuffer
.cFileName
);
799 /* If there is no wildcard you can use the name the user entered */
800 if ((_tcschr(UseThisName
, _T('*')) == NULL
) &&
801 (_tcschr(UseThisName
, _T('?')) == NULL
))
803 _tcscat(tmpDestPath
, UseThisName
);
807 TCHAR DoneFile
[MAX_PATH
];
809 BuildFileName(findBuffer
.cFileName
,
814 /* Add the filename to the tmp string path */
815 _tcscat(tmpDestPath
, DoneFile
);
819 /* Build the string path to the source file */
820 _tcscpy(tmpSrcPath
,szSrcPath
);
821 _tcscat (tmpSrcPath
, findBuffer
.cFileName
);
823 /* Check to see if the file is the same file */
824 if(!bTouch
&& !_tcscmp(tmpSrcPath
, tmpDestPath
))
826 ConOutResPrintf(STRING_COPY_ERROR2
);
832 /* only print source name when more then one file */
834 ConOutPrintf(_T("%s\n"), tmpSrcPath
);
836 /* Handle any overriding / prompting that needs to be done */
837 if (((!(dwFlags
& COPY_NO_PROMPT
) && IsExistingFile (tmpDestPath
)) || dwFlags
& COPY_PROMPT
) && !bTouch
)
838 nOverwrite
= CopyOverwrite(tmpDestPath
);
839 if (nOverwrite
== PROMPT_NO
|| nOverwrite
== PROMPT_BREAK
)
841 if (nOverwrite
== PROMPT_ALL
|| (nOverwrite
== PROMPT_YES
&& bAppend
))
842 dwFlags
|= COPY_NO_PROMPT
;
844 /* Tell weather the copy was successful or not */
845 if(copy(tmpSrcPath
,tmpDestPath
, bAppend
, dwFlags
, bTouch
))
848 //LoadString(CMD_ModuleHandle, STRING_MOVE_ERROR1, szMsg, RC_STRING_MAX_SIZE);
852 /* print out the error message */
853 ConOutResPrintf(STRING_COPY_ERROR3
);
854 ConOutFormatMessage (GetLastError(), szSrcPath
);
858 /* Loop through all wildcard files */
859 } while (FindNextFile(hFile
, &findBuffer
));
861 /* Loop through all files in src string with a + */
864 /* print out the number of files copied */
865 ConOutResPrintf(STRING_COPY_FILE
, bAppend
? 1 : nFiles
);
867 if (hFile
) FindClose(hFile
);
875 #endif /* INCLUDE_CMD_COPY */