wintrust: Implement SPC PE image decoding.
[wine/testsucceed.git] / programs / xcopy / xcopy.c
bloba88a0ad2b1cecd83390793dce66c598acc2b5877
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;
511 /* Allocate some working memory on heap to minimize footprint */
512 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
513 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
514 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
516 /* Build the search info into a single parm */
517 lstrcpyW(inputpath, srcstem);
518 lstrcatW(inputpath, srcspec);
520 /* Search 1 - Look for matching files */
521 h = FindFirstFile(inputpath, finddata);
522 while (h != INVALID_HANDLE_VALUE && findres) {
524 skipFile = FALSE;
526 /* Ignore . and .. */
527 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
528 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
529 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
531 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
532 } else {
534 /* Get the filename information */
535 lstrcpyW(copyFrom, srcstem);
536 if (flags & OPT_SHORTNAME) {
537 lstrcatW(copyFrom, finddata->cAlternateFileName);
538 } else {
539 lstrcatW(copyFrom, finddata->cFileName);
542 lstrcpyW(copyTo, deststem);
543 if (*destspec == 0x00) {
544 if (flags & OPT_SHORTNAME) {
545 lstrcatW(copyTo, finddata->cAlternateFileName);
546 } else {
547 lstrcatW(copyTo, finddata->cFileName);
549 } else {
550 lstrcatW(copyTo, destspec);
553 /* Do the copy */
554 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
555 wine_dbgstr_w(copyTo));
556 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
558 /* See if allowed to copy it */
559 srcAttribs = GetFileAttributesW(copyFrom);
560 WINE_TRACE("Source attribs: %d\n", srcAttribs);
562 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
563 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
565 if (!(flags & OPT_COPYHIDSYS)) {
566 skipFile = TRUE;
570 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
571 (flags & OPT_ARCHIVEONLY)) {
572 skipFile = TRUE;
575 /* See if file exists */
576 destAttribs = GetFileAttributesW(copyTo);
577 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
579 /* Check date ranges if a destination file already exists */
580 if (!skipFile && (flags & OPT_DATERANGE) &&
581 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
582 WINE_TRACE("Skipping file as modified date too old\n");
583 skipFile = TRUE;
586 /* If just /D supplied, only overwrite if src newer than dest */
587 if (!skipFile && (flags & OPT_DATENEWER) &&
588 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
589 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
590 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
591 NULL);
592 if (h != INVALID_HANDLE_VALUE) {
593 FILETIME writeTime;
594 GetFileTime(h, NULL, NULL, &writeTime);
596 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
597 WINE_TRACE("Skipping file as dest newer or same date\n");
598 skipFile = TRUE;
600 CloseHandle(h);
604 /* See if exclude list provided. Note since filenames are case
605 insensitive, need to uppercase the filename before doing
606 strstr */
607 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
608 EXCLUDELIST *pos = excludeList;
609 WCHAR copyFromUpper[MAX_PATH];
611 /* Uppercase source filename */
612 lstrcpyW(copyFromUpper, copyFrom);
613 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
615 /* Loop through testing each exclude line */
616 while (pos) {
617 if (wcsstr(copyFromUpper, pos->name) != NULL) {
618 WINE_TRACE("Skipping file as matches exclude '%s'\n",
619 wine_dbgstr_w(pos->name));
620 skipFile = TRUE;
621 pos = NULL;
622 } else {
623 pos = pos->next;
628 /* Prompt each file if necessary */
629 if (!skipFile && (flags & OPT_SRCPROMPT)) {
630 DWORD count;
631 char answer[10];
632 BOOL answered = FALSE;
633 WCHAR yesChar[2];
634 WCHAR noChar[2];
636 /* Read the Y and N characters from the resource file */
637 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
638 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
640 while (!answered) {
641 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
642 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
643 &count, NULL);
645 answered = TRUE;
646 if (toupper(answer[0]) == noChar[0])
647 skipFile = TRUE;
648 else if (toupper(answer[0]) != yesChar[0])
649 answered = FALSE;
653 if (!skipFile &&
654 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
655 DWORD count;
656 char answer[10];
657 BOOL answered = FALSE;
658 WCHAR yesChar[2];
659 WCHAR allChar[2];
660 WCHAR noChar[2];
662 /* Read the A,Y and N characters from the resource file */
663 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
664 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
665 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
667 while (!answered) {
668 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
669 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
670 &count, NULL);
672 answered = TRUE;
673 if (toupper(answer[0]) == allChar[0])
674 flags |= OPT_NOPROMPT;
675 else if (toupper(answer[0]) == noChar[0])
676 skipFile = TRUE;
677 else if (toupper(answer[0]) != yesChar[0])
678 answered = FALSE;
682 /* See if it has to exist! */
683 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
684 skipFile = TRUE;
687 /* Output a status message */
688 if (!skipFile) {
689 if (flags & OPT_QUIET) {
690 /* Skip message */
691 } else if (flags & OPT_FULL) {
692 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
693 '%', 's', '\n', 0};
695 XCOPY_wprintf(infostr, copyFrom, copyTo);
696 } else {
697 const WCHAR infostr[] = {'%', 's', '\n', 0};
698 XCOPY_wprintf(infostr, copyFrom);
701 /* If allowing overwriting of read only files, remove any
702 write protection */
703 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
704 (flags & OPT_REPLACEREAD)) {
705 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
708 copiedFile = TRUE;
709 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
710 /* Skip copy */
711 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
713 DWORD error = GetLastError();
714 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
715 copyFrom, copyTo, error);
716 XCOPY_FailMessage(error);
718 if (flags & OPT_IGNOREERRORS) {
719 skipFile = TRUE;
720 } else {
721 return RC_WRITEERROR;
725 /* If /M supplied, remove the archive bit after successful copy */
726 if (!skipFile) {
727 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
728 (flags & OPT_REMOVEARCH)) {
729 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
731 filesCopied++;
736 /* Find next file */
737 findres = FindNextFile(h, finddata);
739 FindClose(h);
741 /* Search 2 - do subdirs */
742 if (flags & OPT_RECURSIVE) {
743 lstrcpyW(inputpath, srcstem);
744 lstrcatW(inputpath, wchr_star);
745 findres = TRUE;
746 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
748 h = FindFirstFile(inputpath, finddata);
749 while (h != INVALID_HANDLE_VALUE && findres) {
751 /* Only looking for dirs */
752 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
753 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
754 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
756 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
758 /* Make up recursive information */
759 lstrcpyW(inputpath, srcstem);
760 lstrcatW(inputpath, finddata->cFileName);
761 lstrcatW(inputpath, wchr_slash);
763 lstrcpyW(outputpath, deststem);
764 if (*destspec == 0x00) {
765 lstrcatW(outputpath, finddata->cFileName);
767 /* If /E is supplied, create the directory now */
768 if ((flags & OPT_EMPTYDIR) &&
769 !(flags & OPT_SIMULATE))
770 XCOPY_CreateDirectory(outputpath);
772 lstrcatW(outputpath, wchr_slash);
775 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
778 /* Find next one */
779 findres = FindNextFile(h, finddata);
783 /* free up memory */
784 HeapFree(GetProcessHeap(), 0, finddata);
785 HeapFree(GetProcessHeap(), 0, inputpath);
786 HeapFree(GetProcessHeap(), 0, outputpath);
788 return 0;
791 /* =========================================================================
792 * Routine copied from cmd.exe md command -
793 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
794 * dir2 if they do not already exist.
795 * ========================================================================= */
796 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
798 int len;
799 WCHAR *new_path;
800 BOOL ret = TRUE;
802 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
803 lstrcpyW(new_path,path);
805 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
806 new_path[len - 1] = 0;
808 while (!CreateDirectory(new_path,NULL))
810 WCHAR *slash;
811 DWORD last_error = GetLastError();
812 if (last_error == ERROR_ALREADY_EXISTS)
813 break;
815 if (last_error != ERROR_PATH_NOT_FOUND)
817 ret = FALSE;
818 break;
821 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
823 ret = FALSE;
824 break;
827 len = slash - new_path;
828 new_path[len] = 0;
829 if (!XCOPY_CreateDirectory(new_path))
831 ret = FALSE;
832 break;
834 new_path[len] = '\\';
836 HeapFree(GetProcessHeap(),0,new_path);
837 return ret;
840 /* =========================================================================
841 * Process the /EXCLUDE: file list, building up a list of substrings to
842 * avoid copying
843 * Returns TRUE on any failure
844 * ========================================================================= */
845 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
847 WCHAR *filenameStart = parms;
849 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
850 excludeList = NULL;
852 while (*parms && *parms != ' ' && *parms != '/') {
854 /* If found '+' then process the file found so far */
855 if (*parms == '+') {
856 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
857 return TRUE;
859 filenameStart = parms+1;
861 parms++;
864 if (filenameStart != parms) {
865 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
866 return TRUE;
870 return FALSE;
873 /* =========================================================================
874 * Process a single file from the /EXCLUDE: file list, building up a list
875 * of substrings to avoid copying
876 * Returns TRUE on any failure
877 * ========================================================================= */
878 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
880 WCHAR endChar = *endOfName;
881 WCHAR buffer[MAXSTRING];
882 FILE *inFile = NULL;
883 const WCHAR readTextMode[] = {'r', 't', 0};
885 /* Null terminate the filename (temporarily updates the filename hence
886 parms not const) */
887 *endOfName = 0x00;
889 /* Open the file */
890 inFile = _wfopen(filename, readTextMode);
891 if (inFile == NULL) {
892 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
893 *endOfName = endChar;
894 return TRUE;
897 /* Process line by line */
898 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
899 EXCLUDELIST *thisEntry;
900 int length = lstrlenW(buffer);
902 /* Strip CRLF */
903 buffer[length-1] = 0x00;
905 /* If more than CRLF */
906 if (length > 1) {
907 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
908 thisEntry->next = excludeList;
909 excludeList = thisEntry;
910 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
911 (length * sizeof(WCHAR))+1);
912 lstrcpyW(thisEntry->name, buffer);
913 CharUpperBuff(thisEntry->name, length);
914 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
918 /* See if EOF or error occurred */
919 if (!feof(inFile)) {
920 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
921 *endOfName = endChar;
922 return TRUE;
925 /* Revert the input string to original form, and cleanup + return */
926 *endOfName = endChar;
927 fclose(inFile);
928 return FALSE;
931 /* =========================================================================
932 * Load a string from the resource file, handling any error
933 * Returns string retrieved from resource file
934 * ========================================================================= */
935 static WCHAR *XCOPY_LoadMessage(UINT id) {
936 static WCHAR msg[MAXSTRING];
937 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
939 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
940 WINE_FIXME("LoadString failed with %d\n", GetLastError());
941 lstrcpyW(msg, failedMsg);
943 return msg;
946 /* =========================================================================
947 * Load a string for a system error and writes it to the screen
948 * Returns string retrieved from resource file
949 * ========================================================================= */
950 static void XCOPY_FailMessage(DWORD err) {
951 LPWSTR lpMsgBuf;
952 int status;
954 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
955 FORMAT_MESSAGE_FROM_SYSTEM,
956 NULL, err, 0,
957 (LPTSTR) &lpMsgBuf, 0, NULL);
958 if (!status) {
959 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
960 err, GetLastError());
961 } else {
962 const WCHAR infostr[] = {'%', 's', '\n', 0};
963 XCOPY_wprintf(infostr, lpMsgBuf);
964 LocalFree ((HLOCAL)lpMsgBuf);
968 /* =========================================================================
969 * Output a formatted unicode string. Ideally this will go to the console
970 * and hence required WriteConsoleW to output it, however if file i/o is
971 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
972 * ========================================================================= */
973 int XCOPY_wprintf(const WCHAR *format, ...) {
975 static WCHAR *output_bufW = NULL;
976 static char *output_bufA = NULL;
977 static BOOL toConsole = TRUE;
978 static BOOL traceOutput = FALSE;
979 #define MAX_WRITECONSOLE_SIZE 65535
981 va_list parms;
982 DWORD len, nOut;
983 DWORD res = 0;
986 * Allocate buffer to use when writing to console
987 * Note: Not freed - memory will be allocated once and released when
988 * xcopy ends
991 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
992 MAX_WRITECONSOLE_SIZE);
993 if (!output_bufW) {
994 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
995 return 0;
998 /* Use wvsprintf to store output into unicode buffer */
999 va_start(parms, format);
1000 len = vswprintf(output_bufW, format, parms);
1001 va_end(parms);
1003 /* Try to write as unicode all the time we think its a console */
1004 if (toConsole) {
1005 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1006 output_bufW, len, &nOut, NULL);
1009 /* If writing to console has failed (ever) we assume its file
1010 i/o so convert to OEM codepage and output */
1011 if (!res) {
1012 BOOL usedDefaultChar = FALSE;
1013 DWORD convertedChars;
1015 toConsole = FALSE;
1018 * Allocate buffer to use when writing to file. Not freed, as above
1020 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1021 MAX_WRITECONSOLE_SIZE);
1022 if (!output_bufA) {
1023 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1024 return 0;
1027 /* Convert to OEM, then output */
1028 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1029 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1030 "?", &usedDefaultChar);
1031 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1032 &nOut, FALSE);
1035 /* Trace whether screen or console */
1036 if (!traceOutput) {
1037 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1038 traceOutput = TRUE;
1040 return nOut;