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
);
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);
293 INT
cmd_copy (LPTSTR cmd
, LPTSTR param
)
296 INT argc
, i
, nFiles
, nOverwrite
= 0, nSrc
= -1, nDes
= -1;
297 /* this is the path up to the folder of the src and dest ie C:\windows\ */
298 TCHAR szDestPath
[MAX_PATH
];
299 TCHAR szSrcPath
[MAX_PATH
];
301 /* If this is the type of copy where we are adding files */
302 BOOL bAppend
= FALSE
;
303 WIN32_FIND_DATA findBuffer
;
306 /* Used when something like "copy c*.exe d*.exe" during the process of
307 figuring out the new name */
308 TCHAR tmpName
[MAX_PATH
] = _T("");
309 /* Pointer to keep track of how far through the append input(file1+file2+file3) we are */
310 TCHAR
* appendPointer
= _T("\0");
311 /* The full path to src and dest. This has drive letter, folders, and filename */
312 TCHAR tmpDestPath
[MAX_PATH
];
313 TCHAR tmpSrcPath
[MAX_PATH
];
314 /* A bool on weather or not the destination name will be taking from the input */
315 BOOL bSrcName
= FALSE
;
316 /* Seems like a waste but it is a pointer used to copy from input to PreserveName */
318 /* Stores the name( i.e. blah.txt or blah*.txt) which later we might need */
319 TCHAR PreserveName
[MAX_PATH
];
320 /* for CMDCOPY env */
327 /* Show help/usage info */
328 if (!_tcsncmp (param
, _T("/?"), 2))
330 ConOutResPaging(TRUE
, STRING_COPY_HELP2
);
336 /* Get the envor value if it exists */
337 evar
= cmd_alloc(512 * sizeof(TCHAR
));
341 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, 512);
345 evar
= cmd_realloc(evar
,size
* sizeof(TCHAR
) );
347 size
= GetEnvironmentVariable (_T("COPYCMD"), evar
, size
);
352 /* check see if we did get any env variable */
356 /* scan and set the flags */
359 if (_tcsncicmp(_T("/A"),&evar
[t
],2) == 0)
361 dwFlags
|=COPY_ASCII
;
364 else if (_tcsncicmp(_T("/B"),&evar
[t
],2) == 0)
366 dwFlags
|= COPY_BINARY
;
369 else if (_tcsncicmp(_T("/D"),&evar
[t
],2) == 0)
371 dwFlags
|= COPY_DECRYPT
;
374 else if (_tcsncicmp(_T("/V"),&evar
[t
],2) == 0)
376 dwFlags
|= COPY_VERIFY
;
379 else if (_tcsncicmp(_T("/N"),&evar
[t
],2) == 0)
381 dwFlags
|= COPY_SHORTNAME
;
384 else if (_tcsncicmp(_T("/Y"),&evar
[t
],2) == 0)
386 dwFlags
|= COPY_NO_PROMPT
;
389 else if (_tcsncicmp(_T("/-Y"),&evar
[t
],3) == 0)
391 dwFlags
|= COPY_PROMPT
;
394 else if (_tcsncicmp(_T("/Z"),&evar
[t
],2) == 0)
396 dwFlags
|= COPY_PROMPT
;
404 /* Split the user input into array */
405 arg
= split (param
, &argc
, FALSE
);
409 /* Read switches and count files */
410 for (i
= 0; i
< argc
; i
++)
412 if (*arg
[i
] == _T('/'))
414 if (_tcslen(arg
[i
]) >= 2)
416 switch (_totupper(arg
[i
][1]))
419 dwFlags
|= COPY_ASCII
;
423 dwFlags
|= COPY_BINARY
;
427 dwFlags
|= COPY_DECRYPT
;
431 dwFlags
|= COPY_VERIFY
;
435 dwFlags
|= COPY_SHORTNAME
;
439 dwFlags
|= COPY_NO_PROMPT
;
440 dwFlags
&= ~COPY_PROMPT
;
444 if(_tcslen(arg
[i
]) >= 3)
445 if(_totupper(arg
[i
][2]) == _T('Y'))
447 dwFlags
&= ~COPY_NO_PROMPT
;
448 dwFlags
|= COPY_PROMPT
;
454 dwFlags
|= COPY_RESTART
;
459 ConOutResPrintf(STRING_ERROR_INVALID_SWITCH
, _totupper(arg
[i
][1]));
466 /* If it was a switch, subtract from total arguments */
471 /* If it isn't a switch then it is the source or destination */
476 else if(*arg
[i
] == _T('+') || *arg
[i
] == _T(','))
478 /* Add these onto the source string
479 this way we can do all checks
480 directly on source string later on */
482 int length
= (_tcslen(arg
[nSrc
]) +_tcslen(arg
[i
]) + _tcslen(arg
[i
+1]) + 1) * sizeof(TCHAR
);
483 ptr
= cmd_alloc(length
);
486 _tcscpy(ptr
, arg
[nSrc
]);
487 _tcscat(ptr
, arg
[i
]);
488 _tcscat(ptr
, arg
[i
+1]);
502 /* keep quiet within batch files */
505 dwFlags
|= COPY_NO_PROMPT
;
506 dwFlags
&= ~COPY_PROMPT
;
511 /* There are not enough files, there has to be at least 1 */
512 ConOutResPuts(STRING_ERROR_REQ_PARAM_MISSING
);
519 /* There are too many file names in command */
520 ConErrResPrintf(STRING_ERROR_TOO_MANY_PARAMETERS
,_T(""));
526 if (nDes
!= -1) /* you can only append files when there is a destination */
528 if(((_tcschr (arg
[nSrc
], _T('+')) != NULL
) ||
529 (_tcschr (arg
[nSrc
], _T('*')) != NULL
&& _tcschr (arg
[nDes
], _T('*')) == NULL
) ||
530 (IsExistingDirectory (arg
[nSrc
]) && (_tcschr (arg
[nDes
], _T('*')) == NULL
&& !IsExistingDirectory (arg
[nDes
])))
533 /* There is a + in the source filename, this means
534 that there is more then one file being put into
537 if(_tcschr (arg
[nSrc
], _T('+')) != NULL
)
538 appendPointer
= arg
[nSrc
];
542 /* Reusing the number of files variable */
547 /* Set up the string that is the path to the destination */
550 if(_tcslen(arg
[nDes
]) == 2 && arg
[nDes
][1] == _T(':'))
552 GetRootPath(arg
[nDes
],szDestPath
,MAX_PATH
);
555 /* If the user entered two file names then form the full string path */
556 GetFullPathName (arg
[nDes
], MAX_PATH
, szDestPath
, NULL
);
560 /* If no destination was entered then just use
561 the current directory as the destination */
562 GetCurrentDirectory (MAX_PATH
, szDestPath
);
565 /* Get the full string of the path to the source file */
566 if(_tcschr (arg
[nSrc
], _T('+')) != NULL
)
568 _tcscpy(tmpName
,_T("\0"));
569 /* Loop through the source file name and copy all
570 the chars one at a time until it gets too + */
573 if(!_tcsncmp (appendPointer
,_T("+"),1) || !_tcsncmp (appendPointer
,_T("\0"),1))
575 /* Now that the pointer is on the + we
576 need to go to the start of the next filename */
577 if(!_tcsncmp (appendPointer
,_T("+"),1))
584 _tcsncat(tmpName
,appendPointer
,1);
588 /* Finish the string off with a null char */
589 _tcsncat(tmpName
,_T("\0"),1);
591 if(_tcschr (arg
[nSrc
], _T(',')) != NULL
)
593 /* Only time there is a , in the source is when they are using touch
594 Cant have a destination and can only have on ,, at the end of the string
595 Cant have more then one file name */
596 szTouch
= _tcsstr (arg
[nSrc
], _T("+"));
597 if(_tcsncmp (szTouch
,_T("+,,\0"),4) || nDes
!= -1)
599 ConErrResPrintf(STRING_ERROR_INVALID_PARAM_FORMAT
,arg
[nSrc
]);
608 if(_tcslen(tmpName
) == 2)
610 if(tmpName
[1] == _T(':'))
612 GetRootPath(tmpName
,szSrcPath
,MAX_PATH
);
616 /* Get the full path to first file in the string of file names */
617 GetFullPathName (tmpName
, MAX_PATH
, szSrcPath
, NULL
);
622 if(_tcslen(arg
[nSrc
]) == 2 && arg
[nSrc
][1] == _T(':'))
624 GetRootPath(arg
[nSrc
],szSrcPath
,MAX_PATH
);
627 /* Get the full path of the source file */
628 GetFullPathName (arg
[nSrc
], MAX_PATH
, szSrcPath
, NULL
);
631 /* From this point on, we can assume that the shortest path is 3 letters long
632 and that would be [DriveLetter]:\ */
634 /* If there is no * in the path name and it is a folder
635 then we will need to add a wildcard to the pathname
636 so FindFirstFile comes up with all the files in that
638 if(_tcschr (szSrcPath
, _T('*')) == NULL
&&
639 IsExistingDirectory (szSrcPath
))
641 /* If it doesnt have a \ at the end already then on needs to be added */
642 if(szSrcPath
[_tcslen(szSrcPath
) - 1] != _T('\\'))
643 _tcscat (szSrcPath
, _T("\\"));
644 /* Add a wildcard after the \ */
645 _tcscat (szSrcPath
, _T("*"));
647 /* Make sure there is an ending slash to the path if the dest is a folder */
648 if(_tcschr (szDestPath
, _T('*')) == NULL
&&
649 IsExistingDirectory(szDestPath
))
651 if(szDestPath
[_tcslen(szDestPath
) - 1] != _T('\\'))
652 _tcscat (szDestPath
, _T("\\"));
655 /* Get a list of all the files */
656 hFile
= FindFirstFile (szSrcPath
, &findBuffer
);
658 /* We need to figure out what the name of the file in the is going to be */
659 if((szDestPath
[_tcslen(szDestPath
) - 1] == _T('*') && szDestPath
[_tcslen(szDestPath
) - 2] == _T('\\')) ||
660 szDestPath
[_tcslen(szDestPath
) - 1] == _T('\\'))
662 /* In this case we will be using the same name as the source file
663 for the destination file because destination is a folder */
668 /* Save the name the user entered */
669 UseThisName
= _tcsrchr(szDestPath
,_T('\\'));
671 _tcscpy(PreserveName
,UseThisName
);
674 /* Strip the paths back to the folder they are in */
675 for(i
= (_tcslen(szSrcPath
) - 1); i
> -1; i
--)
676 if(szSrcPath
[i
] != _T('\\'))
677 szSrcPath
[i
] = _T('\0');
681 for(i
= (_tcslen(szDestPath
) - 1); i
> -1; i
--)
682 if(szDestPath
[i
] != _T('\\'))
683 szDestPath
[i
] = _T('\0');
690 if(CheckCtrlBreak(BREAK_INPUT
))
695 /* Set the override to yes each new file */
698 /* If it couldnt open the file handle, print out the error */
699 if(hFile
== INVALID_HANDLE_VALUE
)
701 ConOutFormatMessage (GetLastError(), szSrcPath
);
707 /* Ignore the . and .. files */
708 if(!_tcscmp (findBuffer
.cFileName
, _T(".")) ||
709 !_tcscmp (findBuffer
.cFileName
, _T(".."))||
710 findBuffer
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
713 /* Copy the base folder over to a tmp string */
714 _tcscpy(tmpDestPath
,szDestPath
);
716 /* Can't put a file into a folder that isnt there */
717 if(_tcscmp (szDestPath
, _T("\\\\.\\")) && !IsExistingDirectory(szDestPath
))
719 ConOutFormatMessage (GetLastError (), szSrcPath
);
724 /* Copy over the destination path name */
726 _tcscat (tmpDestPath
, findBuffer
.cFileName
);
729 /* If there is no wildcard you can use the name the user entered */
730 if(_tcschr (PreserveName
, _T('*')) == NULL
)
732 _tcscat (tmpDestPath
, PreserveName
);
736 /* The following lines of copy were written by someone else
737 (most likely Eric Khoul) and it was taken from ren.c */
739 TCHAR DoneFile
[MAX_PATH
];
740 /* build destination file name */
741 p
= findBuffer
.cFileName
;
749 while (*p
!= 0 && *p
!= *q
)
776 /* Add the filename to the tmp string path */
777 _tcscat (tmpDestPath
, DoneFile
);
781 /* Build the string path to the source file */
782 _tcscpy(tmpSrcPath
,szSrcPath
);
783 _tcscat (tmpSrcPath
, findBuffer
.cFileName
);
785 /* Check to see if the file is the same file */
786 if(!bTouch
&& !_tcscmp (tmpSrcPath
, tmpDestPath
))
788 ConOutResPrintf(STRING_COPY_ERROR2
);
794 /* Handle any overriding / prompting that needs to be done */
795 if(((!(dwFlags
& COPY_NO_PROMPT
) && IsExistingFile (tmpDestPath
)) || dwFlags
& COPY_PROMPT
) && !bTouch
)
796 nOverwrite
= CopyOverwrite(tmpDestPath
);
797 if(nOverwrite
== PROMPT_NO
|| nOverwrite
== PROMPT_BREAK
)
799 if(nOverwrite
== PROMPT_ALL
|| (nOverwrite
== PROMPT_YES
&& bAppend
))
800 dwFlags
|= COPY_NO_PROMPT
;
802 /* Tell weather the copy was successful or not */
803 if(copy(tmpSrcPath
,tmpDestPath
, bAppend
, dwFlags
, bTouch
))
806 /* only print source name when more then one file */
807 if(_tcschr (arg
[nSrc
], _T('+')) != NULL
|| _tcschr (arg
[nSrc
], _T('*')) != NULL
)
808 ConOutPrintf(_T("%s\n"),findBuffer
.cFileName
);
809 //LoadString(CMD_ModuleHandle, STRING_MOVE_ERROR1, szMsg, RC_STRING_MAX_SIZE);
813 /* print out the error message */
814 ConOutResPrintf(STRING_COPY_ERROR3
);
815 ConOutFormatMessage (GetLastError(), szSrcPath
);
819 /* Loop through all wildcard files */
820 } while(FindNextFile (hFile
, &findBuffer
));
822 /* Loop through all files in src string with a + */
825 /* print out the number of files copied */
826 ConOutResPrintf(STRING_COPY_FILE
, nFiles
);
836 #endif /* INCLUDE_CMD_COPY */