2 * XCOPY - Wine-compatible xcopy program
4 * Copyright (C) 2007 J. Edmeades
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * This should now support all options listed in the xcopy help from
25 * /Z - Copy from network drives in restartable mode
26 * /X - Copy file audit settings (sets /O)
27 * /O - Copy file ownership + ACL info
28 * /G - Copy encrypted files to unencrypted destination
34 * Apparently, valid return codes are:
36 * 1 - No files found to copy
37 * 2 - CTRL+C during copy
38 * 4 - Initialization error, or invalid source specification
39 * 5 - Disk write error
45 #include <wine/debug.h>
46 #include <wine/unicode.h>
49 WINE_DEFAULT_DEBUG_CHANNEL(xcopy
);
52 static int XCOPY_ParseCommandLine(WCHAR
*suppliedsource
,
53 WCHAR
*supplieddestination
, DWORD
*flags
);
54 static int XCOPY_ProcessSourceParm(WCHAR
*suppliedsource
, WCHAR
*stem
,
55 WCHAR
*spec
, DWORD flags
);
56 static int XCOPY_ProcessDestParm(WCHAR
*supplieddestination
, WCHAR
*stem
,
57 WCHAR
*spec
, WCHAR
*srcspec
, DWORD flags
);
58 static int XCOPY_DoCopy(WCHAR
*srcstem
, WCHAR
*srcspec
,
59 WCHAR
*deststem
, WCHAR
*destspec
,
61 static BOOL
XCOPY_CreateDirectory(const WCHAR
* path
);
62 static BOOL
XCOPY_ProcessExcludeList(WCHAR
* parms
);
63 static BOOL
XCOPY_ProcessExcludeFile(WCHAR
* filename
, WCHAR
* endOfName
);
64 static WCHAR
*XCOPY_LoadMessage(UINT id
);
65 static void XCOPY_FailMessage(DWORD err
);
66 static int XCOPY_wprintf(const WCHAR
*format
, ...);
69 typedef struct _EXCLUDELIST
71 struct _EXCLUDELIST
*next
;
76 /* Global variables */
77 static ULONG filesCopied
= 0; /* Number of files copied */
78 static EXCLUDELIST
*excludeList
= NULL
; /* Excluded strings list */
79 static FILETIME dateRange
; /* Date range to copy after*/
80 static const WCHAR wchr_slash
[] = {'\\', 0};
81 static const WCHAR wchr_star
[] = {'*', 0};
82 static const WCHAR wchr_dot
[] = {'.', 0};
83 static const WCHAR wchr_dotdot
[] = {'.', '.', 0};
85 /* Constants (Mostly for widechars) */
88 /* To minimize stack usage during recursion, some temporary variables
90 static WCHAR copyFrom
[MAX_PATH
];
91 static WCHAR copyTo
[MAX_PATH
];
94 /* =========================================================================
95 main - Main entrypoint for the xcopy command
97 Processes the args, and drives the actual copying
98 ========================================================================= */
99 int wmain (int argc
, WCHAR
*argvW
[])
102 WCHAR suppliedsource
[MAX_PATH
] = {0}; /* As supplied on the cmd line */
103 WCHAR supplieddestination
[MAX_PATH
] = {0};
104 WCHAR sourcestem
[MAX_PATH
] = {0}; /* Stem of source */
105 WCHAR sourcespec
[MAX_PATH
] = {0}; /* Filespec of source */
106 WCHAR destinationstem
[MAX_PATH
] = {0}; /* Stem of destination */
107 WCHAR destinationspec
[MAX_PATH
] = {0}; /* Filespec of destination */
108 WCHAR copyCmd
[MAXSTRING
]; /* COPYCMD env var */
109 DWORD flags
= 0; /* Option flags */
110 const WCHAR PROMPTSTR1
[] = {'/', 'Y', 0};
111 const WCHAR PROMPTSTR2
[] = {'/', 'y', 0};
112 const WCHAR COPYCMD
[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
114 /* Preinitialize flags based on COPYCMD */
115 if (GetEnvironmentVariableW(COPYCMD
, copyCmd
, MAXSTRING
)) {
116 if (wcsstr(copyCmd
, PROMPTSTR1
) != NULL
||
117 wcsstr(copyCmd
, PROMPTSTR2
) != NULL
) {
118 flags
|= OPT_NOPROMPT
;
122 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
123 wine, but on windows these can be normal files. At least one installer
124 uses files such as .packlist and (validly) expects them to be copied.
125 Under wine, if we do not copy hidden files by default then they get
127 flags
|= OPT_COPYHIDSYS
;
130 * Parse the command line
132 if ((rc
= XCOPY_ParseCommandLine(suppliedsource
, supplieddestination
,
140 /* Trace out the supplied information */
141 WINE_TRACE("Supplied parameters:\n");
142 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource
));
143 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination
));
145 /* Extract required information from source specification */
146 rc
= XCOPY_ProcessSourceParm(suppliedsource
, sourcestem
, sourcespec
, flags
);
147 if (rc
!= RC_OK
) return rc
;
149 /* Extract required information from destination specification */
150 rc
= XCOPY_ProcessDestParm(supplieddestination
, destinationstem
,
151 destinationspec
, sourcespec
, flags
);
152 if (rc
!= RC_OK
) return rc
;
154 /* Trace out the resulting information */
155 WINE_TRACE("Resolved parameters:\n");
156 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem
));
157 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec
));
158 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem
));
159 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec
));
161 /* Pause if necessary */
162 if (flags
& OPT_PAUSE
) {
166 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE
));
167 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), pausestr
, sizeof(pausestr
),
171 /* Now do the hard work... */
172 rc
= XCOPY_DoCopy(sourcestem
, sourcespec
,
173 destinationstem
, destinationspec
,
176 /* Clear up exclude list allocated memory */
177 while (excludeList
) {
178 EXCLUDELIST
*pos
= excludeList
;
179 excludeList
= excludeList
-> next
;
180 HeapFree(GetProcessHeap(), 0, pos
->name
);
181 HeapFree(GetProcessHeap(), 0, pos
);
184 /* Finished - print trailer and exit */
185 if (flags
& OPT_SIMULATE
) {
186 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY
), filesCopied
);
187 } else if (!(flags
& OPT_NOCOPY
)) {
188 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY
), filesCopied
);
190 if (rc
== RC_OK
&& filesCopied
== 0) rc
= RC_NOFILES
;
195 /* =========================================================================
196 XCOPY_ParseCommandLine - Parses the command line
197 ========================================================================= */
198 static BOOL
is_whitespace(WCHAR c
)
200 return c
== ' ' || c
== '\t';
203 static WCHAR
*skip_whitespace(WCHAR
*p
)
205 for (; *p
&& is_whitespace(*p
); p
++);
209 /* Windows XCOPY uses a simplified command line parsing algorithm
210 that lacks the escaped-quote logic of build_argv(), because
211 literal double quotes are illegal in any of its arguments.
212 Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
213 static int find_end_of_word(const WCHAR
*word
, WCHAR
**end
)
216 const WCHAR
*ptr
= word
;
218 for (; *ptr
!= '\0' && *ptr
!= '"' &&
219 (in_quotes
|| !is_whitespace(*ptr
)); ptr
++);
221 in_quotes
= !in_quotes
;
224 /* Odd number of double quotes is illegal for XCOPY */
225 if (in_quotes
&& *ptr
== '\0')
227 if (*ptr
== '\0' || (!in_quotes
&& is_whitespace(*ptr
)))
234 /* Remove all double quotes from a word */
235 static void strip_quotes(WCHAR
*word
, WCHAR
**end
)
238 for (rp
= word
, wp
= word
; *rp
!= '\0'; rp
++) {
249 static int XCOPY_ParseCommandLine(WCHAR
*suppliedsource
,
250 WCHAR
*supplieddestination
, DWORD
*pflags
)
252 const WCHAR EXCLUDE
[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
253 DWORD flags
= *pflags
;
254 WCHAR
*cmdline
, *word
, *end
, *next
;
255 int rc
= RC_INITERROR
;
257 cmdline
= _wcsdup(GetCommandLineW());
261 /* Skip first arg, which is the program name */
262 if ((rc
= find_end_of_word(cmdline
, &word
)) != RC_OK
)
264 word
= skip_whitespace(word
);
269 if ((rc
= find_end_of_word(word
, &end
)) != RC_OK
)
272 next
= skip_whitespace(end
);
275 strip_quotes(word
, &end
);
276 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word
));
278 /* First non-switch parameter is source, second is destination */
280 if (suppliedsource
[0] == 0x00) {
281 lstrcpyW(suppliedsource
, word
);
282 } else if (supplieddestination
[0] == 0x00) {
283 lstrcpyW(supplieddestination
, word
);
285 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS
));
289 /* Process all the switch options
290 Note: Windows docs say /P prompts when dest is created
291 but tests show it is done for each src file
292 regardless of the destination */
293 switch (toupper(word
[1])) {
294 case 'I': flags
|= OPT_ASSUMEDIR
; break;
295 case 'S': flags
|= OPT_RECURSIVE
; break;
296 case 'Q': flags
|= OPT_QUIET
; break;
297 case 'F': flags
|= OPT_FULL
; break;
298 case 'L': flags
|= OPT_SIMULATE
; break;
299 case 'W': flags
|= OPT_PAUSE
; break;
300 case 'T': flags
|= OPT_NOCOPY
| OPT_RECURSIVE
; break;
301 case 'Y': flags
|= OPT_NOPROMPT
; break;
302 case 'N': flags
|= OPT_SHORTNAME
; break;
303 case 'U': flags
|= OPT_MUSTEXIST
; break;
304 case 'R': flags
|= OPT_REPLACEREAD
; break;
305 case 'H': flags
|= OPT_COPYHIDSYS
; break;
306 case 'C': flags
|= OPT_IGNOREERRORS
; break;
307 case 'P': flags
|= OPT_SRCPROMPT
; break;
308 case 'A': flags
|= OPT_ARCHIVEONLY
; break;
309 case 'M': flags
|= OPT_ARCHIVEONLY
|
310 OPT_REMOVEARCH
; break;
312 /* E can be /E or /EXCLUDE */
313 case 'E': if (CompareStringW(LOCALE_USER_DEFAULT
,
314 NORM_IGNORECASE
| SORT_STRINGSORT
,
317 if (XCOPY_ProcessExcludeList(&word
[9])) {
318 XCOPY_FailMessage(ERROR_INVALID_PARAMETER
);
320 } else flags
|= OPT_EXCLUDELIST
;
321 } else flags
|= OPT_EMPTYDIR
| OPT_RECURSIVE
;
324 /* D can be /D or /D: */
325 case 'D': if (word
[2]==':' && isdigit(word
[3])) {
327 WCHAR
*pos
= &word
[3];
328 BOOL isError
= FALSE
;
329 memset(&st
, 0x00, sizeof(st
));
331 /* Parse the arg : Month */
332 st
.wMonth
= _wtol(pos
);
333 while (*pos
&& isdigit(*pos
)) pos
++;
334 if (*pos
++ != '-') isError
= TRUE
;
336 /* Parse the arg : Day */
338 st
.wDay
= _wtol(pos
);
339 while (*pos
&& isdigit(*pos
)) pos
++;
340 if (*pos
++ != '-') isError
= TRUE
;
343 /* Parse the arg : Day */
345 st
.wYear
= _wtol(pos
);
346 while (*pos
&& isdigit(*pos
)) pos
++;
347 if (st
.wYear
< 100) st
.wYear
+=2000;
350 if (!isError
&& SystemTimeToFileTime(&st
, &dateRange
)) {
352 WCHAR datestring
[32], timestring
[32];
354 flags
|= OPT_DATERANGE
;
357 FileTimeToSystemTime (&dateRange
, &st
);
358 GetDateFormatW(0, DATE_SHORTDATE
, &st
, NULL
, datestring
,
359 sizeof(datestring
)/sizeof(WCHAR
));
360 GetTimeFormatW(0, TIME_NOSECONDS
, &st
,
361 NULL
, timestring
, sizeof(timestring
)/sizeof(WCHAR
));
363 WINE_TRACE("Date being used is: %s %s\n",
364 wine_dbgstr_w(datestring
), wine_dbgstr_w(timestring
));
366 XCOPY_FailMessage(ERROR_INVALID_PARAMETER
);
370 flags
|= OPT_DATENEWER
;
374 case '-': if (toupper(word
[2])=='Y')
375 flags
&= ~OPT_NOPROMPT
; break;
376 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP
));
380 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word
));
381 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM
), word
);
388 /* Default the destination if not supplied */
389 if (supplieddestination
[0] == 0x00)
390 lstrcpyW(supplieddestination
, wchr_dot
);
401 /* =========================================================================
402 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
403 converts it into a stem and a filespec
404 ========================================================================= */
405 static int XCOPY_ProcessSourceParm(WCHAR
*suppliedsource
, WCHAR
*stem
,
406 WCHAR
*spec
, DWORD flags
)
408 WCHAR actualsource
[MAX_PATH
];
414 * Validate the source, expanding to full path ensuring it exists
416 if (GetFullPathNameW(suppliedsource
, MAX_PATH
, actualsource
, NULL
) == 0) {
417 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
421 /* If full names required, convert to using the full path */
422 if (flags
& OPT_FULL
) {
423 lstrcpyW(suppliedsource
, actualsource
);
427 * Work out the stem of the source
430 /* If a directory is supplied, use that as-is (either fully or
432 If a filename is supplied + a directory or drive path, use that
435 If no directory or path specified, add eg. C:
436 stem is Drive/Directory is bit up to last \ (or first :)
437 spec is bit after that */
439 starPos
= wcschr(suppliedsource
, '*');
440 questPos
= wcschr(suppliedsource
, '?');
441 if (starPos
|| questPos
) {
442 attribs
= 0x00; /* Ensures skips invalid or directory check below */
444 attribs
= GetFileAttributesW(actualsource
);
447 if (attribs
== INVALID_FILE_ATTRIBUTES
) {
448 XCOPY_FailMessage(GetLastError());
452 stem should be exactly as supplied plus a '\', unless it was
453 eg. C: in which case no slash required */
454 } else if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
457 WINE_TRACE("Directory supplied\n");
458 lstrcpyW(stem
, suppliedsource
);
459 lastChar
= stem
[lstrlenW(stem
)-1];
460 if (lastChar
!= '\\' && lastChar
!= ':') {
461 lstrcatW(stem
, wchr_slash
);
463 lstrcpyW(spec
, wchr_star
);
465 /* File or wildcard search:
467 Up to and including last slash if directory path supplied
468 If c:filename supplied, just the c:
469 Otherwise stem should be the current drive letter + ':' */
473 WINE_TRACE("Filename supplied\n");
474 lastDir
= wcsrchr(suppliedsource
, '\\');
477 lstrcpyW(stem
, suppliedsource
);
478 stem
[(lastDir
-suppliedsource
) + 1] = 0x00;
479 lstrcpyW(spec
, (lastDir
+1));
480 } else if (suppliedsource
[1] == ':') {
481 lstrcpyW(stem
, suppliedsource
);
483 lstrcpyW(spec
, suppliedsource
+2);
485 WCHAR curdir
[MAXSTRING
];
486 GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
490 lstrcpyW(spec
, suppliedsource
);
497 /* =========================================================================
498 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
499 converts it into a stem
500 ========================================================================= */
501 static int XCOPY_ProcessDestParm(WCHAR
*supplieddestination
, WCHAR
*stem
, WCHAR
*spec
,
502 WCHAR
*srcspec
, DWORD flags
)
504 WCHAR actualdestination
[MAX_PATH
];
509 * Validate the source, expanding to full path ensuring it exists
511 if (GetFullPathNameW(supplieddestination
, MAX_PATH
, actualdestination
, NULL
) == 0) {
512 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
516 /* Destination is either a directory or a file */
517 attribs
= GetFileAttributesW(actualdestination
);
519 if (attribs
== INVALID_FILE_ATTRIBUTES
) {
521 /* If /I supplied and wildcard copy, assume directory */
522 /* Also if destination ends with backslash */
523 if ((flags
& OPT_ASSUMEDIR
&&
524 (wcschr(srcspec
, '?') || wcschr(srcspec
, '*'))) ||
525 (supplieddestination
[lstrlenW(supplieddestination
)-1] == '\\')) {
531 char answer
[10] = "";
535 /* Read the F and D characters from the resource file */
536 wcscpy(fileChar
, XCOPY_LoadMessage(STRING_FILE_CHAR
));
537 wcscpy(dirChar
, XCOPY_LoadMessage(STRING_DIR_CHAR
));
539 while (answer
[0] != fileChar
[0] && answer
[0] != dirChar
[0]) {
540 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR
), supplieddestination
);
542 ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
), &count
, NULL
);
543 WINE_TRACE("User answer %c\n", answer
[0]);
545 answer
[0] = toupper(answer
[0]);
548 if (answer
[0] == dirChar
[0]) {
555 isDir
= (attribs
& FILE_ATTRIBUTE_DIRECTORY
);
559 lstrcpyW(stem
, actualdestination
);
562 /* Ensure ends with a '\' */
563 if (stem
[lstrlenW(stem
)-1] != '\\') {
564 lstrcatW(stem
, wchr_slash
);
568 WCHAR drive
[MAX_PATH
];
570 WCHAR fname
[MAX_PATH
];
572 _wsplitpath(actualdestination
, drive
, dir
, fname
, ext
);
573 lstrcpyW(stem
, drive
);
575 lstrcpyW(spec
, fname
);
581 /* =========================================================================
582 XCOPY_DoCopy - Recursive function to copy files based on input parms
585 This works by using FindFirstFile supplying the source stem and spec.
586 If results are found, any non-directory ones are processed
587 Then, if /S or /E is supplied, another search is made just for
588 directories, and this function is called again for that directory
590 ========================================================================= */
591 static int XCOPY_DoCopy(WCHAR
*srcstem
, WCHAR
*srcspec
,
592 WCHAR
*deststem
, WCHAR
*destspec
,
595 WIN32_FIND_DATAW
*finddata
;
598 WCHAR
*inputpath
, *outputpath
;
599 BOOL copiedFile
= FALSE
;
600 DWORD destAttribs
, srcAttribs
;
604 /* Allocate some working memory on heap to minimize footprint */
605 finddata
= HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW
));
606 inputpath
= HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
));
607 outputpath
= HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
));
609 /* Build the search info into a single parm */
610 lstrcpyW(inputpath
, srcstem
);
611 lstrcatW(inputpath
, srcspec
);
613 /* Search 1 - Look for matching files */
614 h
= FindFirstFileW(inputpath
, finddata
);
615 while (h
!= INVALID_HANDLE_VALUE
&& findres
) {
619 /* Ignore . and .. */
620 if (lstrcmpW(finddata
->cFileName
, wchr_dot
)==0 ||
621 lstrcmpW(finddata
->cFileName
, wchr_dotdot
)==0 ||
622 finddata
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
624 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata
->cFileName
));
627 /* Get the filename information */
628 lstrcpyW(copyFrom
, srcstem
);
629 if (flags
& OPT_SHORTNAME
) {
630 lstrcatW(copyFrom
, finddata
->cAlternateFileName
);
632 lstrcatW(copyFrom
, finddata
->cFileName
);
635 lstrcpyW(copyTo
, deststem
);
636 if (*destspec
== 0x00) {
637 if (flags
& OPT_SHORTNAME
) {
638 lstrcatW(copyTo
, finddata
->cAlternateFileName
);
640 lstrcatW(copyTo
, finddata
->cFileName
);
643 lstrcatW(copyTo
, destspec
);
647 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom
),
648 wine_dbgstr_w(copyTo
));
649 if (!copiedFile
&& !(flags
& OPT_SIMULATE
)) XCOPY_CreateDirectory(deststem
);
651 /* See if allowed to copy it */
652 srcAttribs
= GetFileAttributesW(copyFrom
);
653 WINE_TRACE("Source attribs: %d\n", srcAttribs
);
655 if ((srcAttribs
& FILE_ATTRIBUTE_HIDDEN
) ||
656 (srcAttribs
& FILE_ATTRIBUTE_SYSTEM
)) {
658 if (!(flags
& OPT_COPYHIDSYS
)) {
663 if (!(srcAttribs
& FILE_ATTRIBUTE_ARCHIVE
) &&
664 (flags
& OPT_ARCHIVEONLY
)) {
668 /* See if file exists */
669 destAttribs
= GetFileAttributesW(copyTo
);
670 WINE_TRACE("Dest attribs: %d\n", srcAttribs
);
672 /* Check date ranges if a destination file already exists */
673 if (!skipFile
&& (flags
& OPT_DATERANGE
) &&
674 (CompareFileTime(&finddata
->ftLastWriteTime
, &dateRange
) < 0)) {
675 WINE_TRACE("Skipping file as modified date too old\n");
679 /* If just /D supplied, only overwrite if src newer than dest */
680 if (!skipFile
&& (flags
& OPT_DATENEWER
) &&
681 (destAttribs
!= INVALID_FILE_ATTRIBUTES
)) {
682 HANDLE h
= CreateFileW(copyTo
, GENERIC_READ
, FILE_SHARE_READ
,
683 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
,
685 if (h
!= INVALID_HANDLE_VALUE
) {
687 GetFileTime(h
, NULL
, NULL
, &writeTime
);
689 if (CompareFileTime(&finddata
->ftLastWriteTime
, &writeTime
) <= 0) {
690 WINE_TRACE("Skipping file as dest newer or same date\n");
697 /* See if exclude list provided. Note since filenames are case
698 insensitive, need to uppercase the filename before doing
700 if (!skipFile
&& (flags
& OPT_EXCLUDELIST
)) {
701 EXCLUDELIST
*pos
= excludeList
;
702 WCHAR copyFromUpper
[MAX_PATH
];
704 /* Uppercase source filename */
705 lstrcpyW(copyFromUpper
, copyFrom
);
706 CharUpperBuffW(copyFromUpper
, lstrlenW(copyFromUpper
));
708 /* Loop through testing each exclude line */
710 if (wcsstr(copyFromUpper
, pos
->name
) != NULL
) {
711 WINE_TRACE("Skipping file as matches exclude '%s'\n",
712 wine_dbgstr_w(pos
->name
));
721 /* Prompt each file if necessary */
722 if (!skipFile
&& (flags
& OPT_SRCPROMPT
)) {
725 BOOL answered
= FALSE
;
729 /* Read the Y and N characters from the resource file */
730 wcscpy(yesChar
, XCOPY_LoadMessage(STRING_YES_CHAR
));
731 wcscpy(noChar
, XCOPY_LoadMessage(STRING_NO_CHAR
));
734 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT
), copyFrom
);
735 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
739 if (toupper(answer
[0]) == noChar
[0])
741 else if (toupper(answer
[0]) != yesChar
[0])
747 destAttribs
!= INVALID_FILE_ATTRIBUTES
&& !(flags
& OPT_NOPROMPT
)) {
750 BOOL answered
= FALSE
;
755 /* Read the A,Y and N characters from the resource file */
756 wcscpy(yesChar
, XCOPY_LoadMessage(STRING_YES_CHAR
));
757 wcscpy(allChar
, XCOPY_LoadMessage(STRING_ALL_CHAR
));
758 wcscpy(noChar
, XCOPY_LoadMessage(STRING_NO_CHAR
));
761 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE
), copyTo
);
762 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
766 if (toupper(answer
[0]) == allChar
[0])
767 flags
|= OPT_NOPROMPT
;
768 else if (toupper(answer
[0]) == noChar
[0])
770 else if (toupper(answer
[0]) != yesChar
[0])
775 /* See if it has to exist! */
776 if (destAttribs
== INVALID_FILE_ATTRIBUTES
&& (flags
& OPT_MUSTEXIST
)) {
780 /* Output a status message */
782 if (flags
& OPT_QUIET
) {
784 } else if (flags
& OPT_FULL
) {
785 const WCHAR infostr
[] = {'%', 's', ' ', '-', '>', ' ',
788 XCOPY_wprintf(infostr
, copyFrom
, copyTo
);
790 const WCHAR infostr
[] = {'%', 's', '\n', 0};
791 XCOPY_wprintf(infostr
, copyFrom
);
794 /* If allowing overwriting of read only files, remove any
796 if ((destAttribs
& FILE_ATTRIBUTE_READONLY
) &&
797 (flags
& OPT_REPLACEREAD
)) {
798 SetFileAttributesW(copyTo
, destAttribs
& ~FILE_ATTRIBUTE_READONLY
);
802 if (flags
& OPT_SIMULATE
|| flags
& OPT_NOCOPY
) {
804 } else if (CopyFileW(copyFrom
, copyTo
, FALSE
) == 0) {
806 DWORD error
= GetLastError();
807 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL
),
808 copyFrom
, copyTo
, error
);
809 XCOPY_FailMessage(error
);
811 if (flags
& OPT_IGNOREERRORS
) {
819 /* If /M supplied, remove the archive bit after successful copy */
821 if ((srcAttribs
& FILE_ATTRIBUTE_ARCHIVE
) &&
822 (flags
& OPT_REMOVEARCH
)) {
823 SetFileAttributesW(copyFrom
, (srcAttribs
& ~FILE_ATTRIBUTE_ARCHIVE
));
831 findres
= FindNextFileW(h
, finddata
);
835 /* Search 2 - do subdirs */
836 if (flags
& OPT_RECURSIVE
) {
837 lstrcpyW(inputpath
, srcstem
);
838 lstrcatW(inputpath
, wchr_star
);
840 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath
));
842 h
= FindFirstFileW(inputpath
, finddata
);
843 while (h
!= INVALID_HANDLE_VALUE
&& findres
) {
845 /* Only looking for dirs */
846 if ((finddata
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
847 (lstrcmpW(finddata
->cFileName
, wchr_dot
) != 0) &&
848 (lstrcmpW(finddata
->cFileName
, wchr_dotdot
) != 0)) {
850 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata
->cFileName
));
852 /* Make up recursive information */
853 lstrcpyW(inputpath
, srcstem
);
854 lstrcatW(inputpath
, finddata
->cFileName
);
855 lstrcatW(inputpath
, wchr_slash
);
857 lstrcpyW(outputpath
, deststem
);
858 if (*destspec
== 0x00) {
859 lstrcatW(outputpath
, finddata
->cFileName
);
861 /* If /E is supplied, create the directory now */
862 if ((flags
& OPT_EMPTYDIR
) &&
863 !(flags
& OPT_SIMULATE
))
864 XCOPY_CreateDirectory(outputpath
);
866 lstrcatW(outputpath
, wchr_slash
);
869 XCOPY_DoCopy(inputpath
, srcspec
, outputpath
, destspec
, flags
);
873 findres
= FindNextFileW(h
, finddata
);
880 HeapFree(GetProcessHeap(), 0, finddata
);
881 HeapFree(GetProcessHeap(), 0, inputpath
);
882 HeapFree(GetProcessHeap(), 0, outputpath
);
887 /* =========================================================================
888 * Routine copied from cmd.exe md command -
889 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
890 * dir2 if they do not already exist.
891 * ========================================================================= */
892 static BOOL
XCOPY_CreateDirectory(const WCHAR
* path
)
898 new_path
= HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR
) * (lstrlenW(path
)+1));
899 lstrcpyW(new_path
,path
);
901 while ((len
= lstrlenW(new_path
)) && new_path
[len
- 1] == '\\')
902 new_path
[len
- 1] = 0;
904 while (!CreateDirectoryW(new_path
,NULL
))
907 DWORD last_error
= GetLastError();
908 if (last_error
== ERROR_ALREADY_EXISTS
)
911 if (last_error
!= ERROR_PATH_NOT_FOUND
)
917 if (!(slash
= wcsrchr(new_path
,'\\')) && ! (slash
= wcsrchr(new_path
,'/')))
923 len
= slash
- new_path
;
925 if (!XCOPY_CreateDirectory(new_path
))
930 new_path
[len
] = '\\';
932 HeapFree(GetProcessHeap(),0,new_path
);
936 /* =========================================================================
937 * Process the /EXCLUDE: file list, building up a list of substrings to
939 * Returns TRUE on any failure
940 * ========================================================================= */
941 static BOOL
XCOPY_ProcessExcludeList(WCHAR
* parms
) {
943 WCHAR
*filenameStart
= parms
;
945 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms
));
948 while (*parms
&& *parms
!= ' ' && *parms
!= '/') {
950 /* If found '+' then process the file found so far */
952 if (XCOPY_ProcessExcludeFile(filenameStart
, parms
)) {
955 filenameStart
= parms
+1;
960 if (filenameStart
!= parms
) {
961 if (XCOPY_ProcessExcludeFile(filenameStart
, parms
)) {
969 /* =========================================================================
970 * Process a single file from the /EXCLUDE: file list, building up a list
971 * of substrings to avoid copying
972 * Returns TRUE on any failure
973 * ========================================================================= */
974 static BOOL
XCOPY_ProcessExcludeFile(WCHAR
* filename
, WCHAR
* endOfName
) {
976 WCHAR endChar
= *endOfName
;
977 WCHAR buffer
[MAXSTRING
];
979 const WCHAR readTextMode
[] = {'r', 't', 0};
981 /* Null terminate the filename (temporarily updates the filename hence
986 inFile
= _wfopen(filename
, readTextMode
);
987 if (inFile
== NULL
) {
988 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL
), filename
);
989 *endOfName
= endChar
;
993 /* Process line by line */
994 while (fgetws(buffer
, sizeof(buffer
)/sizeof(WCHAR
), inFile
) != NULL
) {
995 EXCLUDELIST
*thisEntry
;
996 int length
= lstrlenW(buffer
);
999 buffer
[length
-1] = 0x00;
1001 /* If more than CRLF */
1003 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST
));
1004 thisEntry
->next
= excludeList
;
1005 excludeList
= thisEntry
;
1006 thisEntry
->name
= HeapAlloc(GetProcessHeap(), 0,
1007 (length
* sizeof(WCHAR
))+1);
1008 lstrcpyW(thisEntry
->name
, buffer
);
1009 CharUpperBuffW(thisEntry
->name
, length
);
1010 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry
->name
));
1014 /* See if EOF or error occurred */
1015 if (!feof(inFile
)) {
1016 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL
), filename
);
1017 *endOfName
= endChar
;
1021 /* Revert the input string to original form, and cleanup + return */
1022 *endOfName
= endChar
;
1027 /* =========================================================================
1028 * Load a string from the resource file, handling any error
1029 * Returns string retrieved from resource file
1030 * ========================================================================= */
1031 static WCHAR
*XCOPY_LoadMessage(UINT id
) {
1032 static WCHAR msg
[MAXSTRING
];
1033 const WCHAR failedMsg
[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
1035 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1036 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1037 lstrcpyW(msg
, failedMsg
);
1042 /* =========================================================================
1043 * Load a string for a system error and writes it to the screen
1044 * Returns string retrieved from resource file
1045 * ========================================================================= */
1046 static void XCOPY_FailMessage(DWORD err
) {
1050 status
= FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
|
1051 FORMAT_MESSAGE_FROM_SYSTEM
,
1053 (LPWSTR
) &lpMsgBuf
, 0, NULL
);
1055 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
1056 err
, GetLastError());
1058 const WCHAR infostr
[] = {'%', 's', '\n', 0};
1059 XCOPY_wprintf(infostr
, lpMsgBuf
);
1060 LocalFree ((HLOCAL
)lpMsgBuf
);
1064 /* =========================================================================
1065 * Output a formatted unicode string. Ideally this will go to the console
1066 * and hence required WriteConsoleW to output it, however if file i/o is
1067 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1068 * ========================================================================= */
1069 int XCOPY_wprintf(const WCHAR
*format
, ...) {
1071 static WCHAR
*output_bufW
= NULL
;
1072 static char *output_bufA
= NULL
;
1073 static BOOL toConsole
= TRUE
;
1074 static BOOL traceOutput
= FALSE
;
1075 #define MAX_WRITECONSOLE_SIZE 65535
1083 * Allocate buffer to use when writing to console
1084 * Note: Not freed - memory will be allocated once and released when
1088 if (!output_bufW
) output_bufW
= HeapAlloc(GetProcessHeap(), 0,
1089 MAX_WRITECONSOLE_SIZE
);
1091 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1095 va_start(parms
, format
);
1096 len
= vsnprintfW(output_bufW
, MAX_WRITECONSOLE_SIZE
/sizeof(WCHAR
), format
, parms
);
1099 WINE_FIXME("String too long.\n");
1103 /* Try to write as unicode all the time we think its a console */
1105 res
= WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE
),
1106 output_bufW
, len
, &nOut
, NULL
);
1109 /* If writing to console has failed (ever) we assume its file
1110 i/o so convert to OEM codepage and output */
1112 BOOL usedDefaultChar
= FALSE
;
1113 DWORD convertedChars
;
1118 * Allocate buffer to use when writing to file. Not freed, as above
1120 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1121 MAX_WRITECONSOLE_SIZE
);
1123 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1127 /* Convert to OEM, then output */
1128 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW
,
1129 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1130 "?", &usedDefaultChar
);
1131 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), output_bufA
, convertedChars
,
1135 /* Trace whether screen or console */
1137 WINE_TRACE("Writing to console? (%d)\n", toConsole
);