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 XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
266 /* Extract required information from destination specification */
267 XCOPY_ProcessDestParm(supplieddestination, destinationstem,
268 destinationspec, sourcespec, flags);
270 /* Trace out the resulting information */
271 WINE_TRACE("Resolved parameters:\n");
272 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
273 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
274 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
275 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
277 /* Pause if necessary */
278 if (flags & OPT_PAUSE) {
282 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
283 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
287 /* Now do the hard work... */
288 rc = XCOPY_DoCopy(sourcestem, sourcespec,
289 destinationstem, destinationspec,
292 /* Clear up exclude list allocated memory */
293 while (excludeList) {
294 EXCLUDELIST *pos = excludeList;
295 excludeList = excludeList -> next;
296 HeapFree(GetProcessHeap(), 0, pos->name);
297 HeapFree(GetProcessHeap(), 0, pos);
300 /* Finished - print trailer and exit */
301 if (flags & OPT_SIMULATE) {
302 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
303 } else if (!(flags & OPT_NOCOPY)) {
304 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
306 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
312 /* =========================================================================
313 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
314 converts it into a stem and a filespec
315 ========================================================================= */
316 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
317 WCHAR *spec, DWORD flags)
319 WCHAR actualsource[MAX_PATH];
325 * Validate the source, expanding to full path ensuring it exists
327 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
328 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
332 /* If full names required, convert to using the full path */
333 if (flags & OPT_FULL) {
334 lstrcpyW(suppliedsource, actualsource);
338 * Work out the stem of the source
341 /* If a directory is supplied, use that as-is (either fully or
343 If a filename is supplied + a directory or drive path, use that
346 If no directory or path specified, add eg. C:
347 stem is Drive/Directory is bit up to last \ (or first :)
348 spec is bit after that */
350 starPos = wcschr(suppliedsource, '*');
351 questPos = wcschr(suppliedsource, '?');
352 if (starPos || questPos) {
353 attribs = 0x00; /* Ensures skips invalid or directory check below */
355 attribs = GetFileAttributesW(actualsource);
358 if (attribs == INVALID_FILE_ATTRIBUTES) {
359 XCOPY_FailMessage(GetLastError());
363 stem should be exactly as supplied plus a '\', unless it was
364 eg. C: in which case no slash required */
365 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
368 WINE_TRACE("Directory supplied\n");
369 lstrcpyW(stem, suppliedsource);
370 lastChar = stem[lstrlenW(stem)-1];
371 if (lastChar != '\\' && lastChar != ':') {
372 lstrcatW(stem, wchr_slash);
374 lstrcpyW(spec, wchr_star);
376 /* File or wildcard search:
378 Up to and including last slash if directory path supplied
379 If c:filename supplied, just the c:
380 Otherwise stem should be the current drive letter + ':' */
384 WINE_TRACE("Filename supplied\n");
385 lastDir = wcsrchr(suppliedsource, '\\');
388 lstrcpyW(stem, suppliedsource);
389 stem[(lastDir-suppliedsource) + 1] = 0x00;
390 lstrcpyW(spec, (lastDir+1));
391 } else if (suppliedsource[1] == ':') {
392 lstrcpyW(stem, suppliedsource);
394 lstrcpyW(spec, suppliedsource+2);
396 WCHAR curdir[MAXSTRING];
397 GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
401 lstrcpyW(spec, suppliedsource);
408 /* =========================================================================
409 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
410 converts it into a stem
411 ========================================================================= */
412 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
413 WCHAR *srcspec, DWORD flags)
415 WCHAR actualdestination[MAX_PATH];
420 * Validate the source, expanding to full path ensuring it exists
422 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
423 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
427 /* Destination is either a directory or a file */
428 attribs = GetFileAttributesW(actualdestination);
430 if (attribs == INVALID_FILE_ATTRIBUTES) {
432 /* If /I supplied and wildcard copy, assume directory */
433 if (flags & OPT_ASSUMEDIR &&
434 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
440 char answer[10] = "";
444 /* Read the F and D characters from the resource file */
445 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
446 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
448 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
449 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
451 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
452 WINE_TRACE("User answer %c\n", answer[0]);
454 answer[0] = toupper(answer[0]);
457 if (answer[0] == dirChar[0]) {
464 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
468 lstrcpyW(stem, actualdestination);
471 /* Ensure ends with a '\' */
472 if (stem[lstrlenW(stem)-1] != '\\') {
473 lstrcatW(stem, wchr_slash);
477 WCHAR drive[MAX_PATH];
479 WCHAR fname[MAX_PATH];
481 _wsplitpath(actualdestination, drive, dir, fname, ext);
482 lstrcpyW(stem, drive);
484 lstrcpyW(spec, fname);
490 /* =========================================================================
491 XCOPY_DoCopy - Recursive function to copy files based on input parms
494 This works by using FindFirstFile supplying the source stem and spec.
495 If results are found, any non-directory ones are processed
496 Then, if /S or /E is supplied, another search is made just for
497 directories, and this function is called again for that directory
499 ========================================================================= */
500 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
501 WCHAR *deststem, WCHAR *destspec,
504 WIN32_FIND_DATAW *finddata;
507 WCHAR *inputpath, *outputpath;
508 BOOL copiedFile = FALSE;
509 DWORD destAttribs, srcAttribs;
513 /* Allocate some working memory on heap to minimize footprint */
514 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
515 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
516 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
518 /* Build the search info into a single parm */
519 lstrcpyW(inputpath, srcstem);
520 lstrcatW(inputpath, srcspec);
522 /* Search 1 - Look for matching files */
523 h = FindFirstFileW(inputpath, finddata);
524 while (h != INVALID_HANDLE_VALUE && findres) {
528 /* Ignore . and .. */
529 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
530 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
531 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
533 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
536 /* Get the filename information */
537 lstrcpyW(copyFrom, srcstem);
538 if (flags & OPT_SHORTNAME) {
539 lstrcatW(copyFrom, finddata->cAlternateFileName);
541 lstrcatW(copyFrom, finddata->cFileName);
544 lstrcpyW(copyTo, deststem);
545 if (*destspec == 0x00) {
546 if (flags & OPT_SHORTNAME) {
547 lstrcatW(copyTo, finddata->cAlternateFileName);
549 lstrcatW(copyTo, finddata->cFileName);
552 lstrcatW(copyTo, destspec);
556 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
557 wine_dbgstr_w(copyTo));
558 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
560 /* See if allowed to copy it */
561 srcAttribs = GetFileAttributesW(copyFrom);
562 WINE_TRACE("Source attribs: %d\n", srcAttribs);
564 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
565 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
567 if (!(flags & OPT_COPYHIDSYS)) {
572 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
573 (flags & OPT_ARCHIVEONLY)) {
577 /* See if file exists */
578 destAttribs = GetFileAttributesW(copyTo);
579 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
581 /* Check date ranges if a destination file already exists */
582 if (!skipFile && (flags & OPT_DATERANGE) &&
583 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
584 WINE_TRACE("Skipping file as modified date too old\n");
588 /* If just /D supplied, only overwrite if src newer than dest */
589 if (!skipFile && (flags & OPT_DATENEWER) &&
590 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
591 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
592 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
594 if (h != INVALID_HANDLE_VALUE) {
596 GetFileTime(h, NULL, NULL, &writeTime);
598 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
599 WINE_TRACE("Skipping file as dest newer or same date\n");
606 /* See if exclude list provided. Note since filenames are case
607 insensitive, need to uppercase the filename before doing
609 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
610 EXCLUDELIST *pos = excludeList;
611 WCHAR copyFromUpper[MAX_PATH];
613 /* Uppercase source filename */
614 lstrcpyW(copyFromUpper, copyFrom);
615 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
617 /* Loop through testing each exclude line */
619 if (wcsstr(copyFromUpper, pos->name) != NULL) {
620 WINE_TRACE("Skipping file as matches exclude '%s'\n",
621 wine_dbgstr_w(pos->name));
630 /* Prompt each file if necessary */
631 if (!skipFile && (flags & OPT_SRCPROMPT)) {
634 BOOL answered = FALSE;
638 /* Read the Y and N characters from the resource file */
639 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
640 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
643 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
644 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
648 if (toupper(answer[0]) == noChar[0])
650 else if (toupper(answer[0]) != yesChar[0])
656 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
659 BOOL answered = FALSE;
664 /* Read the A,Y and N characters from the resource file */
665 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
666 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
667 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
670 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
671 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
675 if (toupper(answer[0]) == allChar[0])
676 flags |= OPT_NOPROMPT;
677 else if (toupper(answer[0]) == noChar[0])
679 else if (toupper(answer[0]) != yesChar[0])
684 /* See if it has to exist! */
685 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
689 /* Output a status message */
691 if (flags & OPT_QUIET) {
693 } else if (flags & OPT_FULL) {
694 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
697 XCOPY_wprintf(infostr, copyFrom, copyTo);
699 const WCHAR infostr[] = {'%', 's', '\n', 0};
700 XCOPY_wprintf(infostr, copyFrom);
703 /* If allowing overwriting of read only files, remove any
705 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
706 (flags & OPT_REPLACEREAD)) {
707 SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
711 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
713 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
715 DWORD error = GetLastError();
716 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
717 copyFrom, copyTo, error);
718 XCOPY_FailMessage(error);
720 if (flags & OPT_IGNOREERRORS) {
728 /* If /M supplied, remove the archive bit after successful copy */
730 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
731 (flags & OPT_REMOVEARCH)) {
732 SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
740 findres = FindNextFileW(h, finddata);
744 /* Search 2 - do subdirs */
745 if (flags & OPT_RECURSIVE) {
746 lstrcpyW(inputpath, srcstem);
747 lstrcatW(inputpath, wchr_star);
749 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
751 h = FindFirstFileW(inputpath, finddata);
752 while (h != INVALID_HANDLE_VALUE && findres) {
754 /* Only looking for dirs */
755 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
756 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
757 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
759 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
761 /* Make up recursive information */
762 lstrcpyW(inputpath, srcstem);
763 lstrcatW(inputpath, finddata->cFileName);
764 lstrcatW(inputpath, wchr_slash);
766 lstrcpyW(outputpath, deststem);
767 if (*destspec == 0x00) {
768 lstrcatW(outputpath, finddata->cFileName);
770 /* If /E is supplied, create the directory now */
771 if ((flags & OPT_EMPTYDIR) &&
772 !(flags & OPT_SIMULATE))
773 XCOPY_CreateDirectory(outputpath);
775 lstrcatW(outputpath, wchr_slash);
778 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
782 findres = FindNextFileW(h, finddata);
789 HeapFree(GetProcessHeap(), 0, finddata);
790 HeapFree(GetProcessHeap(), 0, inputpath);
791 HeapFree(GetProcessHeap(), 0, outputpath);
796 /* =========================================================================
797 * Routine copied from cmd.exe md command -
798 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
799 * dir2 if they do not already exist.
800 * ========================================================================= */
801 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
807 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
808 lstrcpyW(new_path,path);
810 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
811 new_path[len - 1] = 0;
813 while (!CreateDirectoryW(new_path,NULL))
816 DWORD last_error = GetLastError();
817 if (last_error == ERROR_ALREADY_EXISTS)
820 if (last_error != ERROR_PATH_NOT_FOUND)
826 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
832 len = slash - new_path;
834 if (!XCOPY_CreateDirectory(new_path))
839 new_path[len] = '\\';
841 HeapFree(GetProcessHeap(),0,new_path);
845 /* =========================================================================
846 * Process the /EXCLUDE: file list, building up a list of substrings to
848 * Returns TRUE on any failure
849 * ========================================================================= */
850 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
852 WCHAR *filenameStart = parms;
854 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
857 while (*parms && *parms != ' ' && *parms != '/') {
859 /* If found '+' then process the file found so far */
861 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
864 filenameStart = parms+1;
869 if (filenameStart != parms) {
870 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
878 /* =========================================================================
879 * Process a single file from the /EXCLUDE: file list, building up a list
880 * of substrings to avoid copying
881 * Returns TRUE on any failure
882 * ========================================================================= */
883 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
885 WCHAR endChar = *endOfName;
886 WCHAR buffer[MAXSTRING];
888 const WCHAR readTextMode[] = {'r', 't', 0};
890 /* Null terminate the filename (temporarily updates the filename hence
895 inFile = _wfopen(filename, readTextMode);
896 if (inFile == NULL) {
897 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
898 *endOfName = endChar;
902 /* Process line by line */
903 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
904 EXCLUDELIST *thisEntry;
905 int length = lstrlenW(buffer);
908 buffer[length-1] = 0x00;
910 /* If more than CRLF */
912 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
913 thisEntry->next = excludeList;
914 excludeList = thisEntry;
915 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
916 (length * sizeof(WCHAR))+1);
917 lstrcpyW(thisEntry->name, buffer);
918 CharUpperBuffW(thisEntry->name, length);
919 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
923 /* See if EOF or error occurred */
925 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
926 *endOfName = endChar;
930 /* Revert the input string to original form, and cleanup + return */
931 *endOfName = endChar;
936 /* =========================================================================
937 * Load a string from the resource file, handling any error
938 * Returns string retrieved from resource file
939 * ========================================================================= */
940 static WCHAR *XCOPY_LoadMessage(UINT id) {
941 static WCHAR msg[MAXSTRING];
942 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
944 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
945 WINE_FIXME("LoadString failed with %d\n", GetLastError());
946 lstrcpyW(msg, failedMsg);
951 /* =========================================================================
952 * Load a string for a system error and writes it to the screen
953 * Returns string retrieved from resource file
954 * ========================================================================= */
955 static void XCOPY_FailMessage(DWORD err) {
959 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
960 FORMAT_MESSAGE_FROM_SYSTEM,
962 (LPWSTR) &lpMsgBuf, 0, NULL);
964 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
965 err, GetLastError());
967 const WCHAR infostr[] = {'%', 's', '\n', 0};
968 XCOPY_wprintf(infostr, lpMsgBuf);
969 LocalFree ((HLOCAL)lpMsgBuf);
973 /* =========================================================================
974 * Output a formatted unicode string. Ideally this will go to the console
975 * and hence required WriteConsoleW to output it, however if file i/o is
976 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
977 * ========================================================================= */
978 int XCOPY_wprintf(const WCHAR *format, ...) {
980 static WCHAR *output_bufW = NULL;
981 static char *output_bufA = NULL;
982 static BOOL toConsole = TRUE;
983 static BOOL traceOutput = FALSE;
984 #define MAX_WRITECONSOLE_SIZE 65535
992 * Allocate buffer to use when writing to console
993 * Note: Not freed - memory will be allocated once and released when
997 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
998 MAX_WRITECONSOLE_SIZE);
1000 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1004 va_start(parms, format);
1005 len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
1008 WINE_FIXME("String too long.\n");
1012 /* Try to write as unicode all the time we think its a console */
1014 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1015 output_bufW, len, &nOut, NULL);
1018 /* If writing to console has failed (ever) we assume its file
1019 i/o so convert to OEM codepage and output */
1021 BOOL usedDefaultChar = FALSE;
1022 DWORD convertedChars;
1027 * Allocate buffer to use when writing to file. Not freed, as above
1029 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1030 MAX_WRITECONSOLE_SIZE);
1032 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1036 /* Convert to OEM, then output */
1037 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1038 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1039 "?", &usedDefaultChar);
1040 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1044 /* Trace whether screen or console */
1046 WINE_TRACE("Writing to console? (%d)\n", toConsole);