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);
65 typedef struct _EXCLUDELIST
67 struct _EXCLUDELIST *next;
72 /* Global variables */
73 static ULONG filesCopied = 0; /* Number of files copied */
74 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
75 static FILETIME dateRange; /* Date range to copy after*/
76 static const WCHAR wchr_slash[] = {'\\', 0};
77 static const WCHAR wchr_star[] = {'*', 0};
78 static const WCHAR wchr_dot[] = {'.', 0};
79 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
81 /* Constants (Mostly for widechars) */
84 /* To minimize stack usage during recursion, some temporary variables
86 static WCHAR copyFrom[MAX_PATH];
87 static WCHAR copyTo[MAX_PATH];
90 /* =========================================================================
91 main - Main entrypoint for the xcopy command
93 Processes the args, and drives the actual copying
94 ========================================================================= */
95 int main (int argc, char *argv[])
98 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
99 WCHAR supplieddestination[MAX_PATH] = {0};
100 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
101 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
102 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
103 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
104 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
105 DWORD flags = 0; /* Option flags */
106 LPWSTR *argvW = NULL;
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 /* overwrite the command line */
117 argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
119 /* Confirm at least one parameter */
121 wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
125 /* Preinitialize flags based on COPYCMD */
126 if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
127 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
128 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
129 flags |= OPT_NOPROMPT;
133 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
134 wine, but on windows these can be normal files. At least one installer
135 uses files such as .packlist and (validly) expects them to be copied.
136 Under wine, if we do not copy hidden files by default then they get
138 flags |= OPT_COPYHIDSYS;
140 /* Skip first arg, which is the program name */
146 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
148 /* First non-switch parameter is source, second is destination */
149 if (*argvW[0] != '/') {
150 if (suppliedsource[0] == 0x00) {
151 lstrcpyW(suppliedsource, *argvW);
152 } else if (supplieddestination[0] == 0x00) {
153 lstrcpyW(supplieddestination, *argvW);
155 wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
159 /* Process all the switch options
160 Note: Windows docs say /P prompts when dest is created
161 but tests show it is done for each src file
162 regardless of the destination */
163 switch (toupper(argvW[0][1])) {
164 case 'I': flags |= OPT_ASSUMEDIR; break;
165 case 'S': flags |= OPT_RECURSIVE; break;
166 case 'Q': flags |= OPT_QUIET; break;
167 case 'F': flags |= OPT_FULL; break;
168 case 'L': flags |= OPT_SIMULATE; break;
169 case 'W': flags |= OPT_PAUSE; break;
170 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
171 case 'Y': flags |= OPT_NOPROMPT; break;
172 case 'N': flags |= OPT_SHORTNAME; break;
173 case 'U': flags |= OPT_MUSTEXIST; break;
174 case 'R': flags |= OPT_REPLACEREAD; break;
175 case 'H': flags |= OPT_COPYHIDSYS; break;
176 case 'C': flags |= OPT_IGNOREERRORS; break;
177 case 'P': flags |= OPT_SRCPROMPT; break;
178 case 'A': flags |= OPT_ARCHIVEONLY; break;
179 case 'M': flags |= OPT_ARCHIVEONLY |
180 OPT_REMOVEARCH; break;
182 /* E can be /E or /EXCLUDE */
183 case 'E': if (CompareString (LOCALE_USER_DEFAULT,
184 NORM_IGNORECASE | SORT_STRINGSORT,
187 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
188 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
190 } else flags |= OPT_EXCLUDELIST;
191 } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
194 /* D can be /D or /D: */
195 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
197 WCHAR *pos = &argvW[0][3];
198 BOOL isError = FALSE;
199 memset(&st, 0x00, sizeof(st));
201 /* Parse the arg : Month */
202 st.wMonth = _wtol(pos);
203 while (*pos && isdigit(*pos)) pos++;
204 if (*pos++ != '-') isError = TRUE;
206 /* Parse the arg : Day */
208 st.wDay = _wtol(pos);
209 while (*pos && isdigit(*pos)) pos++;
210 if (*pos++ != '-') isError = TRUE;
213 /* Parse the arg : Day */
215 st.wYear = _wtol(pos);
216 if (st.wYear < 100) st.wYear+=2000;
219 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
221 WCHAR datestring[32], timestring[32];
223 flags |= OPT_DATERANGE;
226 FileTimeToSystemTime (&dateRange, &st);
227 GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
229 GetTimeFormat (0, TIME_NOSECONDS, &st,
230 NULL, timestring, sizeof(timestring));
232 WINE_TRACE("Date being used is: %s %s\n",
233 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
235 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
239 flags |= OPT_DATENEWER;
243 case '-': if (toupper(argvW[0][2])=='Y')
244 flags &= ~OPT_NOPROMPT; break;
245 case '?': wprintf(XCOPY_LoadMessage(STRING_HELP));
248 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
249 wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
256 /* Default the destination if not supplied */
257 if (supplieddestination[0] == 0x00)
258 lstrcpyW(supplieddestination, wchr_dot);
260 /* Trace out the supplied information */
261 WINE_TRACE("Supplied parameters:\n");
262 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
263 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
265 /* Extract required information from source specification */
266 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
268 /* Extract required information from destination specification */
269 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
270 destinationspec, sourcespec, flags);
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 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 wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
305 } else if (!(flags & OPT_NOCOPY)) {
306 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 (GetFullPathName(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 = GetFileAttributes(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 GetCurrentDirectory (sizeof(curdir), 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 (GetFullPathName(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 = GetFileAttributes(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 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_DATA *finddata;
509 WCHAR *inputpath, *outputpath;
510 BOOL copiedFile = FALSE;
511 DWORD destAttribs, srcAttribs;
514 /* Allocate some working memory on heap to minimize footprint */
515 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
516 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
517 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
519 /* Build the search info into a single parm */
520 lstrcpyW(inputpath, srcstem);
521 lstrcatW(inputpath, srcspec);
523 /* Search 1 - Look for matching files */
524 h = FindFirstFile(inputpath, finddata);
525 while (h != INVALID_HANDLE_VALUE && findres) {
529 /* Ignore . and .. */
530 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
531 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
532 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
534 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
537 /* Get the filename information */
538 lstrcpyW(copyFrom, srcstem);
539 if (flags & OPT_SHORTNAME) {
540 lstrcatW(copyFrom, finddata->cAlternateFileName);
542 lstrcatW(copyFrom, finddata->cFileName);
545 lstrcpyW(copyTo, deststem);
546 if (*destspec == 0x00) {
547 if (flags & OPT_SHORTNAME) {
548 lstrcatW(copyTo, finddata->cAlternateFileName);
550 lstrcatW(copyTo, finddata->cFileName);
553 lstrcatW(copyTo, destspec);
557 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
558 wine_dbgstr_w(copyTo));
559 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
561 /* See if allowed to copy it */
562 srcAttribs = GetFileAttributesW(copyFrom);
563 WINE_TRACE("Source attribs: %d\n", srcAttribs);
565 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
566 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
568 if (!(flags & OPT_COPYHIDSYS)) {
573 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
574 (flags & OPT_ARCHIVEONLY)) {
578 /* See if file exists */
579 destAttribs = GetFileAttributesW(copyTo);
580 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
582 /* Check date ranges if a destination file already exists */
583 if (!skipFile && (flags & OPT_DATERANGE) &&
584 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
585 WINE_TRACE("Skipping file as modified date too old\n");
589 /* If just /D supplied, only overwrite if src newer than dest */
590 if (!skipFile && (flags & OPT_DATENEWER) &&
591 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
592 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
593 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
595 if (h != INVALID_HANDLE_VALUE) {
597 GetFileTime(h, NULL, NULL, &writeTime);
599 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
600 WINE_TRACE("Skipping file as dest newer or same date\n");
607 /* See if exclude list provided. Note since filenames are case
608 insensitive, need to uppercase the filename before doing
610 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
611 EXCLUDELIST *pos = excludeList;
612 WCHAR copyFromUpper[MAX_PATH];
614 /* Uppercase source filename */
615 lstrcpyW(copyFromUpper, copyFrom);
616 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
618 /* Loop through testing each exclude line */
620 if (wcsstr(copyFromUpper, pos->name) != NULL) {
621 WINE_TRACE("Skipping file as matches exclude '%s'\n",
622 wine_dbgstr_w(pos->name));
631 /* Prompt each file if necessary */
632 if (!skipFile && (flags & OPT_SRCPROMPT)) {
635 BOOL answered = FALSE;
639 /* Read the Y and N characters from the resource file */
640 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
641 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
644 wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
645 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
649 if (toupper(answer[0]) == noChar[0])
651 else if (toupper(answer[0]) != yesChar[0])
657 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
660 BOOL answered = FALSE;
665 /* Read the A,Y and N characters from the resource file */
666 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
667 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
668 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
671 wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
672 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
676 if (toupper(answer[0]) == allChar[0])
677 flags |= OPT_NOPROMPT;
678 else if (toupper(answer[0]) == noChar[0])
680 else if (toupper(answer[0]) != yesChar[0])
685 /* See if it has to exist! */
686 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
690 /* Output a status message */
692 if (flags & OPT_QUIET) {
694 } else if (flags & OPT_FULL) {
695 printf("%S -> %S\n", copyFrom, copyTo);
697 printf("%S\n", copyFrom);
700 /* If allowing overwriting of read only files, remove any
702 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
703 (flags & OPT_REPLACEREAD)) {
704 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
708 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
710 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
712 DWORD error = GetLastError();
713 wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
714 copyFrom, copyTo, error);
715 XCOPY_FailMessage(error);
717 if (flags & OPT_IGNOREERRORS) {
720 return RC_WRITEERROR;
724 /* If /M supplied, remove the archive bit after successful copy */
726 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
727 (flags & OPT_REMOVEARCH)) {
728 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
736 findres = FindNextFile(h, finddata);
740 /* Search 2 - do subdirs */
741 if (flags & OPT_RECURSIVE) {
742 lstrcpyW(inputpath, srcstem);
743 lstrcatW(inputpath, wchr_star);
745 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
747 h = FindFirstFile(inputpath, finddata);
748 while (h != INVALID_HANDLE_VALUE && findres) {
750 /* Only looking for dirs */
751 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
752 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
753 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
755 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
757 /* Make up recursive information */
758 lstrcpyW(inputpath, srcstem);
759 lstrcatW(inputpath, finddata->cFileName);
760 lstrcatW(inputpath, wchr_slash);
762 lstrcpyW(outputpath, deststem);
763 if (*destspec == 0x00) {
764 lstrcatW(outputpath, finddata->cFileName);
766 /* If /E is supplied, create the directory now */
767 if ((flags & OPT_EMPTYDIR) &&
768 !(flags & OPT_SIMULATE))
769 XCOPY_CreateDirectory(outputpath);
771 lstrcatW(outputpath, wchr_slash);
774 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
778 findres = FindNextFile(h, finddata);
783 HeapFree(GetProcessHeap(), 0, finddata);
784 HeapFree(GetProcessHeap(), 0, inputpath);
785 HeapFree(GetProcessHeap(), 0, outputpath);
790 /* =========================================================================
791 * Routine copied from cmd.exe md command -
792 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
793 * dir2 if they do not already exist.
794 * ========================================================================= */
795 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
801 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
802 lstrcpyW(new_path,path);
804 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
805 new_path[len - 1] = 0;
807 while (!CreateDirectory(new_path,NULL))
810 DWORD last_error = GetLastError();
811 if (last_error == ERROR_ALREADY_EXISTS)
814 if (last_error != ERROR_PATH_NOT_FOUND)
820 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
826 len = slash - new_path;
828 if (!XCOPY_CreateDirectory(new_path))
833 new_path[len] = '\\';
835 HeapFree(GetProcessHeap(),0,new_path);
839 /* =========================================================================
840 * Process the /EXCLUDE: file list, building up a list of substrings to
842 * Returns TRUE on any failure
843 * ========================================================================= */
844 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
846 WCHAR *filenameStart = parms;
848 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
851 while (*parms && *parms != ' ' && *parms != '/') {
853 /* If found '+' then process the file found so far */
855 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
858 filenameStart = parms+1;
863 if (filenameStart != parms) {
864 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
872 /* =========================================================================
873 * Process a single file from the /EXCLUDE: file list, building up a list
874 * of substrings to avoid copying
875 * Returns TRUE on any failure
876 * ========================================================================= */
877 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
879 WCHAR endChar = *endOfName;
880 WCHAR buffer[MAXSTRING];
882 const WCHAR readTextMode[] = {'r', 't', 0};
884 /* Null terminate the filename (temporarily updates the filename hence
889 inFile = _wfopen(filename, readTextMode);
890 if (inFile == NULL) {
891 wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
892 *endOfName = endChar;
896 /* Process line by line */
897 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
898 EXCLUDELIST *thisEntry;
899 int length = lstrlenW(buffer);
902 buffer[length-1] = 0x00;
904 /* If more than CRLF */
906 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
907 thisEntry->next = excludeList;
908 excludeList = thisEntry;
909 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
910 (length * sizeof(WCHAR))+1);
911 lstrcpyW(thisEntry->name, buffer);
912 CharUpperBuff(thisEntry->name, length);
913 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
917 /* See if EOF or error occurred */
919 wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
920 *endOfName = endChar;
924 /* Revert the input string to original form, and cleanup + return */
925 *endOfName = endChar;
930 /* =========================================================================
931 * Load a string from the resource file, handling any error
932 * Returns string retrieved from resource file
933 * ========================================================================= */
934 static WCHAR *XCOPY_LoadMessage(UINT id) {
935 static WCHAR msg[2048];
936 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
938 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
939 WINE_FIXME("LoadString failed with %d\n", GetLastError());
940 lstrcpyW(msg, failedMsg);
945 /* =========================================================================
946 * Load a string for a system error and writes it to the screen
947 * Returns string retrieved from resource file
948 * ========================================================================= */
949 static void XCOPY_FailMessage(DWORD err) {
953 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
954 FORMAT_MESSAGE_FROM_SYSTEM,
956 (LPTSTR) &lpMsgBuf, 0, NULL);
958 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
959 err, GetLastError());
961 printf("%S\n", lpMsgBuf);
962 LocalFree ((HLOCAL)lpMsgBuf);