mshtml: Fixed event tests.
[wine/testsucceed.git] / programs / xcopy / xcopy.c
blob4572374de9fb7240b44ff5b9536f974e2fd02f54
1 /*
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
22 * FIXME:
23 * This should now support all options listed in the xcopy help from
24 * windows XP except:
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
29 * /V - Verifies files
33 * Notes:
34 * Apparently, valid return codes are:
35 * 0 - OK
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
43 #include <stdio.h>
44 #include <windows.h>
45 #include <wine/debug.h>
46 #include <wine/unicode.h>
47 #include "xcopy.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
51 /* Prototypes */
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,
58 DWORD flags);
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, ...);
66 /* Typedefs */
67 typedef struct _EXCLUDELIST
69 struct _EXCLUDELIST *next;
70 WCHAR *name;
71 } EXCLUDELIST;
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
87 made global */
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[])
99 int rc = 0;
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 */
118 if (argc < 2) {
119 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
120 return RC_INITERROR;
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
135 lose */
136 flags |= OPT_COPYHIDSYS;
138 /* Skip first arg, which is the program name */
139 argvW++;
141 while (argc > 1)
143 argc--;
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);
152 } else {
153 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
154 return RC_INITERROR;
156 } else {
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,
183 &argvW[0][1], 8,
184 EXCLUDE, -1) == 2) {
185 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
186 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
187 return RC_INITERROR;
188 } else flags |= OPT_EXCLUDELIST;
189 } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
190 break;
192 /* D can be /D or /D: */
193 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
194 SYSTEMTIME st;
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 */
205 if (!isError) {
206 st.wDay = _wtol(pos);
207 while (*pos && isdigit(*pos)) pos++;
208 if (*pos++ != '-') isError = TRUE;
211 /* Parse the arg : Day */
212 if (!isError) {
213 st.wYear = _wtol(pos);
214 if (st.wYear < 100) st.wYear+=2000;
217 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
218 SYSTEMTIME st;
219 WCHAR datestring[32], timestring[32];
221 flags |= OPT_DATERANGE;
223 /* Debug info: */
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));
232 } else {
233 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
234 return RC_INITERROR;
236 } else {
237 flags |= OPT_DATENEWER;
239 break;
241 case '-': if (toupper(argvW[0][2])=='Y')
242 flags &= ~OPT_NOPROMPT; break;
243 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
244 return RC_OK;
245 default:
246 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
247 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
248 return RC_INITERROR;
251 argvW++;
254 /* Default the destination if not supplied */
255 if (supplieddestination[0] == 0x00)
256 lstrcpyW(supplieddestination, wchr_dot);
258 /* Trace out the supplied information */
259 WINE_TRACE("Supplied parameters:\n");
260 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
261 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
263 /* Extract required information from source specification */
264 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
265 if (rc != RC_OK) return rc;
267 /* Extract required information from destination specification */
268 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
269 destinationspec, sourcespec, flags);
270 if (rc != RC_OK) return rc;
272 /* Trace out the resulting information */
273 WINE_TRACE("Resolved parameters:\n");
274 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
275 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
276 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
277 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
279 /* Pause if necessary */
280 if (flags & OPT_PAUSE) {
281 DWORD count;
282 char pausestr[10];
284 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
285 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
286 &count, NULL);
289 /* Now do the hard work... */
290 rc = XCOPY_DoCopy(sourcestem, sourcespec,
291 destinationstem, destinationspec,
292 flags);
294 /* Clear up exclude list allocated memory */
295 while (excludeList) {
296 EXCLUDELIST *pos = excludeList;
297 excludeList = excludeList -> next;
298 HeapFree(GetProcessHeap(), 0, pos->name);
299 HeapFree(GetProcessHeap(), 0, pos);
302 /* Finished - print trailer and exit */
303 if (flags & OPT_SIMULATE) {
304 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
305 } else if (!(flags & OPT_NOCOPY)) {
306 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
308 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
309 return rc;
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];
322 WCHAR *starPos;
323 WCHAR *questPos;
324 DWORD attribs;
327 * Validate the source, expanding to full path ensuring it exists
329 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
330 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
331 return RC_INITERROR;
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
344 partially qualified)
345 If a filename is supplied + a directory or drive path, use that
346 as-is
347 Otherwise
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 */
356 } else {
357 attribs = GetFileAttributesW(actualsource);
360 if (attribs == INVALID_FILE_ATTRIBUTES) {
361 XCOPY_FailMessage(GetLastError());
362 return RC_INITERROR;
364 /* Directory:
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) {
368 WCHAR lastChar;
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:
379 stem should be:
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 + ':' */
383 } else {
384 WCHAR *lastDir;
386 WINE_TRACE("Filename supplied\n");
387 lastDir = wcsrchr(suppliedsource, '\\');
389 if (lastDir) {
390 lstrcpyW(stem, suppliedsource);
391 stem[(lastDir-suppliedsource) + 1] = 0x00;
392 lstrcpyW(spec, (lastDir+1));
393 } else if (suppliedsource[1] == ':') {
394 lstrcpyW(stem, suppliedsource);
395 stem[2] = 0x00;
396 lstrcpyW(spec, suppliedsource+2);
397 } else {
398 WCHAR curdir[MAXSTRING];
399 GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
400 stem[0] = curdir[0];
401 stem[1] = curdir[1];
402 stem[2] = 0x00;
403 lstrcpyW(spec, suppliedsource);
407 return RC_OK;
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];
418 DWORD attribs;
419 BOOL isDir = FALSE;
422 * Validate the source, expanding to full path ensuring it exists
424 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
425 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
426 return RC_INITERROR;
429 /* Destination is either a directory or a file */
430 attribs = GetFileAttributesW(actualdestination);
432 if (attribs == INVALID_FILE_ATTRIBUTES) {
434 /* If /I supplied and wildcard copy, assume directory */
435 if (flags & OPT_ASSUMEDIR &&
436 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
438 isDir = TRUE;
440 } else {
441 DWORD count;
442 char answer[10] = "";
443 WCHAR fileChar[2];
444 WCHAR dirChar[2];
446 /* Read the F and D characters from the resource file */
447 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
448 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
450 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
451 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
453 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
454 WINE_TRACE("User answer %c\n", answer[0]);
456 answer[0] = toupper(answer[0]);
459 if (answer[0] == dirChar[0]) {
460 isDir = TRUE;
461 } else {
462 isDir = FALSE;
465 } else {
466 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
469 if (isDir) {
470 lstrcpyW(stem, actualdestination);
471 *spec = 0x00;
473 /* Ensure ends with a '\' */
474 if (stem[lstrlenW(stem)-1] != '\\') {
475 lstrcatW(stem, wchr_slash);
478 } else {
479 WCHAR drive[MAX_PATH];
480 WCHAR dir[MAX_PATH];
481 WCHAR fname[MAX_PATH];
482 WCHAR ext[MAX_PATH];
483 _wsplitpath(actualdestination, drive, dir, fname, ext);
484 lstrcpyW(stem, drive);
485 lstrcatW(stem, dir);
486 lstrcpyW(spec, fname);
487 lstrcatW(spec, ext);
489 return RC_OK;
492 /* =========================================================================
493 XCOPY_DoCopy - Recursive function to copy files based on input parms
494 of a stem and a spec
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,
504 DWORD flags)
506 WIN32_FIND_DATAW *finddata;
507 HANDLE h;
508 BOOL findres = TRUE;
509 WCHAR *inputpath, *outputpath;
510 BOOL copiedFile = FALSE;
511 DWORD destAttribs, srcAttribs;
512 BOOL skipFile;
513 int ret = 0;
515 /* Allocate some working memory on heap to minimize footprint */
516 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
517 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
518 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
520 /* Build the search info into a single parm */
521 lstrcpyW(inputpath, srcstem);
522 lstrcatW(inputpath, srcspec);
524 /* Search 1 - Look for matching files */
525 h = FindFirstFileW(inputpath, finddata);
526 while (h != INVALID_HANDLE_VALUE && findres) {
528 skipFile = FALSE;
530 /* Ignore . and .. */
531 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
532 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
533 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
535 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
536 } else {
538 /* Get the filename information */
539 lstrcpyW(copyFrom, srcstem);
540 if (flags & OPT_SHORTNAME) {
541 lstrcatW(copyFrom, finddata->cAlternateFileName);
542 } else {
543 lstrcatW(copyFrom, finddata->cFileName);
546 lstrcpyW(copyTo, deststem);
547 if (*destspec == 0x00) {
548 if (flags & OPT_SHORTNAME) {
549 lstrcatW(copyTo, finddata->cAlternateFileName);
550 } else {
551 lstrcatW(copyTo, finddata->cFileName);
553 } else {
554 lstrcatW(copyTo, destspec);
557 /* Do the copy */
558 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
559 wine_dbgstr_w(copyTo));
560 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
562 /* See if allowed to copy it */
563 srcAttribs = GetFileAttributesW(copyFrom);
564 WINE_TRACE("Source attribs: %d\n", srcAttribs);
566 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
567 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
569 if (!(flags & OPT_COPYHIDSYS)) {
570 skipFile = TRUE;
574 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
575 (flags & OPT_ARCHIVEONLY)) {
576 skipFile = TRUE;
579 /* See if file exists */
580 destAttribs = GetFileAttributesW(copyTo);
581 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
583 /* Check date ranges if a destination file already exists */
584 if (!skipFile && (flags & OPT_DATERANGE) &&
585 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
586 WINE_TRACE("Skipping file as modified date too old\n");
587 skipFile = TRUE;
590 /* If just /D supplied, only overwrite if src newer than dest */
591 if (!skipFile && (flags & OPT_DATENEWER) &&
592 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
593 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
594 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
595 NULL);
596 if (h != INVALID_HANDLE_VALUE) {
597 FILETIME writeTime;
598 GetFileTime(h, NULL, NULL, &writeTime);
600 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
601 WINE_TRACE("Skipping file as dest newer or same date\n");
602 skipFile = TRUE;
604 CloseHandle(h);
608 /* See if exclude list provided. Note since filenames are case
609 insensitive, need to uppercase the filename before doing
610 strstr */
611 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
612 EXCLUDELIST *pos = excludeList;
613 WCHAR copyFromUpper[MAX_PATH];
615 /* Uppercase source filename */
616 lstrcpyW(copyFromUpper, copyFrom);
617 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
619 /* Loop through testing each exclude line */
620 while (pos) {
621 if (wcsstr(copyFromUpper, pos->name) != NULL) {
622 WINE_TRACE("Skipping file as matches exclude '%s'\n",
623 wine_dbgstr_w(pos->name));
624 skipFile = TRUE;
625 pos = NULL;
626 } else {
627 pos = pos->next;
632 /* Prompt each file if necessary */
633 if (!skipFile && (flags & OPT_SRCPROMPT)) {
634 DWORD count;
635 char answer[10];
636 BOOL answered = FALSE;
637 WCHAR yesChar[2];
638 WCHAR noChar[2];
640 /* Read the Y and N characters from the resource file */
641 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
642 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
644 while (!answered) {
645 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
646 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
647 &count, NULL);
649 answered = TRUE;
650 if (toupper(answer[0]) == noChar[0])
651 skipFile = TRUE;
652 else if (toupper(answer[0]) != yesChar[0])
653 answered = FALSE;
657 if (!skipFile &&
658 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
659 DWORD count;
660 char answer[10];
661 BOOL answered = FALSE;
662 WCHAR yesChar[2];
663 WCHAR allChar[2];
664 WCHAR noChar[2];
666 /* Read the A,Y and N characters from the resource file */
667 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
668 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
669 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
671 while (!answered) {
672 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
673 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
674 &count, NULL);
676 answered = TRUE;
677 if (toupper(answer[0]) == allChar[0])
678 flags |= OPT_NOPROMPT;
679 else if (toupper(answer[0]) == noChar[0])
680 skipFile = TRUE;
681 else if (toupper(answer[0]) != yesChar[0])
682 answered = FALSE;
686 /* See if it has to exist! */
687 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
688 skipFile = TRUE;
691 /* Output a status message */
692 if (!skipFile) {
693 if (flags & OPT_QUIET) {
694 /* Skip message */
695 } else if (flags & OPT_FULL) {
696 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
697 '%', 's', '\n', 0};
699 XCOPY_wprintf(infostr, copyFrom, copyTo);
700 } else {
701 const WCHAR infostr[] = {'%', 's', '\n', 0};
702 XCOPY_wprintf(infostr, copyFrom);
705 /* If allowing overwriting of read only files, remove any
706 write protection */
707 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
708 (flags & OPT_REPLACEREAD)) {
709 SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
712 copiedFile = TRUE;
713 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
714 /* Skip copy */
715 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
717 DWORD error = GetLastError();
718 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
719 copyFrom, copyTo, error);
720 XCOPY_FailMessage(error);
722 if (flags & OPT_IGNOREERRORS) {
723 skipFile = TRUE;
724 } else {
725 ret = RC_WRITEERROR;
726 goto cleanup;
730 /* If /M supplied, remove the archive bit after successful copy */
731 if (!skipFile) {
732 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
733 (flags & OPT_REMOVEARCH)) {
734 SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
736 filesCopied++;
741 /* Find next file */
742 findres = FindNextFileW(h, finddata);
744 FindClose(h);
746 /* Search 2 - do subdirs */
747 if (flags & OPT_RECURSIVE) {
748 lstrcpyW(inputpath, srcstem);
749 lstrcatW(inputpath, wchr_star);
750 findres = TRUE;
751 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
753 h = FindFirstFileW(inputpath, finddata);
754 while (h != INVALID_HANDLE_VALUE && findres) {
756 /* Only looking for dirs */
757 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
758 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
759 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
761 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
763 /* Make up recursive information */
764 lstrcpyW(inputpath, srcstem);
765 lstrcatW(inputpath, finddata->cFileName);
766 lstrcatW(inputpath, wchr_slash);
768 lstrcpyW(outputpath, deststem);
769 if (*destspec == 0x00) {
770 lstrcatW(outputpath, finddata->cFileName);
772 /* If /E is supplied, create the directory now */
773 if ((flags & OPT_EMPTYDIR) &&
774 !(flags & OPT_SIMULATE))
775 XCOPY_CreateDirectory(outputpath);
777 lstrcatW(outputpath, wchr_slash);
780 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
783 /* Find next one */
784 findres = FindNextFileW(h, finddata);
788 cleanup:
790 /* free up memory */
791 HeapFree(GetProcessHeap(), 0, finddata);
792 HeapFree(GetProcessHeap(), 0, inputpath);
793 HeapFree(GetProcessHeap(), 0, outputpath);
795 return ret;
798 /* =========================================================================
799 * Routine copied from cmd.exe md command -
800 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
801 * dir2 if they do not already exist.
802 * ========================================================================= */
803 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
805 int len;
806 WCHAR *new_path;
807 BOOL ret = TRUE;
809 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
810 lstrcpyW(new_path,path);
812 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
813 new_path[len - 1] = 0;
815 while (!CreateDirectoryW(new_path,NULL))
817 WCHAR *slash;
818 DWORD last_error = GetLastError();
819 if (last_error == ERROR_ALREADY_EXISTS)
820 break;
822 if (last_error != ERROR_PATH_NOT_FOUND)
824 ret = FALSE;
825 break;
828 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
830 ret = FALSE;
831 break;
834 len = slash - new_path;
835 new_path[len] = 0;
836 if (!XCOPY_CreateDirectory(new_path))
838 ret = FALSE;
839 break;
841 new_path[len] = '\\';
843 HeapFree(GetProcessHeap(),0,new_path);
844 return ret;
847 /* =========================================================================
848 * Process the /EXCLUDE: file list, building up a list of substrings to
849 * avoid copying
850 * Returns TRUE on any failure
851 * ========================================================================= */
852 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
854 WCHAR *filenameStart = parms;
856 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
857 excludeList = NULL;
859 while (*parms && *parms != ' ' && *parms != '/') {
861 /* If found '+' then process the file found so far */
862 if (*parms == '+') {
863 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
864 return TRUE;
866 filenameStart = parms+1;
868 parms++;
871 if (filenameStart != parms) {
872 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
873 return TRUE;
877 return FALSE;
880 /* =========================================================================
881 * Process a single file from the /EXCLUDE: file list, building up a list
882 * of substrings to avoid copying
883 * Returns TRUE on any failure
884 * ========================================================================= */
885 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
887 WCHAR endChar = *endOfName;
888 WCHAR buffer[MAXSTRING];
889 FILE *inFile = NULL;
890 const WCHAR readTextMode[] = {'r', 't', 0};
892 /* Null terminate the filename (temporarily updates the filename hence
893 parms not const) */
894 *endOfName = 0x00;
896 /* Open the file */
897 inFile = _wfopen(filename, readTextMode);
898 if (inFile == NULL) {
899 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
900 *endOfName = endChar;
901 return TRUE;
904 /* Process line by line */
905 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
906 EXCLUDELIST *thisEntry;
907 int length = lstrlenW(buffer);
909 /* Strip CRLF */
910 buffer[length-1] = 0x00;
912 /* If more than CRLF */
913 if (length > 1) {
914 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
915 thisEntry->next = excludeList;
916 excludeList = thisEntry;
917 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
918 (length * sizeof(WCHAR))+1);
919 lstrcpyW(thisEntry->name, buffer);
920 CharUpperBuffW(thisEntry->name, length);
921 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
925 /* See if EOF or error occurred */
926 if (!feof(inFile)) {
927 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
928 *endOfName = endChar;
929 return TRUE;
932 /* Revert the input string to original form, and cleanup + return */
933 *endOfName = endChar;
934 fclose(inFile);
935 return FALSE;
938 /* =========================================================================
939 * Load a string from the resource file, handling any error
940 * Returns string retrieved from resource file
941 * ========================================================================= */
942 static WCHAR *XCOPY_LoadMessage(UINT id) {
943 static WCHAR msg[MAXSTRING];
944 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
946 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
947 WINE_FIXME("LoadString failed with %d\n", GetLastError());
948 lstrcpyW(msg, failedMsg);
950 return msg;
953 /* =========================================================================
954 * Load a string for a system error and writes it to the screen
955 * Returns string retrieved from resource file
956 * ========================================================================= */
957 static void XCOPY_FailMessage(DWORD err) {
958 LPWSTR lpMsgBuf;
959 int status;
961 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
962 FORMAT_MESSAGE_FROM_SYSTEM,
963 NULL, err, 0,
964 (LPWSTR) &lpMsgBuf, 0, NULL);
965 if (!status) {
966 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
967 err, GetLastError());
968 } else {
969 const WCHAR infostr[] = {'%', 's', '\n', 0};
970 XCOPY_wprintf(infostr, lpMsgBuf);
971 LocalFree ((HLOCAL)lpMsgBuf);
975 /* =========================================================================
976 * Output a formatted unicode string. Ideally this will go to the console
977 * and hence required WriteConsoleW to output it, however if file i/o is
978 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
979 * ========================================================================= */
980 int XCOPY_wprintf(const WCHAR *format, ...) {
982 static WCHAR *output_bufW = NULL;
983 static char *output_bufA = NULL;
984 static BOOL toConsole = TRUE;
985 static BOOL traceOutput = FALSE;
986 #define MAX_WRITECONSOLE_SIZE 65535
988 va_list parms;
989 DWORD nOut;
990 int len;
991 DWORD res = 0;
994 * Allocate buffer to use when writing to console
995 * Note: Not freed - memory will be allocated once and released when
996 * xcopy ends
999 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
1000 MAX_WRITECONSOLE_SIZE);
1001 if (!output_bufW) {
1002 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1003 return 0;
1006 va_start(parms, format);
1007 len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
1008 va_end(parms);
1009 if (len < 0) {
1010 WINE_FIXME("String too long.\n");
1011 return 0;
1014 /* Try to write as unicode all the time we think its a console */
1015 if (toConsole) {
1016 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1017 output_bufW, len, &nOut, NULL);
1020 /* If writing to console has failed (ever) we assume its file
1021 i/o so convert to OEM codepage and output */
1022 if (!res) {
1023 BOOL usedDefaultChar = FALSE;
1024 DWORD convertedChars;
1026 toConsole = FALSE;
1029 * Allocate buffer to use when writing to file. Not freed, as above
1031 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1032 MAX_WRITECONSOLE_SIZE);
1033 if (!output_bufA) {
1034 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1035 return 0;
1038 /* Convert to OEM, then output */
1039 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1040 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1041 "?", &usedDefaultChar);
1042 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1043 &nOut, FALSE);
1046 /* Trace whether screen or console */
1047 if (!traceOutput) {
1048 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1049 traceOutput = TRUE;
1051 return nOut;