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>
37 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
40 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
41 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
42 WCHAR *spec, WCHAR *srcspec, DWORD flags);
43 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
44 WCHAR *deststem, WCHAR *destspec,
46 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
47 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
48 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
49 static WCHAR *XCOPY_LoadMessage(UINT id);
50 static void XCOPY_FailMessage(DWORD err);
53 typedef struct _EXCLUDELIST
55 struct _EXCLUDELIST *next;
60 /* Global variables */
61 static ULONG filesCopied = 0; /* Number of files copied */
62 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
63 static FILETIME dateRange; /* Date range to copy after*/
64 static const WCHAR wchr_slash[] = {'\\', 0};
65 static const WCHAR wchr_star[] = {'*', 0};
66 static const WCHAR wchr_dot[] = {'.', 0};
67 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
69 /* Constants (Mostly for widechars) */
72 /* To minimize stack usage during recursion, some temporary variables
74 static WCHAR copyFrom[MAX_PATH];
75 static WCHAR copyTo[MAX_PATH];
78 /* =========================================================================
79 main - Main entrypoint for the xcopy command
81 Processes the args, and drives the actual copying
82 ========================================================================= */
83 int main (int argc, char *argv[])
86 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
87 WCHAR supplieddestination[MAX_PATH] = {0};
88 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
89 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
90 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
91 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
92 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
93 DWORD flags = 0; /* Option flags */
95 const WCHAR PROMPTSTR1[] = {'/', 'Y', 0};
96 const WCHAR PROMPTSTR2[] = {'/', 'y', 0};
97 const WCHAR COPYCMD[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
98 const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
101 * Parse the command line
104 /* overwrite the command line */
105 argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
107 /* Confirm at least one parameter */
109 wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
113 /* Preinitialize flags based on COPYCMD */
114 if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
115 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
116 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
117 flags |= OPT_NOPROMPT;
121 /* Skip first arg, which is the program name */
127 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
129 /* First non-switch parameter is source, second is destination */
130 if (*argvW[0] != '/') {
131 if (suppliedsource[0] == 0x00) {
132 lstrcpyW(suppliedsource, *argvW);
133 } else if (supplieddestination[0] == 0x00) {
134 lstrcpyW(supplieddestination, *argvW);
136 wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
140 /* Process all the switch options
141 Note: Windows docs say /P prompts when dest is created
142 but tests show it is done for each src file
143 regardless of the destination */
144 switch (toupper(argvW[0][1])) {
145 case 'I': flags |= OPT_ASSUMEDIR; break;
146 case 'S': flags |= OPT_RECURSIVE; break;
147 case 'Q': flags |= OPT_QUIET; break;
148 case 'F': flags |= OPT_FULL; break;
149 case 'L': flags |= OPT_SIMULATE; break;
150 case 'W': flags |= OPT_PAUSE; break;
151 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
152 case 'Y': flags |= OPT_NOPROMPT; break;
153 case 'N': flags |= OPT_SHORTNAME; break;
154 case 'U': flags |= OPT_MUSTEXIST; break;
155 case 'R': flags |= OPT_REPLACEREAD; break;
156 case 'H': flags |= OPT_COPYHIDSYS; break;
157 case 'C': flags |= OPT_IGNOREERRORS; break;
158 case 'P': flags |= OPT_SRCPROMPT; break;
159 case 'A': flags |= OPT_ARCHIVEONLY; break;
160 case 'M': flags |= OPT_ARCHIVEONLY |
161 OPT_REMOVEARCH; break;
163 /* E can be /E or /EXCLUDE */
164 case 'E': if (CompareString (LOCALE_USER_DEFAULT,
165 NORM_IGNORECASE | SORT_STRINGSORT,
168 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
169 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
171 } else flags |= OPT_EXCLUDELIST;
172 } else flags |= OPT_EMPTYDIR;
175 /* D can be /D or /D: */
176 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
178 WCHAR *pos = &argvW[0][3];
179 BOOL isError = FALSE;
180 memset(&st, 0x00, sizeof(st));
182 /* Parse the arg : Month */
183 st.wMonth = _wtol(pos);
184 while (*pos && isdigit(*pos)) pos++;
185 if (*pos++ != '-') isError = TRUE;
187 /* Parse the arg : Day */
189 st.wDay = _wtol(pos);
190 while (*pos && isdigit(*pos)) pos++;
191 if (*pos++ != '-') isError = TRUE;
194 /* Parse the arg : Day */
196 st.wYear = _wtol(pos);
197 if (st.wYear < 100) st.wYear+=2000;
200 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
202 WCHAR datestring[32], timestring[32];
204 flags |= OPT_DATERANGE;
207 FileTimeToSystemTime (&dateRange, &st);
208 GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
210 GetTimeFormat (0, TIME_NOSECONDS, &st,
211 NULL, timestring, sizeof(timestring));
213 WINE_TRACE("Date being used is: %s %s\n",
214 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
216 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
220 flags |= OPT_DATENEWER;
224 case '-': if (toupper(argvW[0][2])=='Y')
225 flags &= ~OPT_NOPROMPT; break;
227 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
228 wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
235 /* Default the destination if not supplied */
236 if (supplieddestination[0] == 0x00)
237 lstrcpyW(supplieddestination, wchr_dot);
239 /* Trace out the supplied information */
240 WINE_TRACE("Supplied parameters:\n");
241 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
242 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
244 /* Extract required information from source specification */
245 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
247 /* Extract required information from destination specification */
248 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
249 destinationspec, sourcespec, flags);
251 /* Trace out the resulting information */
252 WINE_TRACE("Resolved parameters:\n");
253 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
254 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
255 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
256 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
258 /* Pause if necessary */
259 if (flags & OPT_PAUSE) {
263 wprintf(XCOPY_LoadMessage(STRING_PAUSE));
264 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
268 /* Now do the hard work... */
269 rc = XCOPY_DoCopy(sourcestem, sourcespec,
270 destinationstem, destinationspec,
273 /* Clear up exclude list allocated memory */
274 while (excludeList) {
275 EXCLUDELIST *pos = excludeList;
276 excludeList = excludeList -> next;
277 HeapFree(GetProcessHeap(), 0, pos->name);
278 HeapFree(GetProcessHeap(), 0, pos);
281 /* Finished - print trailer and exit */
282 if (flags & OPT_SIMULATE) {
283 wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
284 } else if (!(flags & OPT_NOCOPY)) {
285 wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
287 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
293 /* =========================================================================
294 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
295 converts it into a stem and a filespec
296 ========================================================================= */
297 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
299 WCHAR actualsource[MAX_PATH];
304 * Validate the source, expanding to full path ensuring it exists
306 if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
307 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
312 * Work out the stem of the source
315 /* If no wildcard were supplied then the source is either a single
316 file or a directory - in which case thats the stem of the search,
317 otherwise split off the wildcards and use the higher level as the
319 lstrcpyW(stem, actualsource);
320 starPos = wcschr(stem, '*');
321 questPos = wcschr(stem, '?');
322 if (starPos || questPos) {
325 if (starPos) *starPos = 0x00;
326 if (questPos) *questPos = 0x00;
328 lastDir = wcsrchr(stem, '\\');
329 if (lastDir) *(lastDir+1) = 0x00;
331 WINE_FIXME("Unexpected syntax error in source parameter\n");
334 lstrcpyW(spec, actualsource + (lastDir - stem)+1);
337 DWORD attribs = GetFileAttributes(actualsource);
339 if (attribs == INVALID_FILE_ATTRIBUTES) {
340 XCOPY_FailMessage(GetLastError());
344 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
345 lstrcatW(stem, wchr_slash);
346 lstrcpyW(spec, wchr_star);
350 WCHAR drive[MAX_PATH];
352 WCHAR fname[MAX_PATH];
354 _wsplitpath(actualsource, drive, dir, fname, ext);
355 lstrcpyW(stem, drive);
357 lstrcpyW(spec, fname);
364 /* =========================================================================
365 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
366 converts it into a stem
367 ========================================================================= */
368 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
369 WCHAR *srcspec, DWORD flags)
371 WCHAR actualdestination[MAX_PATH];
376 * Validate the source, expanding to full path ensuring it exists
378 if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
379 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
383 /* Destination is either a directory or a file */
384 attribs = GetFileAttributes(actualdestination);
386 if (attribs == INVALID_FILE_ATTRIBUTES) {
388 /* If /I supplied and wildcard copy, assume directory */
389 if (flags & OPT_ASSUMEDIR &&
390 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
396 char answer[10] = "";
400 /* Read the F and D characters from the resource file */
401 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
402 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
404 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
405 wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
407 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
408 WINE_TRACE("User answer %c\n", answer[0]);
410 answer[0] = toupper(answer[0]);
413 if (answer[0] == dirChar[0]) {
420 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
424 lstrcpyW(stem, actualdestination);
427 /* Ensure ends with a '\' */
428 if (stem[lstrlenW(stem)-1] != '\\') {
429 lstrcatW(stem, wchr_slash);
433 WCHAR drive[MAX_PATH];
435 WCHAR fname[MAX_PATH];
437 _wsplitpath(actualdestination, drive, dir, fname, ext);
438 lstrcpyW(stem, drive);
440 lstrcpyW(spec, fname);
446 /* =========================================================================
447 XCOPY_DoCopy - Recursive function to copy files based on input parms
450 This works by using FindFirstFile supplying the source stem and spec.
451 If results are found, any non-directory ones are processed
452 Then, if /S or /E is supplied, another search is made just for
453 directories, and this function is called again for that directory
455 ========================================================================= */
456 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
457 WCHAR *deststem, WCHAR *destspec,
460 WIN32_FIND_DATA *finddata;
463 WCHAR *inputpath, *outputpath;
464 BOOL copiedFile = FALSE;
465 DWORD destAttribs, srcAttribs;
468 /* Allocate some working memory on heap to minimize footprint */
469 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
470 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
471 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
473 /* Build the search info into a single parm */
474 lstrcpyW(inputpath, srcstem);
475 lstrcatW(inputpath, srcspec);
477 /* Search 1 - Look for matching files */
478 h = FindFirstFile(inputpath, finddata);
479 while (h != INVALID_HANDLE_VALUE && findres) {
483 /* Ignore . and .. */
484 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
485 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
486 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
488 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
491 /* Get the filename information */
492 lstrcpyW(copyFrom, srcstem);
493 if (flags & OPT_SHORTNAME) {
494 lstrcatW(copyFrom, finddata->cAlternateFileName);
496 lstrcatW(copyFrom, finddata->cFileName);
499 lstrcpyW(copyTo, deststem);
500 if (*destspec == 0x00) {
501 if (flags & OPT_SHORTNAME) {
502 lstrcatW(copyTo, finddata->cAlternateFileName);
504 lstrcatW(copyTo, finddata->cFileName);
507 lstrcatW(copyTo, destspec);
511 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
512 wine_dbgstr_w(copyTo));
513 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
515 /* See if allowed to copy it */
516 srcAttribs = GetFileAttributesW(copyFrom);
517 WINE_TRACE("Source attribs: %d\n", srcAttribs);
519 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
520 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
522 if (!(flags & OPT_COPYHIDSYS)) {
527 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
528 (flags & OPT_ARCHIVEONLY)) {
532 /* See if file exists */
533 destAttribs = GetFileAttributesW(copyTo);
534 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
536 /* Check date ranges if a destination file already exists */
537 if (!skipFile && (flags & OPT_DATERANGE) &&
538 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
539 WINE_TRACE("Skipping file as modified date too old\n");
543 /* If just /D supplied, only overwrite if src newer than dest */
544 if (!skipFile && (flags & OPT_DATENEWER) &&
545 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
546 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
547 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
549 if (h != INVALID_HANDLE_VALUE) {
551 GetFileTime(h, NULL, NULL, &writeTime);
553 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
554 WINE_TRACE("Skipping file as dest newer or same date\n");
561 /* See if exclude list provided. Note since filenames are case
562 insensitive, need to uppercase the filename before doing
564 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
565 EXCLUDELIST *pos = excludeList;
566 WCHAR copyFromUpper[MAX_PATH];
568 /* Uppercase source filename */
569 lstrcpyW(copyFromUpper, copyFrom);
570 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
572 /* Loop through testing each exclude line */
574 if (wcsstr(copyFromUpper, pos->name) != NULL) {
575 WINE_TRACE("Skipping file as matches exclude '%s'\n",
576 wine_dbgstr_w(pos->name));
585 /* Prompt each file if necessary */
586 if (!skipFile && (flags & OPT_SRCPROMPT)) {
589 BOOL answered = FALSE;
593 /* Read the Y and N characters from the resource file */
594 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
595 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
598 wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
599 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
603 if (toupper(answer[0]) == noChar[0])
605 else if (toupper(answer[0]) != yesChar[0])
611 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
614 BOOL answered = FALSE;
619 /* Read the A,Y and N characters from the resource file */
620 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
621 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
622 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
625 wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
626 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
630 if (toupper(answer[0]) == allChar[0])
631 flags |= OPT_NOPROMPT;
632 else if (toupper(answer[0]) == noChar[0])
634 else if (toupper(answer[0]) != yesChar[0])
639 /* See if it has to exist! */
640 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
644 /* Output a status message */
646 if (flags & OPT_QUIET) {
648 } else if (flags & OPT_FULL) {
649 printf("%S -> %S\n", copyFrom, copyTo);
651 printf("%S\n", copyFrom);
654 /* If allowing overwriting of read only files, remove any
656 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
657 (flags & OPT_REPLACEREAD)) {
658 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
662 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
664 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
666 DWORD error = GetLastError();
667 wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
668 copyFrom, copyTo, error);
669 XCOPY_FailMessage(error);
671 if (flags & OPT_IGNOREERRORS) {
674 return RC_WRITEERROR;
678 /* If /M supplied, remove the archive bit after successful copy */
680 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
681 (flags & OPT_REMOVEARCH)) {
682 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
690 findres = FindNextFile(h, finddata);
694 /* Search 2 - do subdirs */
695 if (flags & OPT_RECURSIVE) {
696 lstrcpyW(inputpath, srcstem);
697 lstrcatW(inputpath, wchr_star);
699 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
701 h = FindFirstFile(inputpath, finddata);
702 while (h != INVALID_HANDLE_VALUE && findres) {
704 /* Only looking for dirs */
705 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
706 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
707 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
709 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
711 /* Make up recursive information */
712 lstrcpyW(inputpath, srcstem);
713 lstrcatW(inputpath, finddata->cFileName);
714 lstrcatW(inputpath, wchr_slash);
716 lstrcpyW(outputpath, deststem);
717 if (*destspec == 0x00) {
718 lstrcatW(outputpath, finddata->cFileName);
720 /* If /E is supplied, create the directory now */
721 if ((flags & OPT_EMPTYDIR) &&
722 !(flags & OPT_SIMULATE))
723 XCOPY_CreateDirectory(outputpath);
725 lstrcatW(outputpath, wchr_slash);
728 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
732 findres = FindNextFile(h, finddata);
737 HeapFree(GetProcessHeap(), 0, finddata);
738 HeapFree(GetProcessHeap(), 0, inputpath);
739 HeapFree(GetProcessHeap(), 0, outputpath);
744 /* =========================================================================
745 * Routine copied from cmd.exe md command -
746 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
747 * dir2 if they do not already exist.
748 * ========================================================================= */
749 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
755 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
756 lstrcpyW(new_path,path);
758 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
759 new_path[len - 1] = 0;
761 while (!CreateDirectory(new_path,NULL))
764 DWORD last_error = GetLastError();
765 if (last_error == ERROR_ALREADY_EXISTS)
768 if (last_error != ERROR_PATH_NOT_FOUND)
774 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
780 len = slash - new_path;
782 if (!XCOPY_CreateDirectory(new_path))
787 new_path[len] = '\\';
789 HeapFree(GetProcessHeap(),0,new_path);
793 /* =========================================================================
794 * Process the /EXCLUDE: file list, building up a list of substrings to
796 * Returns TRUE on any failure
797 * ========================================================================= */
798 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
800 WCHAR *filenameStart = parms;
802 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
805 while (*parms && *parms != ' ' && *parms != '/') {
807 /* If found '+' then process the file found so far */
809 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
812 filenameStart = parms+1;
817 if (filenameStart != parms) {
818 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
826 /* =========================================================================
827 * Process a single file from the /EXCLUDE: file list, building up a list
828 * of substrings to avoid copying
829 * Returns TRUE on any failure
830 * ========================================================================= */
831 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
833 WCHAR endChar = *endOfName;
834 WCHAR buffer[MAXSTRING];
836 const WCHAR readTextMode[] = {'r', 't', 0};
838 /* Null terminate the filename (temporarily updates the filename hence
843 inFile = _wfopen(filename, readTextMode);
844 if (inFile == NULL) {
845 wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
846 *endOfName = endChar;
850 /* Process line by line */
851 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
852 EXCLUDELIST *thisEntry;
853 int length = lstrlenW(buffer);
856 buffer[length-1] = 0x00;
858 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
859 thisEntry->next = excludeList;
860 excludeList = thisEntry;
861 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
862 (length * sizeof(WCHAR))+1);
863 lstrcpyW(thisEntry->name, buffer);
864 CharUpperBuff(thisEntry->name, length);
865 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
868 /* See if EOF or error occurred */
870 wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
871 *endOfName = endChar;
875 /* Revert the input string to original form, and cleanup + return */
876 *endOfName = endChar;
881 /* =========================================================================
882 * Load a string from the resource file, handling any error
883 * Returns string retrieved from resource file
884 * ========================================================================= */
885 static WCHAR *XCOPY_LoadMessage(UINT id) {
886 static WCHAR msg[2048];
887 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
889 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
890 WINE_FIXME("LoadString failed with %d\n", GetLastError());
891 lstrcpyW(msg, failedMsg);
896 /* =========================================================================
897 * Load a string for a system error and writes it to the screen
898 * Returns string retrieved from resource file
899 * ========================================================================= */
900 static void XCOPY_FailMessage(DWORD err) {
904 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
905 FORMAT_MESSAGE_FROM_SYSTEM,
907 (LPTSTR) &lpMsgBuf, 0, NULL);
909 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
910 err, GetLastError());
912 printf("%S\n", lpMsgBuf);
913 LocalFree ((HLOCAL)lpMsgBuf);