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 * Apparently, valid return codes are:
25 * 1 - No files found to copy
26 * 2 - CTRL+C during copy
27 * 4 - Initialization error, or invalid source specification
28 * 5 - Disk write error
34 #include <wine/debug.h>
40 #define RC_INITERROR 4
41 #define RC_WRITEERROR 5
43 #define OPT_ASSUMEDIR 0x00000001
44 #define OPT_RECURSIVE 0x00000002
45 #define OPT_EMPTYDIR 0x00000004
46 #define OPT_QUIET 0x00000008
47 #define OPT_FULL 0x00000010
48 #define OPT_SIMULATE 0x00000020
49 #define OPT_PAUSE 0x00000040
50 #define OPT_NOCOPY 0x00000080
51 #define OPT_NOPROMPT 0x00000100
52 #define OPT_SHORTNAME 0x00000200
53 #define OPT_MUSTEXIST 0x00000400
54 #define OPT_REPLACEREAD 0x00000800
55 #define OPT_COPYHIDSYS 0x00001000
56 #define OPT_IGNOREERRORS 0x00002000
57 #define OPT_SRCPROMPT 0x00004000
58 #define OPT_ARCHIVEONLY 0x00008000
59 #define OPT_REMOVEARCH 0x00010000
60 #define OPT_EXCLUDELIST 0x00020000
61 #define OPT_DATERANGE 0x00040000
62 #define OPT_DATENEWER 0x00080000
64 #define MAXSTRING 8192
66 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
69 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
70 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
71 WCHAR *spec, WCHAR *srcspec, DWORD flags);
72 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
73 WCHAR *deststem, WCHAR *destspec,
75 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
76 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
77 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
80 typedef struct _EXCLUDELIST
82 struct _EXCLUDELIST *next;
87 /* Global variables */
88 static ULONG filesCopied = 0; /* Number of files copied */
89 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
90 static FILETIME dateRange; /* Date range to copy after*/
91 static const WCHAR wchr_slash[] = {'\\', 0};
92 static const WCHAR wchr_star[] = {'*', 0};
93 static const WCHAR wchr_dot[] = {'.', 0};
94 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
96 /* Constants (Mostly for widechars) */
99 /* To minimize stack usage during recursion, some temporary variables
101 static WCHAR copyFrom[MAX_PATH];
102 static WCHAR copyTo[MAX_PATH];
105 /* =========================================================================
106 main - Main entrypoint for the xcopy command
108 Processes the args, and drives the actual copying
109 ========================================================================= */
110 int main (int argc, char *argv[])
113 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
114 WCHAR supplieddestination[MAX_PATH] = {0};
115 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
116 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
117 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
118 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
119 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
120 DWORD flags = 0; /* Option flags */
121 LPWSTR *argvW = NULL;
122 const WCHAR PROMPTSTR1[] = {'/', 'Y', 0};
123 const WCHAR PROMPTSTR2[] = {'/', 'y', 0};
124 const WCHAR COPYCMD[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
125 const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
128 * Parse the command line
131 /* overwrite the command line */
132 argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
134 /* Confirm at least one parameter */
136 printf("Invalid number of parameters - Use xcopy /? for help\n");
140 /* Preinitialize flags based on COPYCMD */
141 if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
142 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
143 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
144 flags |= OPT_NOPROMPT;
148 /* Skip first arg, which is the program name */
154 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
156 /* First non-switch parameter is source, second is destination */
157 if (*argvW[0] != '/') {
158 if (suppliedsource[0] == 0x00) {
159 lstrcpyW(suppliedsource, *argvW);
160 } else if (supplieddestination[0] == 0x00) {
161 lstrcpyW(supplieddestination, *argvW);
163 printf("Invalid number of parameters - Use xcopy /? for help\n");
167 /* Process all the switch options
168 Note: Windows docs say /P prompts when dest is created
169 but tests show it is done for each src file
170 regardless of the destination */
171 switch (toupper(argvW[0][1])) {
172 case 'I': flags |= OPT_ASSUMEDIR; break;
173 case 'S': flags |= OPT_RECURSIVE; break;
174 case 'Q': flags |= OPT_QUIET; break;
175 case 'F': flags |= OPT_FULL; break;
176 case 'L': flags |= OPT_SIMULATE; break;
177 case 'W': flags |= OPT_PAUSE; break;
178 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
179 case 'Y': flags |= OPT_NOPROMPT; break;
180 case 'N': flags |= OPT_SHORTNAME; break;
181 case 'U': flags |= OPT_MUSTEXIST; break;
182 case 'R': flags |= OPT_REPLACEREAD; break;
183 case 'H': flags |= OPT_COPYHIDSYS; break;
184 case 'C': flags |= OPT_IGNOREERRORS; break;
185 case 'P': flags |= OPT_SRCPROMPT; break;
186 case 'A': flags |= OPT_ARCHIVEONLY; break;
187 case 'M': flags |= OPT_ARCHIVEONLY |
188 OPT_REMOVEARCH; break;
190 /* E can be /E or /EXCLUDE */
191 case 'E': if (CompareString (LOCALE_USER_DEFAULT,
192 NORM_IGNORECASE | SORT_STRINGSORT,
195 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
199 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
200 FORMAT_MESSAGE_FROM_SYSTEM,
201 NULL, ERROR_INVALID_PARAMETER, 0,
202 (LPTSTR) &lpMsgBuf, 0, NULL);
204 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
205 ERROR_INVALID_PARAMETER, GetLastError());
207 printf("%S\n", lpMsgBuf);
208 LocalFree ((HLOCAL)lpMsgBuf);
211 } else flags |= OPT_EXCLUDELIST;
212 } else flags |= OPT_EMPTYDIR;
215 /* D can be /D or /D: */
216 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
218 WCHAR *pos = &argvW[0][3];
219 BOOL isError = FALSE;
220 memset(&st, 0x00, sizeof(st));
222 /* Parse the arg : Month */
223 st.wMonth = _wtol(pos);
224 while (*pos && isdigit(*pos)) pos++;
225 if (*pos++ != '-') isError = TRUE;
227 /* Parse the arg : Day */
229 st.wDay = _wtol(pos);
230 while (*pos && isdigit(*pos)) pos++;
231 if (*pos++ != '-') isError = TRUE;
234 /* Parse the arg : Day */
236 st.wYear = _wtol(pos);
237 if (st.wYear < 100) st.wYear+=2000;
240 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
242 WCHAR datestring[32], timestring[32];
244 flags |= OPT_DATERANGE;
247 FileTimeToSystemTime (&dateRange, &st);
248 GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
250 GetTimeFormat (0, TIME_NOSECONDS, &st,
251 NULL, timestring, sizeof(timestring));
253 WINE_TRACE("Date being used is: %s %s\n",
254 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
259 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
260 FORMAT_MESSAGE_FROM_SYSTEM,
261 NULL, ERROR_INVALID_PARAMETER, 0,
262 (LPTSTR) &lpMsgBuf, 0, NULL);
264 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
265 ERROR_INVALID_PARAMETER, GetLastError());
267 printf("%S\n", lpMsgBuf);
268 LocalFree ((HLOCAL)lpMsgBuf);
273 flags |= OPT_DATENEWER;
277 case '-': if (toupper(argvW[0][2])=='Y')
278 flags &= ~OPT_NOPROMPT; break;
280 WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
286 /* Default the destination if not supplied */
287 if (supplieddestination[0] == 0x00)
288 lstrcpyW(supplieddestination, wchr_dot);
290 /* Trace out the supplied information */
291 WINE_TRACE("Supplied parameters:\n");
292 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
293 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
295 /* Extract required information from source specification */
296 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
298 /* Extract required information from destination specification */
299 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
300 destinationspec, sourcespec, flags);
302 /* Trace out the resulting information */
303 WINE_TRACE("Resolved parameters:\n");
304 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
305 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
306 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
307 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
309 /* Pause if necessary */
310 if (flags & OPT_PAUSE) {
314 printf("Press <enter> to begin copying\n");
315 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
319 /* Now do the hard work... */
320 rc = XCOPY_DoCopy(sourcestem, sourcespec,
321 destinationstem, destinationspec,
324 /* Clear up exclude list allocated memory */
325 while (excludeList) {
326 EXCLUDELIST *pos = excludeList;
327 excludeList = excludeList -> next;
328 HeapFree(GetProcessHeap(), 0, pos->name);
329 HeapFree(GetProcessHeap(), 0, pos);
332 /* Finished - print trailer and exit */
333 if (flags & OPT_SIMULATE) {
334 printf("%d file(s) would be copied\n", filesCopied);
335 } else if (!(flags & OPT_NOCOPY)) {
336 printf("%d file(s) copied\n", filesCopied);
338 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
344 /* =========================================================================
345 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
346 converts it into a stem and a filespec
347 ========================================================================= */
348 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
350 WCHAR actualsource[MAX_PATH];
355 * Validate the source, expanding to full path ensuring it exists
357 if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
358 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
363 * Work out the stem of the source
366 /* If no wildcard were supplied then the source is either a single
367 file or a directory - in which case thats the stem of the search,
368 otherwise split off the wildcards and use the higher level as the
370 lstrcpyW(stem, actualsource);
371 starPos = wcschr(stem, '*');
372 questPos = wcschr(stem, '?');
373 if (starPos || questPos) {
376 if (starPos) *starPos = 0x00;
377 if (questPos) *questPos = 0x00;
379 lastDir = wcsrchr(stem, '\\');
380 if (lastDir) *(lastDir+1) = 0x00;
382 WINE_FIXME("Unexpected syntax error in source parameter\n");
385 lstrcpyW(spec, actualsource + (lastDir - stem)+1);
388 DWORD attribs = GetFileAttributes(actualsource);
390 if (attribs == INVALID_FILE_ATTRIBUTES) {
392 DWORD lastError = GetLastError();
394 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
395 FORMAT_MESSAGE_FROM_SYSTEM,
396 NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
397 printf("%S\n", lpMsgBuf);
401 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
402 lstrcatW(stem, wchr_slash);
403 lstrcpyW(spec, wchr_star);
407 WCHAR drive[MAX_PATH];
409 WCHAR fname[MAX_PATH];
411 _wsplitpath(actualsource, drive, dir, fname, ext);
412 lstrcpyW(stem, drive);
414 lstrcpyW(spec, fname);
421 /* =========================================================================
422 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
423 converts it into a stem
424 ========================================================================= */
425 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
426 WCHAR *srcspec, DWORD flags)
428 WCHAR actualdestination[MAX_PATH];
433 * Validate the source, expanding to full path ensuring it exists
435 if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
436 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
440 /* Destination is either a directory or a file */
441 attribs = GetFileAttributes(actualdestination);
443 if (attribs == INVALID_FILE_ATTRIBUTES) {
445 /* If /I supplied and wildcard copy, assume directory */
446 if (flags & OPT_ASSUMEDIR &&
447 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
453 char answer[10] = "";
455 while (answer[0] != 'F' && answer[0] != 'D') {
456 printf("Is %S a filename or directory\n"
458 "(F - File, D - Directory)\n", supplieddestination);
460 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
461 WINE_TRACE("User answer %c\n", answer[0]);
463 answer[0] = toupper(answer[0]);
466 if (answer[0] == 'D') {
473 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
477 lstrcpyW(stem, actualdestination);
480 /* Ensure ends with a '\' */
481 if (stem[lstrlenW(stem)-1] != '\\') {
482 lstrcatW(stem, wchr_slash);
486 WCHAR drive[MAX_PATH];
488 WCHAR fname[MAX_PATH];
490 _wsplitpath(actualdestination, drive, dir, fname, ext);
491 lstrcpyW(stem, drive);
493 lstrcpyW(spec, fname);
499 /* =========================================================================
500 XCOPY_DoCopy - Recursive function to copy files based on input parms
503 This works by using FindFirstFile supplying the source stem and spec.
504 If results are found, any non-directory ones are processed
505 Then, if /S or /E is supplied, another search is made just for
506 directories, and this function is called again for that directory
508 ========================================================================= */
509 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
510 WCHAR *deststem, WCHAR *destspec,
513 WIN32_FIND_DATA *finddata;
516 WCHAR *inputpath, *outputpath;
517 BOOL copiedFile = FALSE;
518 DWORD destAttribs, srcAttribs;
524 /* Allocate some working memory on heap to minimize footprint */
525 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
526 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
527 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
529 /* Build the search info into a single parm */
530 lstrcpyW(inputpath, srcstem);
531 lstrcatW(inputpath, srcspec);
533 /* Search 1 - Look for matching files */
534 h = FindFirstFile(inputpath, finddata);
535 while (h != INVALID_HANDLE_VALUE && findres) {
539 /* Ignore . and .. */
540 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
541 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
542 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
544 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
547 /* Get the filename information */
548 lstrcpyW(copyFrom, srcstem);
549 if (flags & OPT_SHORTNAME) {
550 lstrcatW(copyFrom, finddata->cAlternateFileName);
552 lstrcatW(copyFrom, finddata->cFileName);
555 lstrcpyW(copyTo, deststem);
556 if (*destspec == 0x00) {
557 if (flags & OPT_SHORTNAME) {
558 lstrcatW(copyTo, finddata->cAlternateFileName);
560 lstrcatW(copyTo, finddata->cFileName);
563 lstrcatW(copyTo, destspec);
567 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
568 wine_dbgstr_w(copyTo));
569 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
571 /* See if allowed to copy it */
572 srcAttribs = GetFileAttributesW(copyFrom);
573 WINE_TRACE("Source attribs: %d\n", srcAttribs);
575 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
576 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
578 if (!(flags & OPT_COPYHIDSYS)) {
583 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
584 (flags & OPT_ARCHIVEONLY)) {
588 /* See if file exists */
589 destAttribs = GetFileAttributesW(copyTo);
590 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
592 /* Check date ranges if a destination file already exists */
593 if (!skipFile && (flags & OPT_DATERANGE) &&
594 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
595 WINE_TRACE("Skipping file as modified date too old\n");
599 /* If just /D supplied, only overwrite if src newer than dest */
600 if (!skipFile && (flags & OPT_DATENEWER) &&
601 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
602 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
603 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
605 if (h != INVALID_HANDLE_VALUE) {
607 GetFileTime(h, NULL, NULL, &writeTime);
609 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
610 WINE_TRACE("Skipping file as dest newer or same date\n");
617 /* See if exclude list provided. Note since filenames are case
618 insensitive, need to uppercase the filename before doing
620 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
621 EXCLUDELIST *pos = excludeList;
622 WCHAR copyFromUpper[MAX_PATH];
624 /* Uppercase source filename */
625 lstrcpyW(copyFromUpper, copyFrom);
626 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
628 /* Loop through testing each exclude line */
630 if (wcsstr(copyFromUpper, pos->name) != NULL) {
631 WINE_TRACE("Skipping file as matches exclude '%s'\n",
632 wine_dbgstr_w(pos->name));
641 /* Prompt each file if necessary */
642 if (!skipFile && (flags & OPT_SRCPROMPT)) {
645 BOOL answered = FALSE;
648 printf("%S? (Yes|No)\n", copyFrom);
649 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
653 if (toupper(answer[0]) == 'N')
655 else if (toupper(answer[0]) != 'Y')
661 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
664 BOOL answered = FALSE;
667 printf("Overwrite %S? (Yes|No|All)\n", copyTo);
668 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
672 if (toupper(answer[0]) == 'A')
673 flags |= OPT_NOPROMPT;
674 else if (toupper(answer[0]) == 'N')
676 else if (toupper(answer[0]) != 'Y')
681 /* See if it has to exist! */
682 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
686 /* Output a status message */
688 if (flags & OPT_QUIET) {
690 } else if (flags & OPT_FULL) {
691 printf("%S -> %S\n", copyFrom, copyTo);
693 printf("%S\n", copyFrom);
696 /* If allowing overwriting of read only files, remove any
698 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
699 (flags & OPT_REPLACEREAD)) {
700 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
704 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
706 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
707 printf("Copying of '%S' to '%S' failed with r/c %d\n",
708 copyFrom, copyTo, GetLastError());
710 error_code = GetLastError ();
711 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
712 FORMAT_MESSAGE_FROM_SYSTEM,
714 (LPTSTR) &lpMsgBuf, 0, NULL);
716 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
717 error_code, GetLastError());
719 printf("%S\n", lpMsgBuf);
720 LocalFree ((HLOCAL)lpMsgBuf);
722 if (flags & OPT_IGNOREERRORS) {
725 return RC_WRITEERROR;
729 /* If /M supplied, remove the archive bit after successful copy */
731 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
732 (flags & OPT_REMOVEARCH)) {
733 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
741 findres = FindNextFile(h, finddata);
745 /* Search 2 - do subdirs */
746 if (flags & OPT_RECURSIVE) {
747 lstrcpyW(inputpath, srcstem);
748 lstrcatW(inputpath, wchr_star);
750 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
752 h = FindFirstFile(inputpath, finddata);
753 while (h != INVALID_HANDLE_VALUE && findres) {
755 /* Only looking for dirs */
756 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
757 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
758 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
760 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
762 /* Make up recursive information */
763 lstrcpyW(inputpath, srcstem);
764 lstrcatW(inputpath, finddata->cFileName);
765 lstrcatW(inputpath, wchr_slash);
767 lstrcpyW(outputpath, deststem);
768 if (*destspec == 0x00) {
769 lstrcatW(outputpath, finddata->cFileName);
771 /* If /E is supplied, create the directory now */
772 if ((flags & OPT_EMPTYDIR) &&
773 !(flags & OPT_SIMULATE))
774 XCOPY_CreateDirectory(outputpath);
776 lstrcatW(outputpath, wchr_slash);
779 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
783 findres = FindNextFile(h, finddata);
788 HeapFree(GetProcessHeap(), 0, finddata);
789 HeapFree(GetProcessHeap(), 0, inputpath);
790 HeapFree(GetProcessHeap(), 0, outputpath);
795 /* =========================================================================
796 * Routine copied from cmd.exe md command -
797 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
798 * dir2 if they do not already exist.
799 * ========================================================================= */
800 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
806 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
807 lstrcpyW(new_path,path);
809 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
810 new_path[len - 1] = 0;
812 while (!CreateDirectory(new_path,NULL))
815 DWORD last_error = GetLastError();
816 if (last_error == ERROR_ALREADY_EXISTS)
819 if (last_error != ERROR_PATH_NOT_FOUND)
825 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
831 len = slash - new_path;
833 if (!XCOPY_CreateDirectory(new_path))
838 new_path[len] = '\\';
840 HeapFree(GetProcessHeap(),0,new_path);
844 /* =========================================================================
845 * Process the /EXCLUDE: file list, building up a list of substrings to
847 * Returns TRUE on any failure
848 * ========================================================================= */
849 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
851 WCHAR *filenameStart = parms;
853 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
856 while (*parms && *parms != ' ' && *parms != '/') {
858 /* If found '+' then process the file found so far */
860 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
863 filenameStart = parms+1;
868 if (filenameStart != parms) {
869 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
877 /* =========================================================================
878 * Process a single file from the /EXCLUDE: file list, building up a list
879 * of substrings to avoid copying
880 * Returns TRUE on any failure
881 * ========================================================================= */
882 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
884 WCHAR endChar = *endOfName;
885 WCHAR buffer[MAXSTRING];
887 const WCHAR readTextMode[] = {'r', 't', 0};
889 /* Null terminate the filename (temporarily updates the filename hence
894 inFile = _wfopen(filename, readTextMode);
895 if (inFile == NULL) {
896 printf("Failed to open '%S'\n", filename);
897 *endOfName = endChar;
901 /* Process line by line */
902 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
903 EXCLUDELIST *thisEntry;
904 int length = lstrlenW(buffer);
907 buffer[length-1] = 0x00;
909 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
910 thisEntry->next = excludeList;
911 excludeList = thisEntry;
912 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
913 (length * sizeof(WCHAR))+1);
914 lstrcpyW(thisEntry->name, buffer);
915 CharUpperBuff(thisEntry->name, length);
916 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
919 /* See if EOF or error occurred */
921 printf("Failed during reading of '%S'\n", filename);
922 *endOfName = endChar;
926 /* Revert the input string to original form, and cleanup + return */
927 *endOfName = endChar;