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_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
53 WCHAR *spec, DWORD flags);
54 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
55 WCHAR *spec, WCHAR *srcspec, DWORD flags);
56 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
57 WCHAR *deststem, WCHAR *destspec,
59 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
60 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
61 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
62 static WCHAR *XCOPY_LoadMessage(UINT id);
63 static void XCOPY_FailMessage(DWORD err);
64 static int XCOPY_wprintf(const WCHAR *format, ...);
67 typedef struct _EXCLUDELIST
69 struct _EXCLUDELIST *next;
74 /* Global variables */
75 static ULONG filesCopied = 0; /* Number of files copied */
76 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
77 static FILETIME dateRange; /* Date range to copy after*/
78 static const WCHAR wchr_slash[] = {'\\', 0};
79 static const WCHAR wchr_star[] = {'*', 0};
80 static const WCHAR wchr_dot[] = {'.', 0};
81 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
83 /* Constants (Mostly for widechars) */
86 /* To minimize stack usage during recursion, some temporary variables
88 static WCHAR copyFrom[MAX_PATH];
89 static WCHAR copyTo[MAX_PATH];
92 /* =========================================================================
93 main - Main entrypoint for the xcopy command
95 Processes the args, and drives the actual copying
96 ========================================================================= */
97 int wmain (int argc, WCHAR *argvW[])
100 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
101 WCHAR supplieddestination[MAX_PATH] = {0};
102 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
103 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
104 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
105 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
106 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
107 DWORD flags = 0; /* Option flags */
108 const WCHAR PROMPTSTR1[] = {'/', 'Y', 0};
109 const WCHAR PROMPTSTR2[] = {'/', 'y', 0};
110 const WCHAR COPYCMD[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
111 const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
114 * Parse the command line
117 /* Confirm at least one parameter */
119 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
123 /* Preinitialize flags based on COPYCMD */
124 if (GetEnvironmentVariableW(COPYCMD, copyCmd, MAXSTRING)) {
125 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
126 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
127 flags |= OPT_NOPROMPT;
131 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
132 wine, but on windows these can be normal files. At least one installer
133 uses files such as .packlist and (validly) expects them to be copied.
134 Under wine, if we do not copy hidden files by default then they get
136 flags |= OPT_COPYHIDSYS;
138 /* Skip first arg, which is the program name */
144 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
146 /* First non-switch parameter is source, second is destination */
147 if (*argvW[0] != '/') {
148 if (suppliedsource[0] == 0x00) {
149 lstrcpyW(suppliedsource, *argvW);
150 } else if (supplieddestination[0] == 0x00) {
151 lstrcpyW(supplieddestination, *argvW);
153 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
157 /* Process all the switch options
158 Note: Windows docs say /P prompts when dest is created
159 but tests show it is done for each src file
160 regardless of the destination */
161 switch (toupper(argvW[0][1])) {
162 case 'I': flags |= OPT_ASSUMEDIR; break;
163 case 'S': flags |= OPT_RECURSIVE; break;
164 case 'Q': flags |= OPT_QUIET; break;
165 case 'F': flags |= OPT_FULL; break;
166 case 'L': flags |= OPT_SIMULATE; break;
167 case 'W': flags |= OPT_PAUSE; break;
168 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
169 case 'Y': flags |= OPT_NOPROMPT; break;
170 case 'N': flags |= OPT_SHORTNAME; break;
171 case 'U': flags |= OPT_MUSTEXIST; break;
172 case 'R': flags |= OPT_REPLACEREAD; break;
173 case 'H': flags |= OPT_COPYHIDSYS; break;
174 case 'C': flags |= OPT_IGNOREERRORS; break;
175 case 'P': flags |= OPT_SRCPROMPT; break;
176 case 'A': flags |= OPT_ARCHIVEONLY; break;
177 case 'M': flags |= OPT_ARCHIVEONLY |
178 OPT_REMOVEARCH; break;
180 /* E can be /E or /EXCLUDE */
181 case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
182 NORM_IGNORECASE | SORT_STRINGSORT,
185 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
186 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
188 } else flags |= OPT_EXCLUDELIST;
189 } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
192 /* D can be /D or /D: */
193 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
195 WCHAR *pos = &argvW[0][3];
196 BOOL isError = FALSE;
197 memset(&st, 0x00, sizeof(st));
199 /* Parse the arg : Month */
200 st.wMonth = _wtol(pos);
201 while (*pos && isdigit(*pos)) pos++;
202 if (*pos++ != '-') isError = TRUE;
204 /* Parse the arg : Day */
206 st.wDay = _wtol(pos);
207 while (*pos && isdigit(*pos)) pos++;
208 if (*pos++ != '-') isError = TRUE;
211 /* Parse the arg : Day */
213 st.wYear = _wtol(pos);
214 if (st.wYear < 100) st.wYear+=2000;
217 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
219 WCHAR datestring[32], timestring[32];
221 flags |= OPT_DATERANGE;
224 FileTimeToSystemTime (&dateRange, &st);
225 GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
226 sizeof(datestring)/sizeof(WCHAR));
227 GetTimeFormatW(0, TIME_NOSECONDS, &st,
228 NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
230 WINE_TRACE("Date being used is: %s %s\n",
231 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
233 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
237 flags |= OPT_DATENEWER;
241 case '-': if (toupper(argvW[0][2])=='Y')
242 flags &= ~OPT_NOPROMPT; break;
243 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
246 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
247 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
254 /* Default the destination if not supplied */
255 if (supplieddestination[0] == 0x00)
256 lstrcpyW(supplieddestination, wchr_dot);
258 /* Trace out the supplied information */
259 WINE_TRACE("Supplied parameters:\n");
260 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
261 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
263 /* Extract required information from source specification */
264 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
265 if (rc != RC_OK) return rc;
267 /* Extract required information from destination specification */
268 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
269 destinationspec, sourcespec, flags);
270 if (rc != RC_OK) return rc;
272 /* Trace out the resulting information */
273 WINE_TRACE("Resolved parameters:\n");
274 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
275 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
276 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
277 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
279 /* Pause if necessary */
280 if (flags & OPT_PAUSE) {
284 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
285 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
289 /* Now do the hard work... */
290 rc = XCOPY_DoCopy(sourcestem, sourcespec,
291 destinationstem, destinationspec,
294 /* Clear up exclude list allocated memory */
295 while (excludeList) {
296 EXCLUDELIST *pos = excludeList;
297 excludeList = excludeList -> next;
298 HeapFree(GetProcessHeap(), 0, pos->name);
299 HeapFree(GetProcessHeap(), 0, pos);
302 /* Finished - print trailer and exit */
303 if (flags & OPT_SIMULATE) {
304 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
305 } else if (!(flags & OPT_NOCOPY)) {
306 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
308 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
314 /* =========================================================================
315 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
316 converts it into a stem and a filespec
317 ========================================================================= */
318 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
319 WCHAR *spec, DWORD flags)
321 WCHAR actualsource[MAX_PATH];
327 * Validate the source, expanding to full path ensuring it exists
329 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
330 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
334 /* If full names required, convert to using the full path */
335 if (flags & OPT_FULL) {
336 lstrcpyW(suppliedsource, actualsource);
340 * Work out the stem of the source
343 /* If a directory is supplied, use that as-is (either fully or
345 If a filename is supplied + a directory or drive path, use that
348 If no directory or path specified, add eg. C:
349 stem is Drive/Directory is bit up to last \ (or first :)
350 spec is bit after that */
352 starPos = wcschr(suppliedsource, '*');
353 questPos = wcschr(suppliedsource, '?');
354 if (starPos || questPos) {
355 attribs = 0x00; /* Ensures skips invalid or directory check below */
357 attribs = GetFileAttributesW(actualsource);
360 if (attribs == INVALID_FILE_ATTRIBUTES) {
361 XCOPY_FailMessage(GetLastError());
365 stem should be exactly as supplied plus a '\', unless it was
366 eg. C: in which case no slash required */
367 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
370 WINE_TRACE("Directory supplied\n");
371 lstrcpyW(stem, suppliedsource);
372 lastChar = stem[lstrlenW(stem)-1];
373 if (lastChar != '\\' && lastChar != ':') {
374 lstrcatW(stem, wchr_slash);
376 lstrcpyW(spec, wchr_star);
378 /* File or wildcard search:
380 Up to and including last slash if directory path supplied
381 If c:filename supplied, just the c:
382 Otherwise stem should be the current drive letter + ':' */
386 WINE_TRACE("Filename supplied\n");
387 lastDir = wcsrchr(suppliedsource, '\\');
390 lstrcpyW(stem, suppliedsource);
391 stem[(lastDir-suppliedsource) + 1] = 0x00;
392 lstrcpyW(spec, (lastDir+1));
393 } else if (suppliedsource[1] == ':') {
394 lstrcpyW(stem, suppliedsource);
396 lstrcpyW(spec, suppliedsource+2);
398 WCHAR curdir[MAXSTRING];
399 GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
403 lstrcpyW(spec, suppliedsource);
410 /* =========================================================================
411 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
412 converts it into a stem
413 ========================================================================= */
414 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
415 WCHAR *srcspec, DWORD flags)
417 WCHAR actualdestination[MAX_PATH];
422 * Validate the source, expanding to full path ensuring it exists
424 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
425 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
429 /* Destination is either a directory or a file */
430 attribs = GetFileAttributesW(actualdestination);
432 if (attribs == INVALID_FILE_ATTRIBUTES) {
434 /* If /I supplied and wildcard copy, assume directory */
435 if (flags & OPT_ASSUMEDIR &&
436 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
442 char answer[10] = "";
446 /* Read the F and D characters from the resource file */
447 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
448 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
450 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
451 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
453 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
454 WINE_TRACE("User answer %c\n", answer[0]);
456 answer[0] = toupper(answer[0]);
459 if (answer[0] == dirChar[0]) {
466 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
470 lstrcpyW(stem, actualdestination);
473 /* Ensure ends with a '\' */
474 if (stem[lstrlenW(stem)-1] != '\\') {
475 lstrcatW(stem, wchr_slash);
479 WCHAR drive[MAX_PATH];
481 WCHAR fname[MAX_PATH];
483 _wsplitpath(actualdestination, drive, dir, fname, ext);
484 lstrcpyW(stem, drive);
486 lstrcpyW(spec, fname);
492 /* =========================================================================
493 XCOPY_DoCopy - Recursive function to copy files based on input parms
496 This works by using FindFirstFile supplying the source stem and spec.
497 If results are found, any non-directory ones are processed
498 Then, if /S or /E is supplied, another search is made just for
499 directories, and this function is called again for that directory
501 ========================================================================= */
502 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
503 WCHAR *deststem, WCHAR *destspec,
506 WIN32_FIND_DATAW *finddata;
509 WCHAR *inputpath, *outputpath;
510 BOOL copiedFile = FALSE;
511 DWORD destAttribs, srcAttribs;
515 /* Allocate some working memory on heap to minimize footprint */
516 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
517 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
518 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
520 /* Build the search info into a single parm */
521 lstrcpyW(inputpath, srcstem);
522 lstrcatW(inputpath, srcspec);
524 /* Search 1 - Look for matching files */
525 h = FindFirstFileW(inputpath, finddata);
526 while (h != INVALID_HANDLE_VALUE && findres) {
530 /* Ignore . and .. */
531 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
532 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
533 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
535 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
538 /* Get the filename information */
539 lstrcpyW(copyFrom, srcstem);
540 if (flags & OPT_SHORTNAME) {
541 lstrcatW(copyFrom, finddata->cAlternateFileName);
543 lstrcatW(copyFrom, finddata->cFileName);
546 lstrcpyW(copyTo, deststem);
547 if (*destspec == 0x00) {
548 if (flags & OPT_SHORTNAME) {
549 lstrcatW(copyTo, finddata->cAlternateFileName);
551 lstrcatW(copyTo, finddata->cFileName);
554 lstrcatW(copyTo, destspec);
558 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
559 wine_dbgstr_w(copyTo));
560 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
562 /* See if allowed to copy it */
563 srcAttribs = GetFileAttributesW(copyFrom);
564 WINE_TRACE("Source attribs: %d\n", srcAttribs);
566 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
567 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
569 if (!(flags & OPT_COPYHIDSYS)) {
574 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
575 (flags & OPT_ARCHIVEONLY)) {
579 /* See if file exists */
580 destAttribs = GetFileAttributesW(copyTo);
581 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
583 /* Check date ranges if a destination file already exists */
584 if (!skipFile && (flags & OPT_DATERANGE) &&
585 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
586 WINE_TRACE("Skipping file as modified date too old\n");
590 /* If just /D supplied, only overwrite if src newer than dest */
591 if (!skipFile && (flags & OPT_DATENEWER) &&
592 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
593 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
594 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
596 if (h != INVALID_HANDLE_VALUE) {
598 GetFileTime(h, NULL, NULL, &writeTime);
600 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
601 WINE_TRACE("Skipping file as dest newer or same date\n");
608 /* See if exclude list provided. Note since filenames are case
609 insensitive, need to uppercase the filename before doing
611 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
612 EXCLUDELIST *pos = excludeList;
613 WCHAR copyFromUpper[MAX_PATH];
615 /* Uppercase source filename */
616 lstrcpyW(copyFromUpper, copyFrom);
617 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
619 /* Loop through testing each exclude line */
621 if (wcsstr(copyFromUpper, pos->name) != NULL) {
622 WINE_TRACE("Skipping file as matches exclude '%s'\n",
623 wine_dbgstr_w(pos->name));
632 /* Prompt each file if necessary */
633 if (!skipFile && (flags & OPT_SRCPROMPT)) {
636 BOOL answered = FALSE;
640 /* Read the Y and N characters from the resource file */
641 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
642 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
645 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
646 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
650 if (toupper(answer[0]) == noChar[0])
652 else if (toupper(answer[0]) != yesChar[0])
658 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
661 BOOL answered = FALSE;
666 /* Read the A,Y and N characters from the resource file */
667 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
668 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
669 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
672 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
673 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
677 if (toupper(answer[0]) == allChar[0])
678 flags |= OPT_NOPROMPT;
679 else if (toupper(answer[0]) == noChar[0])
681 else if (toupper(answer[0]) != yesChar[0])
686 /* See if it has to exist! */
687 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
691 /* Output a status message */
693 if (flags & OPT_QUIET) {
695 } else if (flags & OPT_FULL) {
696 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
699 XCOPY_wprintf(infostr, copyFrom, copyTo);
701 const WCHAR infostr[] = {'%', 's', '\n', 0};
702 XCOPY_wprintf(infostr, copyFrom);
705 /* If allowing overwriting of read only files, remove any
707 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
708 (flags & OPT_REPLACEREAD)) {
709 SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
713 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
715 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
717 DWORD error = GetLastError();
718 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
719 copyFrom, copyTo, error);
720 XCOPY_FailMessage(error);
722 if (flags & OPT_IGNOREERRORS) {
730 /* If /M supplied, remove the archive bit after successful copy */
732 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
733 (flags & OPT_REMOVEARCH)) {
734 SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
742 findres = FindNextFileW(h, finddata);
746 /* Search 2 - do subdirs */
747 if (flags & OPT_RECURSIVE) {
748 lstrcpyW(inputpath, srcstem);
749 lstrcatW(inputpath, wchr_star);
751 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
753 h = FindFirstFileW(inputpath, finddata);
754 while (h != INVALID_HANDLE_VALUE && findres) {
756 /* Only looking for dirs */
757 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
758 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
759 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
761 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
763 /* Make up recursive information */
764 lstrcpyW(inputpath, srcstem);
765 lstrcatW(inputpath, finddata->cFileName);
766 lstrcatW(inputpath, wchr_slash);
768 lstrcpyW(outputpath, deststem);
769 if (*destspec == 0x00) {
770 lstrcatW(outputpath, finddata->cFileName);
772 /* If /E is supplied, create the directory now */
773 if ((flags & OPT_EMPTYDIR) &&
774 !(flags & OPT_SIMULATE))
775 XCOPY_CreateDirectory(outputpath);
777 lstrcatW(outputpath, wchr_slash);
780 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
784 findres = FindNextFileW(h, finddata);
791 HeapFree(GetProcessHeap(), 0, finddata);
792 HeapFree(GetProcessHeap(), 0, inputpath);
793 HeapFree(GetProcessHeap(), 0, outputpath);
798 /* =========================================================================
799 * Routine copied from cmd.exe md command -
800 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
801 * dir2 if they do not already exist.
802 * ========================================================================= */
803 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
809 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
810 lstrcpyW(new_path,path);
812 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
813 new_path[len - 1] = 0;
815 while (!CreateDirectoryW(new_path,NULL))
818 DWORD last_error = GetLastError();
819 if (last_error == ERROR_ALREADY_EXISTS)
822 if (last_error != ERROR_PATH_NOT_FOUND)
828 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
834 len = slash - new_path;
836 if (!XCOPY_CreateDirectory(new_path))
841 new_path[len] = '\\';
843 HeapFree(GetProcessHeap(),0,new_path);
847 /* =========================================================================
848 * Process the /EXCLUDE: file list, building up a list of substrings to
850 * Returns TRUE on any failure
851 * ========================================================================= */
852 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
854 WCHAR *filenameStart = parms;
856 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
859 while (*parms && *parms != ' ' && *parms != '/') {
861 /* If found '+' then process the file found so far */
863 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
866 filenameStart = parms+1;
871 if (filenameStart != parms) {
872 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
880 /* =========================================================================
881 * Process a single file from the /EXCLUDE: file list, building up a list
882 * of substrings to avoid copying
883 * Returns TRUE on any failure
884 * ========================================================================= */
885 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
887 WCHAR endChar = *endOfName;
888 WCHAR buffer[MAXSTRING];
890 const WCHAR readTextMode[] = {'r', 't', 0};
892 /* Null terminate the filename (temporarily updates the filename hence
897 inFile = _wfopen(filename, readTextMode);
898 if (inFile == NULL) {
899 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
900 *endOfName = endChar;
904 /* Process line by line */
905 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
906 EXCLUDELIST *thisEntry;
907 int length = lstrlenW(buffer);
910 buffer[length-1] = 0x00;
912 /* If more than CRLF */
914 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
915 thisEntry->next = excludeList;
916 excludeList = thisEntry;
917 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
918 (length * sizeof(WCHAR))+1);
919 lstrcpyW(thisEntry->name, buffer);
920 CharUpperBuffW(thisEntry->name, length);
921 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
925 /* See if EOF or error occurred */
927 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
928 *endOfName = endChar;
932 /* Revert the input string to original form, and cleanup + return */
933 *endOfName = endChar;
938 /* =========================================================================
939 * Load a string from the resource file, handling any error
940 * Returns string retrieved from resource file
941 * ========================================================================= */
942 static WCHAR *XCOPY_LoadMessage(UINT id) {
943 static WCHAR msg[MAXSTRING];
944 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
946 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
947 WINE_FIXME("LoadString failed with %d\n", GetLastError());
948 lstrcpyW(msg, failedMsg);
953 /* =========================================================================
954 * Load a string for a system error and writes it to the screen
955 * Returns string retrieved from resource file
956 * ========================================================================= */
957 static void XCOPY_FailMessage(DWORD err) {
961 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
962 FORMAT_MESSAGE_FROM_SYSTEM,
964 (LPWSTR) &lpMsgBuf, 0, NULL);
966 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
967 err, GetLastError());
969 const WCHAR infostr[] = {'%', 's', '\n', 0};
970 XCOPY_wprintf(infostr, lpMsgBuf);
971 LocalFree ((HLOCAL)lpMsgBuf);
975 /* =========================================================================
976 * Output a formatted unicode string. Ideally this will go to the console
977 * and hence required WriteConsoleW to output it, however if file i/o is
978 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
979 * ========================================================================= */
980 int XCOPY_wprintf(const WCHAR *format, ...) {
982 static WCHAR *output_bufW = NULL;
983 static char *output_bufA = NULL;
984 static BOOL toConsole = TRUE;
985 static BOOL traceOutput = FALSE;
986 #define MAX_WRITECONSOLE_SIZE 65535
994 * Allocate buffer to use when writing to console
995 * Note: Not freed - memory will be allocated once and released when
999 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
1000 MAX_WRITECONSOLE_SIZE);
1002 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1006 va_start(parms, format);
1007 len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
1010 WINE_FIXME("String too long.\n");
1014 /* Try to write as unicode all the time we think its a console */
1016 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1017 output_bufW, len, &nOut, NULL);
1020 /* If writing to console has failed (ever) we assume its file
1021 i/o so convert to OEM codepage and output */
1023 BOOL usedDefaultChar = FALSE;
1024 DWORD convertedChars;
1029 * Allocate buffer to use when writing to file. Not freed, as above
1031 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1032 MAX_WRITECONSOLE_SIZE);
1034 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1038 /* Convert to OEM, then output */
1039 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1040 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1041 "?", &usedDefaultChar);
1042 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1046 /* Trace whether screen or console */
1048 WINE_TRACE("Writing to console? (%d)\n", toConsole);