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>
48 WINE_DEFAULT_DEBUG_CHANNEL(xcopy
);
51 static int XCOPY_ProcessSourceParm(WCHAR
*suppliedsource
, WCHAR
*stem
,
52 WCHAR
*spec
, DWORD flags
);
53 static int XCOPY_ProcessDestParm(WCHAR
*supplieddestination
, WCHAR
*stem
,
54 WCHAR
*spec
, WCHAR
*srcspec
, DWORD flags
);
55 static int XCOPY_DoCopy(WCHAR
*srcstem
, WCHAR
*srcspec
,
56 WCHAR
*deststem
, WCHAR
*destspec
,
58 static BOOL
XCOPY_CreateDirectory(const WCHAR
* path
);
59 static BOOL
XCOPY_ProcessExcludeList(WCHAR
* parms
);
60 static BOOL
XCOPY_ProcessExcludeFile(WCHAR
* filename
, WCHAR
* endOfName
);
61 static WCHAR
*XCOPY_LoadMessage(UINT id
);
62 static void XCOPY_FailMessage(DWORD err
);
63 static int XCOPY_wprintf(const WCHAR
*format
, ...);
66 typedef struct _EXCLUDELIST
68 struct _EXCLUDELIST
*next
;
73 /* Global variables */
74 static ULONG filesCopied
= 0; /* Number of files copied */
75 static EXCLUDELIST
*excludeList
= NULL
; /* Excluded strings list */
76 static FILETIME dateRange
; /* Date range to copy after*/
77 static const WCHAR wchr_slash
[] = {'\\', 0};
78 static const WCHAR wchr_star
[] = {'*', 0};
79 static const WCHAR wchr_dot
[] = {'.', 0};
80 static const WCHAR wchr_dotdot
[] = {'.', '.', 0};
82 /* Constants (Mostly for widechars) */
85 /* To minimize stack usage during recursion, some temporary variables
87 static WCHAR copyFrom
[MAX_PATH
];
88 static WCHAR copyTo
[MAX_PATH
];
91 /* =========================================================================
92 main - Main entrypoint for the xcopy command
94 Processes the args, and drives the actual copying
95 ========================================================================= */
96 int wmain (int argc
, WCHAR
*argvW
[])
99 WCHAR suppliedsource
[MAX_PATH
] = {0}; /* As supplied on the cmd line */
100 WCHAR supplieddestination
[MAX_PATH
] = {0};
101 WCHAR sourcestem
[MAX_PATH
] = {0}; /* Stem of source */
102 WCHAR sourcespec
[MAX_PATH
] = {0}; /* Filespec of source */
103 WCHAR destinationstem
[MAX_PATH
] = {0}; /* Stem of destination */
104 WCHAR destinationspec
[MAX_PATH
] = {0}; /* Filespec of destination */
105 WCHAR copyCmd
[MAXSTRING
]; /* COPYCMD env var */
106 DWORD flags
= 0; /* Option flags */
107 const WCHAR PROMPTSTR1
[] = {'/', 'Y', 0};
108 const WCHAR PROMPTSTR2
[] = {'/', 'y', 0};
109 const WCHAR COPYCMD
[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
110 const WCHAR EXCLUDE
[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
113 * Parse the command line
116 /* Confirm at least one parameter */
118 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS
));
122 /* Preinitialize flags based on COPYCMD */
123 if (GetEnvironmentVariable(COPYCMD
, copyCmd
, MAXSTRING
)) {
124 if (wcsstr(copyCmd
, PROMPTSTR1
) != NULL
||
125 wcsstr(copyCmd
, PROMPTSTR2
) != NULL
) {
126 flags
|= OPT_NOPROMPT
;
130 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
131 wine, but on windows these can be normal files. At least one installer
132 uses files such as .packlist and (validly) expects them to be copied.
133 Under wine, if we do not copy hidden files by default then they get
135 flags
|= OPT_COPYHIDSYS
;
137 /* Skip first arg, which is the program name */
143 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW
));
145 /* First non-switch parameter is source, second is destination */
146 if (*argvW
[0] != '/') {
147 if (suppliedsource
[0] == 0x00) {
148 lstrcpyW(suppliedsource
, *argvW
);
149 } else if (supplieddestination
[0] == 0x00) {
150 lstrcpyW(supplieddestination
, *argvW
);
152 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS
));
156 /* Process all the switch options
157 Note: Windows docs say /P prompts when dest is created
158 but tests show it is done for each src file
159 regardless of the destination */
160 switch (toupper(argvW
[0][1])) {
161 case 'I': flags
|= OPT_ASSUMEDIR
; break;
162 case 'S': flags
|= OPT_RECURSIVE
; break;
163 case 'Q': flags
|= OPT_QUIET
; break;
164 case 'F': flags
|= OPT_FULL
; break;
165 case 'L': flags
|= OPT_SIMULATE
; break;
166 case 'W': flags
|= OPT_PAUSE
; break;
167 case 'T': flags
|= OPT_NOCOPY
| OPT_RECURSIVE
; break;
168 case 'Y': flags
|= OPT_NOPROMPT
; break;
169 case 'N': flags
|= OPT_SHORTNAME
; break;
170 case 'U': flags
|= OPT_MUSTEXIST
; break;
171 case 'R': flags
|= OPT_REPLACEREAD
; break;
172 case 'H': flags
|= OPT_COPYHIDSYS
; break;
173 case 'C': flags
|= OPT_IGNOREERRORS
; break;
174 case 'P': flags
|= OPT_SRCPROMPT
; break;
175 case 'A': flags
|= OPT_ARCHIVEONLY
; break;
176 case 'M': flags
|= OPT_ARCHIVEONLY
|
177 OPT_REMOVEARCH
; break;
179 /* E can be /E or /EXCLUDE */
180 case 'E': if (CompareString (LOCALE_USER_DEFAULT
,
181 NORM_IGNORECASE
| SORT_STRINGSORT
,
184 if (XCOPY_ProcessExcludeList(&argvW
[0][9])) {
185 XCOPY_FailMessage(ERROR_INVALID_PARAMETER
);
187 } else flags
|= OPT_EXCLUDELIST
;
188 } else flags
|= OPT_EMPTYDIR
| OPT_RECURSIVE
;
191 /* D can be /D or /D: */
192 case 'D': if ((argvW
[0][2])==':' && isdigit(argvW
[0][3])) {
194 WCHAR
*pos
= &argvW
[0][3];
195 BOOL isError
= FALSE
;
196 memset(&st
, 0x00, sizeof(st
));
198 /* Parse the arg : Month */
199 st
.wMonth
= _wtol(pos
);
200 while (*pos
&& isdigit(*pos
)) pos
++;
201 if (*pos
++ != '-') isError
= TRUE
;
203 /* Parse the arg : Day */
205 st
.wDay
= _wtol(pos
);
206 while (*pos
&& isdigit(*pos
)) pos
++;
207 if (*pos
++ != '-') isError
= TRUE
;
210 /* Parse the arg : Day */
212 st
.wYear
= _wtol(pos
);
213 if (st
.wYear
< 100) st
.wYear
+=2000;
216 if (!isError
&& SystemTimeToFileTime(&st
, &dateRange
)) {
218 WCHAR datestring
[32], timestring
[32];
220 flags
|= OPT_DATERANGE
;
223 FileTimeToSystemTime (&dateRange
, &st
);
224 GetDateFormat (0, DATE_SHORTDATE
, &st
, NULL
, datestring
,
226 GetTimeFormat (0, TIME_NOSECONDS
, &st
,
227 NULL
, timestring
, sizeof(timestring
));
229 WINE_TRACE("Date being used is: %s %s\n",
230 wine_dbgstr_w(datestring
), wine_dbgstr_w(timestring
));
232 XCOPY_FailMessage(ERROR_INVALID_PARAMETER
);
236 flags
|= OPT_DATENEWER
;
240 case '-': if (toupper(argvW
[0][2])=='Y')
241 flags
&= ~OPT_NOPROMPT
; break;
242 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP
));
245 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW
));
246 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM
), *argvW
);
253 /* Default the destination if not supplied */
254 if (supplieddestination
[0] == 0x00)
255 lstrcpyW(supplieddestination
, wchr_dot
);
257 /* Trace out the supplied information */
258 WINE_TRACE("Supplied parameters:\n");
259 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource
));
260 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination
));
262 /* Extract required information from source specification */
263 rc
= XCOPY_ProcessSourceParm(suppliedsource
, sourcestem
, sourcespec
, flags
);
265 /* Extract required information from destination specification */
266 rc
= XCOPY_ProcessDestParm(supplieddestination
, destinationstem
,
267 destinationspec
, sourcespec
, flags
);
269 /* Trace out the resulting information */
270 WINE_TRACE("Resolved parameters:\n");
271 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem
));
272 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec
));
273 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem
));
274 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec
));
276 /* Pause if necessary */
277 if (flags
& OPT_PAUSE
) {
281 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE
));
282 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), pausestr
, sizeof(pausestr
),
286 /* Now do the hard work... */
287 rc
= XCOPY_DoCopy(sourcestem
, sourcespec
,
288 destinationstem
, destinationspec
,
291 /* Clear up exclude list allocated memory */
292 while (excludeList
) {
293 EXCLUDELIST
*pos
= excludeList
;
294 excludeList
= excludeList
-> next
;
295 HeapFree(GetProcessHeap(), 0, pos
->name
);
296 HeapFree(GetProcessHeap(), 0, pos
);
299 /* Finished - print trailer and exit */
300 if (flags
& OPT_SIMULATE
) {
301 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY
), filesCopied
);
302 } else if (!(flags
& OPT_NOCOPY
)) {
303 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY
), filesCopied
);
305 if (rc
== RC_OK
&& filesCopied
== 0) rc
= RC_NOFILES
;
311 /* =========================================================================
312 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
313 converts it into a stem and a filespec
314 ========================================================================= */
315 static int XCOPY_ProcessSourceParm(WCHAR
*suppliedsource
, WCHAR
*stem
,
316 WCHAR
*spec
, DWORD flags
)
318 WCHAR actualsource
[MAX_PATH
];
324 * Validate the source, expanding to full path ensuring it exists
326 if (GetFullPathName(suppliedsource
, MAX_PATH
, actualsource
, NULL
) == 0) {
327 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
331 /* If full names required, convert to using the full path */
332 if (flags
& OPT_FULL
) {
333 lstrcpyW(suppliedsource
, actualsource
);
337 * Work out the stem of the source
340 /* If a directory is supplied, use that as-is (either fully or
342 If a filename is supplied + a directory or drive path, use that
345 If no directory or path specified, add eg. C:
346 stem is Drive/Directory is bit up to last \ (or first :)
347 spec is bit after that */
349 starPos
= wcschr(suppliedsource
, '*');
350 questPos
= wcschr(suppliedsource
, '?');
351 if (starPos
|| questPos
) {
352 attribs
= 0x00; /* Ensures skips invalid or directory check below */
354 attribs
= GetFileAttributes(actualsource
);
357 if (attribs
== INVALID_FILE_ATTRIBUTES
) {
358 XCOPY_FailMessage(GetLastError());
362 stem should be exactly as supplied plus a '\', unless it was
363 eg. C: in which case no slash required */
364 } else if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
367 WINE_TRACE("Directory supplied\n");
368 lstrcpyW(stem
, suppliedsource
);
369 lastChar
= stem
[lstrlenW(stem
)-1];
370 if (lastChar
!= '\\' && lastChar
!= ':') {
371 lstrcatW(stem
, wchr_slash
);
373 lstrcpyW(spec
, wchr_star
);
375 /* File or wildcard search:
377 Up to and including last slash if directory path supplied
378 If c:filename supplied, just the c:
379 Otherwise stem should be the current drive letter + ':' */
383 WINE_TRACE("Filename supplied\n");
384 lastDir
= wcsrchr(suppliedsource
, '\\');
387 lstrcpyW(stem
, suppliedsource
);
388 stem
[(lastDir
-suppliedsource
) + 1] = 0x00;
389 lstrcpyW(spec
, (lastDir
+1));
390 } else if (suppliedsource
[1] == ':') {
391 lstrcpyW(stem
, suppliedsource
);
393 lstrcpyW(spec
, suppliedsource
+2);
395 WCHAR curdir
[MAXSTRING
];
396 GetCurrentDirectory (sizeof(curdir
), curdir
);
400 lstrcpyW(spec
, suppliedsource
);
407 /* =========================================================================
408 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
409 converts it into a stem
410 ========================================================================= */
411 static int XCOPY_ProcessDestParm(WCHAR
*supplieddestination
, WCHAR
*stem
, WCHAR
*spec
,
412 WCHAR
*srcspec
, DWORD flags
)
414 WCHAR actualdestination
[MAX_PATH
];
419 * Validate the source, expanding to full path ensuring it exists
421 if (GetFullPathName(supplieddestination
, MAX_PATH
, actualdestination
, NULL
) == 0) {
422 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
426 /* Destination is either a directory or a file */
427 attribs
= GetFileAttributes(actualdestination
);
429 if (attribs
== INVALID_FILE_ATTRIBUTES
) {
431 /* If /I supplied and wildcard copy, assume directory */
432 if (flags
& OPT_ASSUMEDIR
&&
433 (wcschr(srcspec
, '?') || wcschr(srcspec
, '*'))) {
439 char answer
[10] = "";
443 /* Read the F and D characters from the resource file */
444 wcscpy(fileChar
, XCOPY_LoadMessage(STRING_FILE_CHAR
));
445 wcscpy(dirChar
, XCOPY_LoadMessage(STRING_DIR_CHAR
));
447 while (answer
[0] != fileChar
[0] && answer
[0] != dirChar
[0]) {
448 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR
), supplieddestination
);
450 ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
), &count
, NULL
);
451 WINE_TRACE("User answer %c\n", answer
[0]);
453 answer
[0] = toupper(answer
[0]);
456 if (answer
[0] == dirChar
[0]) {
463 isDir
= (attribs
& FILE_ATTRIBUTE_DIRECTORY
);
467 lstrcpyW(stem
, actualdestination
);
470 /* Ensure ends with a '\' */
471 if (stem
[lstrlenW(stem
)-1] != '\\') {
472 lstrcatW(stem
, wchr_slash
);
476 WCHAR drive
[MAX_PATH
];
478 WCHAR fname
[MAX_PATH
];
480 _wsplitpath(actualdestination
, drive
, dir
, fname
, ext
);
481 lstrcpyW(stem
, drive
);
483 lstrcpyW(spec
, fname
);
489 /* =========================================================================
490 XCOPY_DoCopy - Recursive function to copy files based on input parms
493 This works by using FindFirstFile supplying the source stem and spec.
494 If results are found, any non-directory ones are processed
495 Then, if /S or /E is supplied, another search is made just for
496 directories, and this function is called again for that directory
498 ========================================================================= */
499 static int XCOPY_DoCopy(WCHAR
*srcstem
, WCHAR
*srcspec
,
500 WCHAR
*deststem
, WCHAR
*destspec
,
503 WIN32_FIND_DATA
*finddata
;
506 WCHAR
*inputpath
, *outputpath
;
507 BOOL copiedFile
= FALSE
;
508 DWORD destAttribs
, srcAttribs
;
511 /* Allocate some working memory on heap to minimize footprint */
512 finddata
= HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA
));
513 inputpath
= HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
));
514 outputpath
= HeapAlloc(GetProcessHeap(), 0, MAX_PATH
* sizeof(WCHAR
));
516 /* Build the search info into a single parm */
517 lstrcpyW(inputpath
, srcstem
);
518 lstrcatW(inputpath
, srcspec
);
520 /* Search 1 - Look for matching files */
521 h
= FindFirstFile(inputpath
, finddata
);
522 while (h
!= INVALID_HANDLE_VALUE
&& findres
) {
526 /* Ignore . and .. */
527 if (lstrcmpW(finddata
->cFileName
, wchr_dot
)==0 ||
528 lstrcmpW(finddata
->cFileName
, wchr_dotdot
)==0 ||
529 finddata
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
531 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata
->cFileName
));
534 /* Get the filename information */
535 lstrcpyW(copyFrom
, srcstem
);
536 if (flags
& OPT_SHORTNAME
) {
537 lstrcatW(copyFrom
, finddata
->cAlternateFileName
);
539 lstrcatW(copyFrom
, finddata
->cFileName
);
542 lstrcpyW(copyTo
, deststem
);
543 if (*destspec
== 0x00) {
544 if (flags
& OPT_SHORTNAME
) {
545 lstrcatW(copyTo
, finddata
->cAlternateFileName
);
547 lstrcatW(copyTo
, finddata
->cFileName
);
550 lstrcatW(copyTo
, destspec
);
554 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom
),
555 wine_dbgstr_w(copyTo
));
556 if (!copiedFile
&& !(flags
& OPT_SIMULATE
)) XCOPY_CreateDirectory(deststem
);
558 /* See if allowed to copy it */
559 srcAttribs
= GetFileAttributesW(copyFrom
);
560 WINE_TRACE("Source attribs: %d\n", srcAttribs
);
562 if ((srcAttribs
& FILE_ATTRIBUTE_HIDDEN
) ||
563 (srcAttribs
& FILE_ATTRIBUTE_SYSTEM
)) {
565 if (!(flags
& OPT_COPYHIDSYS
)) {
570 if (!(srcAttribs
& FILE_ATTRIBUTE_ARCHIVE
) &&
571 (flags
& OPT_ARCHIVEONLY
)) {
575 /* See if file exists */
576 destAttribs
= GetFileAttributesW(copyTo
);
577 WINE_TRACE("Dest attribs: %d\n", srcAttribs
);
579 /* Check date ranges if a destination file already exists */
580 if (!skipFile
&& (flags
& OPT_DATERANGE
) &&
581 (CompareFileTime(&finddata
->ftLastWriteTime
, &dateRange
) < 0)) {
582 WINE_TRACE("Skipping file as modified date too old\n");
586 /* If just /D supplied, only overwrite if src newer than dest */
587 if (!skipFile
&& (flags
& OPT_DATENEWER
) &&
588 (destAttribs
!= INVALID_FILE_ATTRIBUTES
)) {
589 HANDLE h
= CreateFile(copyTo
, GENERIC_READ
, FILE_SHARE_READ
,
590 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
,
592 if (h
!= INVALID_HANDLE_VALUE
) {
594 GetFileTime(h
, NULL
, NULL
, &writeTime
);
596 if (CompareFileTime(&finddata
->ftLastWriteTime
, &writeTime
) <= 0) {
597 WINE_TRACE("Skipping file as dest newer or same date\n");
604 /* See if exclude list provided. Note since filenames are case
605 insensitive, need to uppercase the filename before doing
607 if (!skipFile
&& (flags
& OPT_EXCLUDELIST
)) {
608 EXCLUDELIST
*pos
= excludeList
;
609 WCHAR copyFromUpper
[MAX_PATH
];
611 /* Uppercase source filename */
612 lstrcpyW(copyFromUpper
, copyFrom
);
613 CharUpperBuff(copyFromUpper
, lstrlenW(copyFromUpper
));
615 /* Loop through testing each exclude line */
617 if (wcsstr(copyFromUpper
, pos
->name
) != NULL
) {
618 WINE_TRACE("Skipping file as matches exclude '%s'\n",
619 wine_dbgstr_w(pos
->name
));
628 /* Prompt each file if necessary */
629 if (!skipFile
&& (flags
& OPT_SRCPROMPT
)) {
632 BOOL answered
= FALSE
;
636 /* Read the Y and N characters from the resource file */
637 wcscpy(yesChar
, XCOPY_LoadMessage(STRING_YES_CHAR
));
638 wcscpy(noChar
, XCOPY_LoadMessage(STRING_NO_CHAR
));
641 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT
), copyFrom
);
642 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
646 if (toupper(answer
[0]) == noChar
[0])
648 else if (toupper(answer
[0]) != yesChar
[0])
654 destAttribs
!= INVALID_FILE_ATTRIBUTES
&& !(flags
& OPT_NOPROMPT
)) {
657 BOOL answered
= FALSE
;
662 /* Read the A,Y and N characters from the resource file */
663 wcscpy(yesChar
, XCOPY_LoadMessage(STRING_YES_CHAR
));
664 wcscpy(allChar
, XCOPY_LoadMessage(STRING_ALL_CHAR
));
665 wcscpy(noChar
, XCOPY_LoadMessage(STRING_NO_CHAR
));
668 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE
), copyTo
);
669 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
673 if (toupper(answer
[0]) == allChar
[0])
674 flags
|= OPT_NOPROMPT
;
675 else if (toupper(answer
[0]) == noChar
[0])
677 else if (toupper(answer
[0]) != yesChar
[0])
682 /* See if it has to exist! */
683 if (destAttribs
== INVALID_FILE_ATTRIBUTES
&& (flags
& OPT_MUSTEXIST
)) {
687 /* Output a status message */
689 if (flags
& OPT_QUIET
) {
691 } else if (flags
& OPT_FULL
) {
692 const WCHAR infostr
[] = {'%', 's', ' ', '-', '>', ' ',
695 XCOPY_wprintf(infostr
, copyFrom
, copyTo
);
697 const WCHAR infostr
[] = {'%', 's', '\n', 0};
698 XCOPY_wprintf(infostr
, copyFrom
);
701 /* If allowing overwriting of read only files, remove any
703 if ((destAttribs
& FILE_ATTRIBUTE_READONLY
) &&
704 (flags
& OPT_REPLACEREAD
)) {
705 SetFileAttributes(copyTo
, destAttribs
& ~FILE_ATTRIBUTE_READONLY
);
709 if (flags
& OPT_SIMULATE
|| flags
& OPT_NOCOPY
) {
711 } else if (CopyFile(copyFrom
, copyTo
, FALSE
) == 0) {
713 DWORD error
= GetLastError();
714 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL
),
715 copyFrom
, copyTo
, error
);
716 XCOPY_FailMessage(error
);
718 if (flags
& OPT_IGNOREERRORS
) {
721 return RC_WRITEERROR
;
725 /* If /M supplied, remove the archive bit after successful copy */
727 if ((srcAttribs
& FILE_ATTRIBUTE_ARCHIVE
) &&
728 (flags
& OPT_REMOVEARCH
)) {
729 SetFileAttributes(copyFrom
, (srcAttribs
& ~FILE_ATTRIBUTE_ARCHIVE
));
737 findres
= FindNextFile(h
, finddata
);
741 /* Search 2 - do subdirs */
742 if (flags
& OPT_RECURSIVE
) {
743 lstrcpyW(inputpath
, srcstem
);
744 lstrcatW(inputpath
, wchr_star
);
746 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath
));
748 h
= FindFirstFile(inputpath
, finddata
);
749 while (h
!= INVALID_HANDLE_VALUE
&& findres
) {
751 /* Only looking for dirs */
752 if ((finddata
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
753 (lstrcmpW(finddata
->cFileName
, wchr_dot
) != 0) &&
754 (lstrcmpW(finddata
->cFileName
, wchr_dotdot
) != 0)) {
756 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata
->cFileName
));
758 /* Make up recursive information */
759 lstrcpyW(inputpath
, srcstem
);
760 lstrcatW(inputpath
, finddata
->cFileName
);
761 lstrcatW(inputpath
, wchr_slash
);
763 lstrcpyW(outputpath
, deststem
);
764 if (*destspec
== 0x00) {
765 lstrcatW(outputpath
, finddata
->cFileName
);
767 /* If /E is supplied, create the directory now */
768 if ((flags
& OPT_EMPTYDIR
) &&
769 !(flags
& OPT_SIMULATE
))
770 XCOPY_CreateDirectory(outputpath
);
772 lstrcatW(outputpath
, wchr_slash
);
775 XCOPY_DoCopy(inputpath
, srcspec
, outputpath
, destspec
, flags
);
779 findres
= FindNextFile(h
, finddata
);
784 HeapFree(GetProcessHeap(), 0, finddata
);
785 HeapFree(GetProcessHeap(), 0, inputpath
);
786 HeapFree(GetProcessHeap(), 0, outputpath
);
791 /* =========================================================================
792 * Routine copied from cmd.exe md command -
793 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
794 * dir2 if they do not already exist.
795 * ========================================================================= */
796 static BOOL
XCOPY_CreateDirectory(const WCHAR
* path
)
802 new_path
= HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR
) * (lstrlenW(path
)+1));
803 lstrcpyW(new_path
,path
);
805 while ((len
= lstrlenW(new_path
)) && new_path
[len
- 1] == '\\')
806 new_path
[len
- 1] = 0;
808 while (!CreateDirectory(new_path
,NULL
))
811 DWORD last_error
= GetLastError();
812 if (last_error
== ERROR_ALREADY_EXISTS
)
815 if (last_error
!= ERROR_PATH_NOT_FOUND
)
821 if (!(slash
= wcsrchr(new_path
,'\\')) && ! (slash
= wcsrchr(new_path
,'/')))
827 len
= slash
- new_path
;
829 if (!XCOPY_CreateDirectory(new_path
))
834 new_path
[len
] = '\\';
836 HeapFree(GetProcessHeap(),0,new_path
);
840 /* =========================================================================
841 * Process the /EXCLUDE: file list, building up a list of substrings to
843 * Returns TRUE on any failure
844 * ========================================================================= */
845 static BOOL
XCOPY_ProcessExcludeList(WCHAR
* parms
) {
847 WCHAR
*filenameStart
= parms
;
849 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms
));
852 while (*parms
&& *parms
!= ' ' && *parms
!= '/') {
854 /* If found '+' then process the file found so far */
856 if (XCOPY_ProcessExcludeFile(filenameStart
, parms
)) {
859 filenameStart
= parms
+1;
864 if (filenameStart
!= parms
) {
865 if (XCOPY_ProcessExcludeFile(filenameStart
, parms
)) {
873 /* =========================================================================
874 * Process a single file from the /EXCLUDE: file list, building up a list
875 * of substrings to avoid copying
876 * Returns TRUE on any failure
877 * ========================================================================= */
878 static BOOL
XCOPY_ProcessExcludeFile(WCHAR
* filename
, WCHAR
* endOfName
) {
880 WCHAR endChar
= *endOfName
;
881 WCHAR buffer
[MAXSTRING
];
883 const WCHAR readTextMode
[] = {'r', 't', 0};
885 /* Null terminate the filename (temporarily updates the filename hence
890 inFile
= _wfopen(filename
, readTextMode
);
891 if (inFile
== NULL
) {
892 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL
), filename
);
893 *endOfName
= endChar
;
897 /* Process line by line */
898 while (fgetws(buffer
, sizeof(buffer
), inFile
) != NULL
) {
899 EXCLUDELIST
*thisEntry
;
900 int length
= lstrlenW(buffer
);
903 buffer
[length
-1] = 0x00;
905 /* If more than CRLF */
907 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST
));
908 thisEntry
->next
= excludeList
;
909 excludeList
= thisEntry
;
910 thisEntry
->name
= HeapAlloc(GetProcessHeap(), 0,
911 (length
* sizeof(WCHAR
))+1);
912 lstrcpyW(thisEntry
->name
, buffer
);
913 CharUpperBuff(thisEntry
->name
, length
);
914 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry
->name
));
918 /* See if EOF or error occurred */
920 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL
), filename
);
921 *endOfName
= endChar
;
925 /* Revert the input string to original form, and cleanup + return */
926 *endOfName
= endChar
;
931 /* =========================================================================
932 * Load a string from the resource file, handling any error
933 * Returns string retrieved from resource file
934 * ========================================================================= */
935 static WCHAR
*XCOPY_LoadMessage(UINT id
) {
936 static WCHAR msg
[MAXSTRING
];
937 const WCHAR failedMsg
[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
939 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
))) {
940 WINE_FIXME("LoadString failed with %d\n", GetLastError());
941 lstrcpyW(msg
, failedMsg
);
946 /* =========================================================================
947 * Load a string for a system error and writes it to the screen
948 * Returns string retrieved from resource file
949 * ========================================================================= */
950 static void XCOPY_FailMessage(DWORD err
) {
954 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
|
955 FORMAT_MESSAGE_FROM_SYSTEM
,
957 (LPTSTR
) &lpMsgBuf
, 0, NULL
);
959 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
960 err
, GetLastError());
962 const WCHAR infostr
[] = {'%', 's', '\n', 0};
963 XCOPY_wprintf(infostr
, lpMsgBuf
);
964 LocalFree ((HLOCAL
)lpMsgBuf
);
968 /* =========================================================================
969 * Output a formatted unicode string. Ideally this will go to the console
970 * and hence required WriteConsoleW to output it, however if file i/o is
971 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
972 * ========================================================================= */
973 int XCOPY_wprintf(const WCHAR
*format
, ...) {
975 static WCHAR
*output_bufW
= NULL
;
976 static char *output_bufA
= NULL
;
977 static BOOL toConsole
= TRUE
;
978 static BOOL traceOutput
= FALSE
;
979 #define MAX_WRITECONSOLE_SIZE 65535
986 * Allocate buffer to use when writing to console
987 * Note: Not freed - memory will be allocated once and released when
991 if (!output_bufW
) output_bufW
= HeapAlloc(GetProcessHeap(), 0,
992 MAX_WRITECONSOLE_SIZE
);
994 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
998 /* Use wvsprintf to store output into unicode buffer */
999 va_start(parms
, format
);
1000 len
= vswprintf(output_bufW
, format
, parms
);
1003 /* Try to write as unicode all the time we think its a console */
1005 res
= WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE
),
1006 output_bufW
, len
, &nOut
, NULL
);
1009 /* If writing to console has failed (ever) we assume its file
1010 i/o so convert to OEM codepage and output */
1012 BOOL usedDefaultChar
= FALSE
;
1013 DWORD convertedChars
;
1018 * Allocate buffer to use when writing to file. Not freed, as above
1020 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1021 MAX_WRITECONSOLE_SIZE
);
1023 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1027 /* Convert to OEM, then output */
1028 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW
,
1029 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1030 "?", &usedDefaultChar
);
1031 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), output_bufA
, convertedChars
,
1035 /* Trace whether screen or console */
1037 WINE_TRACE("Writing to console? (%d)\n", toConsole
);