msi: Fix a few tests that fail in win9x.
[wine.git] / programs / xcopy / xcopy.c
blobaeb839708ac9d88dc3fb0992ca8b240c787bbc2c
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 "xcopy.h"
48 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
50 /* Prototypes */
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,
57 DWORD flags);
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);
63 static int XCOPY_wprintf(const WCHAR *format, ...);
65 /* Typedefs */
66 typedef struct _EXCLUDELIST
68 struct _EXCLUDELIST *next;
69 WCHAR *name;
70 } EXCLUDELIST;
73 /* Global variables */
74 static ULONG filesCopied = 0; /* Number of files copied */
75 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
76 static FILETIME dateRange; /* Date range to copy after*/
77 static const WCHAR wchr_slash[] = {'\\', 0};
78 static const WCHAR wchr_star[] = {'*', 0};
79 static const WCHAR wchr_dot[] = {'.', 0};
80 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
82 /* Constants (Mostly for widechars) */
85 /* To minimize stack usage during recursion, some temporary variables
86 made global */
87 static WCHAR copyFrom[MAX_PATH];
88 static WCHAR copyTo[MAX_PATH];
91 /* =========================================================================
92 main - Main entrypoint for the xcopy command
94 Processes the args, and drives the actual copying
95 ========================================================================= */
96 int wmain (int argc, WCHAR *argvW[])
98 int rc = 0;
99 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
100 WCHAR supplieddestination[MAX_PATH] = {0};
101 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
102 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
103 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
104 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
105 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
106 DWORD flags = 0; /* Option flags */
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 /* Confirm at least one parameter */
117 if (argc < 2) {
118 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
119 return RC_INITERROR;
122 /* Preinitialize flags based on COPYCMD */
123 if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
124 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
125 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
126 flags |= OPT_NOPROMPT;
130 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
131 wine, but on windows these can be normal files. At least one installer
132 uses files such as .packlist and (validly) expects them to be copied.
133 Under wine, if we do not copy hidden files by default then they get
134 lose */
135 flags |= OPT_COPYHIDSYS;
137 /* Skip first arg, which is the program name */
138 argvW++;
140 while (argc > 1)
142 argc--;
143 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
145 /* First non-switch parameter is source, second is destination */
146 if (*argvW[0] != '/') {
147 if (suppliedsource[0] == 0x00) {
148 lstrcpyW(suppliedsource, *argvW);
149 } else if (supplieddestination[0] == 0x00) {
150 lstrcpyW(supplieddestination, *argvW);
151 } else {
152 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
153 return RC_INITERROR;
155 } else {
156 /* Process all the switch options
157 Note: Windows docs say /P prompts when dest is created
158 but tests show it is done for each src file
159 regardless of the destination */
160 switch (toupper(argvW[0][1])) {
161 case 'I': flags |= OPT_ASSUMEDIR; break;
162 case 'S': flags |= OPT_RECURSIVE; break;
163 case 'Q': flags |= OPT_QUIET; break;
164 case 'F': flags |= OPT_FULL; break;
165 case 'L': flags |= OPT_SIMULATE; break;
166 case 'W': flags |= OPT_PAUSE; break;
167 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
168 case 'Y': flags |= OPT_NOPROMPT; break;
169 case 'N': flags |= OPT_SHORTNAME; break;
170 case 'U': flags |= OPT_MUSTEXIST; break;
171 case 'R': flags |= OPT_REPLACEREAD; break;
172 case 'H': flags |= OPT_COPYHIDSYS; break;
173 case 'C': flags |= OPT_IGNOREERRORS; break;
174 case 'P': flags |= OPT_SRCPROMPT; break;
175 case 'A': flags |= OPT_ARCHIVEONLY; break;
176 case 'M': flags |= OPT_ARCHIVEONLY |
177 OPT_REMOVEARCH; break;
179 /* E can be /E or /EXCLUDE */
180 case 'E': if (CompareString (LOCALE_USER_DEFAULT,
181 NORM_IGNORECASE | SORT_STRINGSORT,
182 &argvW[0][1], 8,
183 EXCLUDE, -1) == 2) {
184 if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
185 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
186 return RC_INITERROR;
187 } else flags |= OPT_EXCLUDELIST;
188 } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
189 break;
191 /* D can be /D or /D: */
192 case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
193 SYSTEMTIME st;
194 WCHAR *pos = &argvW[0][3];
195 BOOL isError = FALSE;
196 memset(&st, 0x00, sizeof(st));
198 /* Parse the arg : Month */
199 st.wMonth = _wtol(pos);
200 while (*pos && isdigit(*pos)) pos++;
201 if (*pos++ != '-') isError = TRUE;
203 /* Parse the arg : Day */
204 if (!isError) {
205 st.wDay = _wtol(pos);
206 while (*pos && isdigit(*pos)) pos++;
207 if (*pos++ != '-') isError = TRUE;
210 /* Parse the arg : Day */
211 if (!isError) {
212 st.wYear = _wtol(pos);
213 if (st.wYear < 100) st.wYear+=2000;
216 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
217 SYSTEMTIME st;
218 WCHAR datestring[32], timestring[32];
220 flags |= OPT_DATERANGE;
222 /* Debug info: */
223 FileTimeToSystemTime (&dateRange, &st);
224 GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
225 sizeof(datestring));
226 GetTimeFormat (0, TIME_NOSECONDS, &st,
227 NULL, timestring, sizeof(timestring));
229 WINE_TRACE("Date being used is: %s %s\n",
230 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
231 } else {
232 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
233 return RC_INITERROR;
235 } else {
236 flags |= OPT_DATENEWER;
238 break;
240 case '-': if (toupper(argvW[0][2])=='Y')
241 flags &= ~OPT_NOPROMPT; break;
242 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
243 return RC_OK;
244 default:
245 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
246 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
247 return RC_INITERROR;
250 argvW++;
253 /* Default the destination if not supplied */
254 if (supplieddestination[0] == 0x00)
255 lstrcpyW(supplieddestination, wchr_dot);
257 /* Trace out the supplied information */
258 WINE_TRACE("Supplied parameters:\n");
259 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
260 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
262 /* Extract required information from source specification */
263 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
265 /* Extract required information from destination specification */
266 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
267 destinationspec, sourcespec, flags);
269 /* Trace out the resulting information */
270 WINE_TRACE("Resolved parameters:\n");
271 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
272 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
273 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
274 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
276 /* Pause if necessary */
277 if (flags & OPT_PAUSE) {
278 DWORD count;
279 char pausestr[10];
281 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
282 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
283 &count, NULL);
286 /* Now do the hard work... */
287 rc = XCOPY_DoCopy(sourcestem, sourcespec,
288 destinationstem, destinationspec,
289 flags);
291 /* Clear up exclude list allocated memory */
292 while (excludeList) {
293 EXCLUDELIST *pos = excludeList;
294 excludeList = excludeList -> next;
295 HeapFree(GetProcessHeap(), 0, pos->name);
296 HeapFree(GetProcessHeap(), 0, pos);
299 /* Finished - print trailer and exit */
300 if (flags & OPT_SIMULATE) {
301 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
302 } else if (!(flags & OPT_NOCOPY)) {
303 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
305 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
306 return rc;
311 /* =========================================================================
312 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
313 converts it into a stem and a filespec
314 ========================================================================= */
315 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
316 WCHAR *spec, DWORD flags)
318 WCHAR actualsource[MAX_PATH];
319 WCHAR *starPos;
320 WCHAR *questPos;
321 DWORD attribs;
324 * Validate the source, expanding to full path ensuring it exists
326 if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
327 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
328 return RC_INITERROR;
331 /* If full names required, convert to using the full path */
332 if (flags & OPT_FULL) {
333 lstrcpyW(suppliedsource, actualsource);
337 * Work out the stem of the source
340 /* If a directory is supplied, use that as-is (either fully or
341 partially qualified)
342 If a filename is supplied + a directory or drive path, use that
343 as-is
344 Otherwise
345 If no directory or path specified, add eg. C:
346 stem is Drive/Directory is bit up to last \ (or first :)
347 spec is bit after that */
349 starPos = wcschr(suppliedsource, '*');
350 questPos = wcschr(suppliedsource, '?');
351 if (starPos || questPos) {
352 attribs = 0x00; /* Ensures skips invalid or directory check below */
353 } else {
354 attribs = GetFileAttributes(actualsource);
357 if (attribs == INVALID_FILE_ATTRIBUTES) {
358 XCOPY_FailMessage(GetLastError());
359 return RC_INITERROR;
361 /* Directory:
362 stem should be exactly as supplied plus a '\', unless it was
363 eg. C: in which case no slash required */
364 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
365 WCHAR lastChar;
367 WINE_TRACE("Directory supplied\n");
368 lstrcpyW(stem, suppliedsource);
369 lastChar = stem[lstrlenW(stem)-1];
370 if (lastChar != '\\' && lastChar != ':') {
371 lstrcatW(stem, wchr_slash);
373 lstrcpyW(spec, wchr_star);
375 /* File or wildcard search:
376 stem should be:
377 Up to and including last slash if directory path supplied
378 If c:filename supplied, just the c:
379 Otherwise stem should be the current drive letter + ':' */
380 } else {
381 WCHAR *lastDir;
383 WINE_TRACE("Filename supplied\n");
384 lastDir = wcsrchr(suppliedsource, '\\');
386 if (lastDir) {
387 lstrcpyW(stem, suppliedsource);
388 stem[(lastDir-suppliedsource) + 1] = 0x00;
389 lstrcpyW(spec, (lastDir+1));
390 } else if (suppliedsource[1] == ':') {
391 lstrcpyW(stem, suppliedsource);
392 stem[2] = 0x00;
393 lstrcpyW(spec, suppliedsource+2);
394 } else {
395 WCHAR curdir[MAXSTRING];
396 GetCurrentDirectory (sizeof(curdir), curdir);
397 stem[0] = curdir[0];
398 stem[1] = curdir[1];
399 stem[2] = 0x00;
400 lstrcpyW(spec, suppliedsource);
404 return RC_OK;
407 /* =========================================================================
408 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
409 converts it into a stem
410 ========================================================================= */
411 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
412 WCHAR *srcspec, DWORD flags)
414 WCHAR actualdestination[MAX_PATH];
415 DWORD attribs;
416 BOOL isDir = FALSE;
419 * Validate the source, expanding to full path ensuring it exists
421 if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
422 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
423 return RC_INITERROR;
426 /* Destination is either a directory or a file */
427 attribs = GetFileAttributes(actualdestination);
429 if (attribs == INVALID_FILE_ATTRIBUTES) {
431 /* If /I supplied and wildcard copy, assume directory */
432 if (flags & OPT_ASSUMEDIR &&
433 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
435 isDir = TRUE;
437 } else {
438 DWORD count;
439 char answer[10] = "";
440 WCHAR fileChar[2];
441 WCHAR dirChar[2];
443 /* Read the F and D characters from the resource file */
444 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
445 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
447 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
448 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
450 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
451 WINE_TRACE("User answer %c\n", answer[0]);
453 answer[0] = toupper(answer[0]);
456 if (answer[0] == dirChar[0]) {
457 isDir = TRUE;
458 } else {
459 isDir = FALSE;
462 } else {
463 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
466 if (isDir) {
467 lstrcpyW(stem, actualdestination);
468 *spec = 0x00;
470 /* Ensure ends with a '\' */
471 if (stem[lstrlenW(stem)-1] != '\\') {
472 lstrcatW(stem, wchr_slash);
475 } else {
476 WCHAR drive[MAX_PATH];
477 WCHAR dir[MAX_PATH];
478 WCHAR fname[MAX_PATH];
479 WCHAR ext[MAX_PATH];
480 _wsplitpath(actualdestination, drive, dir, fname, ext);
481 lstrcpyW(stem, drive);
482 lstrcatW(stem, dir);
483 lstrcpyW(spec, fname);
484 lstrcatW(spec, ext);
486 return RC_OK;
489 /* =========================================================================
490 XCOPY_DoCopy - Recursive function to copy files based on input parms
491 of a stem and a spec
493 This works by using FindFirstFile supplying the source stem and spec.
494 If results are found, any non-directory ones are processed
495 Then, if /S or /E is supplied, another search is made just for
496 directories, and this function is called again for that directory
498 ========================================================================= */
499 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
500 WCHAR *deststem, WCHAR *destspec,
501 DWORD flags)
503 WIN32_FIND_DATA *finddata;
504 HANDLE h;
505 BOOL findres = TRUE;
506 WCHAR *inputpath, *outputpath;
507 BOOL copiedFile = FALSE;
508 DWORD destAttribs, srcAttribs;
509 BOOL skipFile;
510 int ret = 0;
512 /* Allocate some working memory on heap to minimize footprint */
513 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
514 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
515 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
517 /* Build the search info into a single parm */
518 lstrcpyW(inputpath, srcstem);
519 lstrcatW(inputpath, srcspec);
521 /* Search 1 - Look for matching files */
522 h = FindFirstFile(inputpath, finddata);
523 while (h != INVALID_HANDLE_VALUE && findres) {
525 skipFile = FALSE;
527 /* Ignore . and .. */
528 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
529 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
530 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
532 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
533 } else {
535 /* Get the filename information */
536 lstrcpyW(copyFrom, srcstem);
537 if (flags & OPT_SHORTNAME) {
538 lstrcatW(copyFrom, finddata->cAlternateFileName);
539 } else {
540 lstrcatW(copyFrom, finddata->cFileName);
543 lstrcpyW(copyTo, deststem);
544 if (*destspec == 0x00) {
545 if (flags & OPT_SHORTNAME) {
546 lstrcatW(copyTo, finddata->cAlternateFileName);
547 } else {
548 lstrcatW(copyTo, finddata->cFileName);
550 } else {
551 lstrcatW(copyTo, destspec);
554 /* Do the copy */
555 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
556 wine_dbgstr_w(copyTo));
557 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
559 /* See if allowed to copy it */
560 srcAttribs = GetFileAttributesW(copyFrom);
561 WINE_TRACE("Source attribs: %d\n", srcAttribs);
563 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
564 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
566 if (!(flags & OPT_COPYHIDSYS)) {
567 skipFile = TRUE;
571 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
572 (flags & OPT_ARCHIVEONLY)) {
573 skipFile = TRUE;
576 /* See if file exists */
577 destAttribs = GetFileAttributesW(copyTo);
578 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
580 /* Check date ranges if a destination file already exists */
581 if (!skipFile && (flags & OPT_DATERANGE) &&
582 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
583 WINE_TRACE("Skipping file as modified date too old\n");
584 skipFile = TRUE;
587 /* If just /D supplied, only overwrite if src newer than dest */
588 if (!skipFile && (flags & OPT_DATENEWER) &&
589 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
590 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
591 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
592 NULL);
593 if (h != INVALID_HANDLE_VALUE) {
594 FILETIME writeTime;
595 GetFileTime(h, NULL, NULL, &writeTime);
597 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
598 WINE_TRACE("Skipping file as dest newer or same date\n");
599 skipFile = TRUE;
601 CloseHandle(h);
605 /* See if exclude list provided. Note since filenames are case
606 insensitive, need to uppercase the filename before doing
607 strstr */
608 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
609 EXCLUDELIST *pos = excludeList;
610 WCHAR copyFromUpper[MAX_PATH];
612 /* Uppercase source filename */
613 lstrcpyW(copyFromUpper, copyFrom);
614 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
616 /* Loop through testing each exclude line */
617 while (pos) {
618 if (wcsstr(copyFromUpper, pos->name) != NULL) {
619 WINE_TRACE("Skipping file as matches exclude '%s'\n",
620 wine_dbgstr_w(pos->name));
621 skipFile = TRUE;
622 pos = NULL;
623 } else {
624 pos = pos->next;
629 /* Prompt each file if necessary */
630 if (!skipFile && (flags & OPT_SRCPROMPT)) {
631 DWORD count;
632 char answer[10];
633 BOOL answered = FALSE;
634 WCHAR yesChar[2];
635 WCHAR noChar[2];
637 /* Read the Y and N characters from the resource file */
638 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
639 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
641 while (!answered) {
642 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
643 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
644 &count, NULL);
646 answered = TRUE;
647 if (toupper(answer[0]) == noChar[0])
648 skipFile = TRUE;
649 else if (toupper(answer[0]) != yesChar[0])
650 answered = FALSE;
654 if (!skipFile &&
655 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
656 DWORD count;
657 char answer[10];
658 BOOL answered = FALSE;
659 WCHAR yesChar[2];
660 WCHAR allChar[2];
661 WCHAR noChar[2];
663 /* Read the A,Y and N characters from the resource file */
664 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
665 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
666 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
668 while (!answered) {
669 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
670 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
671 &count, NULL);
673 answered = TRUE;
674 if (toupper(answer[0]) == allChar[0])
675 flags |= OPT_NOPROMPT;
676 else if (toupper(answer[0]) == noChar[0])
677 skipFile = TRUE;
678 else if (toupper(answer[0]) != yesChar[0])
679 answered = FALSE;
683 /* See if it has to exist! */
684 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
685 skipFile = TRUE;
688 /* Output a status message */
689 if (!skipFile) {
690 if (flags & OPT_QUIET) {
691 /* Skip message */
692 } else if (flags & OPT_FULL) {
693 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
694 '%', 's', '\n', 0};
696 XCOPY_wprintf(infostr, copyFrom, copyTo);
697 } else {
698 const WCHAR infostr[] = {'%', 's', '\n', 0};
699 XCOPY_wprintf(infostr, copyFrom);
702 /* If allowing overwriting of read only files, remove any
703 write protection */
704 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
705 (flags & OPT_REPLACEREAD)) {
706 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
709 copiedFile = TRUE;
710 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
711 /* Skip copy */
712 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
714 DWORD error = GetLastError();
715 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
716 copyFrom, copyTo, error);
717 XCOPY_FailMessage(error);
719 if (flags & OPT_IGNOREERRORS) {
720 skipFile = TRUE;
721 } else {
722 ret = RC_WRITEERROR;
723 goto cleanup;
727 /* If /M supplied, remove the archive bit after successful copy */
728 if (!skipFile) {
729 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
730 (flags & OPT_REMOVEARCH)) {
731 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
733 filesCopied++;
738 /* Find next file */
739 findres = FindNextFile(h, finddata);
741 FindClose(h);
743 /* Search 2 - do subdirs */
744 if (flags & OPT_RECURSIVE) {
745 lstrcpyW(inputpath, srcstem);
746 lstrcatW(inputpath, wchr_star);
747 findres = TRUE;
748 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
750 h = FindFirstFile(inputpath, finddata);
751 while (h != INVALID_HANDLE_VALUE && findres) {
753 /* Only looking for dirs */
754 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
755 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
756 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
758 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
760 /* Make up recursive information */
761 lstrcpyW(inputpath, srcstem);
762 lstrcatW(inputpath, finddata->cFileName);
763 lstrcatW(inputpath, wchr_slash);
765 lstrcpyW(outputpath, deststem);
766 if (*destspec == 0x00) {
767 lstrcatW(outputpath, finddata->cFileName);
769 /* If /E is supplied, create the directory now */
770 if ((flags & OPT_EMPTYDIR) &&
771 !(flags & OPT_SIMULATE))
772 XCOPY_CreateDirectory(outputpath);
774 lstrcatW(outputpath, wchr_slash);
777 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
780 /* Find next one */
781 findres = FindNextFile(h, finddata);
785 cleanup:
787 /* free up memory */
788 HeapFree(GetProcessHeap(), 0, finddata);
789 HeapFree(GetProcessHeap(), 0, inputpath);
790 HeapFree(GetProcessHeap(), 0, outputpath);
792 return ret;
795 /* =========================================================================
796 * Routine copied from cmd.exe md command -
797 * This works recursively. 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)
802 int len;
803 WCHAR *new_path;
804 BOOL ret = TRUE;
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))
814 WCHAR *slash;
815 DWORD last_error = GetLastError();
816 if (last_error == ERROR_ALREADY_EXISTS)
817 break;
819 if (last_error != ERROR_PATH_NOT_FOUND)
821 ret = FALSE;
822 break;
825 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
827 ret = FALSE;
828 break;
831 len = slash - new_path;
832 new_path[len] = 0;
833 if (!XCOPY_CreateDirectory(new_path))
835 ret = FALSE;
836 break;
838 new_path[len] = '\\';
840 HeapFree(GetProcessHeap(),0,new_path);
841 return ret;
844 /* =========================================================================
845 * Process the /EXCLUDE: file list, building up a list of substrings to
846 * avoid copying
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));
854 excludeList = NULL;
856 while (*parms && *parms != ' ' && *parms != '/') {
858 /* If found '+' then process the file found so far */
859 if (*parms == '+') {
860 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
861 return TRUE;
863 filenameStart = parms+1;
865 parms++;
868 if (filenameStart != parms) {
869 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
870 return TRUE;
874 return FALSE;
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];
886 FILE *inFile = NULL;
887 const WCHAR readTextMode[] = {'r', 't', 0};
889 /* Null terminate the filename (temporarily updates the filename hence
890 parms not const) */
891 *endOfName = 0x00;
893 /* Open the file */
894 inFile = _wfopen(filename, readTextMode);
895 if (inFile == NULL) {
896 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
897 *endOfName = endChar;
898 return TRUE;
901 /* Process line by line */
902 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
903 EXCLUDELIST *thisEntry;
904 int length = lstrlenW(buffer);
906 /* Strip CRLF */
907 buffer[length-1] = 0x00;
909 /* If more than CRLF */
910 if (length > 1) {
911 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
912 thisEntry->next = excludeList;
913 excludeList = thisEntry;
914 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
915 (length * sizeof(WCHAR))+1);
916 lstrcpyW(thisEntry->name, buffer);
917 CharUpperBuff(thisEntry->name, length);
918 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
922 /* See if EOF or error occurred */
923 if (!feof(inFile)) {
924 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
925 *endOfName = endChar;
926 return TRUE;
929 /* Revert the input string to original form, and cleanup + return */
930 *endOfName = endChar;
931 fclose(inFile);
932 return FALSE;
935 /* =========================================================================
936 * Load a string from the resource file, handling any error
937 * Returns string retrieved from resource file
938 * ========================================================================= */
939 static WCHAR *XCOPY_LoadMessage(UINT id) {
940 static WCHAR msg[MAXSTRING];
941 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
943 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
944 WINE_FIXME("LoadString failed with %d\n", GetLastError());
945 lstrcpyW(msg, failedMsg);
947 return msg;
950 /* =========================================================================
951 * Load a string for a system error and writes it to the screen
952 * Returns string retrieved from resource file
953 * ========================================================================= */
954 static void XCOPY_FailMessage(DWORD err) {
955 LPWSTR lpMsgBuf;
956 int status;
958 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
959 FORMAT_MESSAGE_FROM_SYSTEM,
960 NULL, err, 0,
961 (LPTSTR) &lpMsgBuf, 0, NULL);
962 if (!status) {
963 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
964 err, GetLastError());
965 } else {
966 const WCHAR infostr[] = {'%', 's', '\n', 0};
967 XCOPY_wprintf(infostr, lpMsgBuf);
968 LocalFree ((HLOCAL)lpMsgBuf);
972 /* =========================================================================
973 * Output a formatted unicode string. Ideally this will go to the console
974 * and hence required WriteConsoleW to output it, however if file i/o is
975 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
976 * ========================================================================= */
977 int XCOPY_wprintf(const WCHAR *format, ...) {
979 static WCHAR *output_bufW = NULL;
980 static char *output_bufA = NULL;
981 static BOOL toConsole = TRUE;
982 static BOOL traceOutput = FALSE;
983 #define MAX_WRITECONSOLE_SIZE 65535
985 va_list parms;
986 DWORD len, nOut;
987 DWORD res = 0;
990 * Allocate buffer to use when writing to console
991 * Note: Not freed - memory will be allocated once and released when
992 * xcopy ends
995 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
996 MAX_WRITECONSOLE_SIZE);
997 if (!output_bufW) {
998 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
999 return 0;
1002 /* Use wvsprintf to store output into unicode buffer */
1003 va_start(parms, format);
1004 len = vswprintf(output_bufW, format, parms);
1005 va_end(parms);
1007 /* Try to write as unicode all the time we think its a console */
1008 if (toConsole) {
1009 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1010 output_bufW, len, &nOut, NULL);
1013 /* If writing to console has failed (ever) we assume its file
1014 i/o so convert to OEM codepage and output */
1015 if (!res) {
1016 BOOL usedDefaultChar = FALSE;
1017 DWORD convertedChars;
1019 toConsole = FALSE;
1022 * Allocate buffer to use when writing to file. Not freed, as above
1024 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1025 MAX_WRITECONSOLE_SIZE);
1026 if (!output_bufA) {
1027 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1028 return 0;
1031 /* Convert to OEM, then output */
1032 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1033 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1034 "?", &usedDefaultChar);
1035 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1036 &nOut, FALSE);
1039 /* Trace whether screen or console */
1040 if (!traceOutput) {
1041 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1042 traceOutput = TRUE;
1044 return nOut;