rsaenh/tests: Fix some test failures on NT4 and lower.
[wine/hacks.git] / programs / xcopy / xcopy.c
blobf996144d543f4c5ddb3d7fd1aec58503c2a5bbe6
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 XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
266 /* Extract required information from destination specification */
267 XCOPY_ProcessDestParm(supplieddestination, destinationstem,
268 destinationspec, sourcespec, flags);
270 /* Trace out the resulting information */
271 WINE_TRACE("Resolved parameters:\n");
272 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
273 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
274 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
275 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
277 /* Pause if necessary */
278 if (flags & OPT_PAUSE) {
279 DWORD count;
280 char pausestr[10];
282 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
283 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
284 &count, NULL);
287 /* Now do the hard work... */
288 rc = XCOPY_DoCopy(sourcestem, sourcespec,
289 destinationstem, destinationspec,
290 flags);
292 /* Clear up exclude list allocated memory */
293 while (excludeList) {
294 EXCLUDELIST *pos = excludeList;
295 excludeList = excludeList -> next;
296 HeapFree(GetProcessHeap(), 0, pos->name);
297 HeapFree(GetProcessHeap(), 0, pos);
300 /* Finished - print trailer and exit */
301 if (flags & OPT_SIMULATE) {
302 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
303 } else if (!(flags & OPT_NOCOPY)) {
304 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
306 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
307 return rc;
312 /* =========================================================================
313 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
314 converts it into a stem and a filespec
315 ========================================================================= */
316 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
317 WCHAR *spec, DWORD flags)
319 WCHAR actualsource[MAX_PATH];
320 WCHAR *starPos;
321 WCHAR *questPos;
322 DWORD attribs;
325 * Validate the source, expanding to full path ensuring it exists
327 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
328 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
329 return RC_INITERROR;
332 /* If full names required, convert to using the full path */
333 if (flags & OPT_FULL) {
334 lstrcpyW(suppliedsource, actualsource);
338 * Work out the stem of the source
341 /* If a directory is supplied, use that as-is (either fully or
342 partially qualified)
343 If a filename is supplied + a directory or drive path, use that
344 as-is
345 Otherwise
346 If no directory or path specified, add eg. C:
347 stem is Drive/Directory is bit up to last \ (or first :)
348 spec is bit after that */
350 starPos = wcschr(suppliedsource, '*');
351 questPos = wcschr(suppliedsource, '?');
352 if (starPos || questPos) {
353 attribs = 0x00; /* Ensures skips invalid or directory check below */
354 } else {
355 attribs = GetFileAttributesW(actualsource);
358 if (attribs == INVALID_FILE_ATTRIBUTES) {
359 XCOPY_FailMessage(GetLastError());
360 return RC_INITERROR;
362 /* Directory:
363 stem should be exactly as supplied plus a '\', unless it was
364 eg. C: in which case no slash required */
365 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
366 WCHAR lastChar;
368 WINE_TRACE("Directory supplied\n");
369 lstrcpyW(stem, suppliedsource);
370 lastChar = stem[lstrlenW(stem)-1];
371 if (lastChar != '\\' && lastChar != ':') {
372 lstrcatW(stem, wchr_slash);
374 lstrcpyW(spec, wchr_star);
376 /* File or wildcard search:
377 stem should be:
378 Up to and including last slash if directory path supplied
379 If c:filename supplied, just the c:
380 Otherwise stem should be the current drive letter + ':' */
381 } else {
382 WCHAR *lastDir;
384 WINE_TRACE("Filename supplied\n");
385 lastDir = wcsrchr(suppliedsource, '\\');
387 if (lastDir) {
388 lstrcpyW(stem, suppliedsource);
389 stem[(lastDir-suppliedsource) + 1] = 0x00;
390 lstrcpyW(spec, (lastDir+1));
391 } else if (suppliedsource[1] == ':') {
392 lstrcpyW(stem, suppliedsource);
393 stem[2] = 0x00;
394 lstrcpyW(spec, suppliedsource+2);
395 } else {
396 WCHAR curdir[MAXSTRING];
397 GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
398 stem[0] = curdir[0];
399 stem[1] = curdir[1];
400 stem[2] = 0x00;
401 lstrcpyW(spec, suppliedsource);
405 return RC_OK;
408 /* =========================================================================
409 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
410 converts it into a stem
411 ========================================================================= */
412 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
413 WCHAR *srcspec, DWORD flags)
415 WCHAR actualdestination[MAX_PATH];
416 DWORD attribs;
417 BOOL isDir = FALSE;
420 * Validate the source, expanding to full path ensuring it exists
422 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
423 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
424 return RC_INITERROR;
427 /* Destination is either a directory or a file */
428 attribs = GetFileAttributesW(actualdestination);
430 if (attribs == INVALID_FILE_ATTRIBUTES) {
432 /* If /I supplied and wildcard copy, assume directory */
433 if (flags & OPT_ASSUMEDIR &&
434 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
436 isDir = TRUE;
438 } else {
439 DWORD count;
440 char answer[10] = "";
441 WCHAR fileChar[2];
442 WCHAR dirChar[2];
444 /* Read the F and D characters from the resource file */
445 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
446 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
448 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
449 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
451 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
452 WINE_TRACE("User answer %c\n", answer[0]);
454 answer[0] = toupper(answer[0]);
457 if (answer[0] == dirChar[0]) {
458 isDir = TRUE;
459 } else {
460 isDir = FALSE;
463 } else {
464 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
467 if (isDir) {
468 lstrcpyW(stem, actualdestination);
469 *spec = 0x00;
471 /* Ensure ends with a '\' */
472 if (stem[lstrlenW(stem)-1] != '\\') {
473 lstrcatW(stem, wchr_slash);
476 } else {
477 WCHAR drive[MAX_PATH];
478 WCHAR dir[MAX_PATH];
479 WCHAR fname[MAX_PATH];
480 WCHAR ext[MAX_PATH];
481 _wsplitpath(actualdestination, drive, dir, fname, ext);
482 lstrcpyW(stem, drive);
483 lstrcatW(stem, dir);
484 lstrcpyW(spec, fname);
485 lstrcatW(spec, ext);
487 return RC_OK;
490 /* =========================================================================
491 XCOPY_DoCopy - Recursive function to copy files based on input parms
492 of a stem and a spec
494 This works by using FindFirstFile supplying the source stem and spec.
495 If results are found, any non-directory ones are processed
496 Then, if /S or /E is supplied, another search is made just for
497 directories, and this function is called again for that directory
499 ========================================================================= */
500 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
501 WCHAR *deststem, WCHAR *destspec,
502 DWORD flags)
504 WIN32_FIND_DATAW *finddata;
505 HANDLE h;
506 BOOL findres = TRUE;
507 WCHAR *inputpath, *outputpath;
508 BOOL copiedFile = FALSE;
509 DWORD destAttribs, srcAttribs;
510 BOOL skipFile;
511 int ret = 0;
513 /* Allocate some working memory on heap to minimize footprint */
514 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
515 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
516 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
518 /* Build the search info into a single parm */
519 lstrcpyW(inputpath, srcstem);
520 lstrcatW(inputpath, srcspec);
522 /* Search 1 - Look for matching files */
523 h = FindFirstFileW(inputpath, finddata);
524 while (h != INVALID_HANDLE_VALUE && findres) {
526 skipFile = FALSE;
528 /* Ignore . and .. */
529 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
530 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
531 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
533 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
534 } else {
536 /* Get the filename information */
537 lstrcpyW(copyFrom, srcstem);
538 if (flags & OPT_SHORTNAME) {
539 lstrcatW(copyFrom, finddata->cAlternateFileName);
540 } else {
541 lstrcatW(copyFrom, finddata->cFileName);
544 lstrcpyW(copyTo, deststem);
545 if (*destspec == 0x00) {
546 if (flags & OPT_SHORTNAME) {
547 lstrcatW(copyTo, finddata->cAlternateFileName);
548 } else {
549 lstrcatW(copyTo, finddata->cFileName);
551 } else {
552 lstrcatW(copyTo, destspec);
555 /* Do the copy */
556 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
557 wine_dbgstr_w(copyTo));
558 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
560 /* See if allowed to copy it */
561 srcAttribs = GetFileAttributesW(copyFrom);
562 WINE_TRACE("Source attribs: %d\n", srcAttribs);
564 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
565 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
567 if (!(flags & OPT_COPYHIDSYS)) {
568 skipFile = TRUE;
572 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
573 (flags & OPT_ARCHIVEONLY)) {
574 skipFile = TRUE;
577 /* See if file exists */
578 destAttribs = GetFileAttributesW(copyTo);
579 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
581 /* Check date ranges if a destination file already exists */
582 if (!skipFile && (flags & OPT_DATERANGE) &&
583 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
584 WINE_TRACE("Skipping file as modified date too old\n");
585 skipFile = TRUE;
588 /* If just /D supplied, only overwrite if src newer than dest */
589 if (!skipFile && (flags & OPT_DATENEWER) &&
590 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
591 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
592 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
593 NULL);
594 if (h != INVALID_HANDLE_VALUE) {
595 FILETIME writeTime;
596 GetFileTime(h, NULL, NULL, &writeTime);
598 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
599 WINE_TRACE("Skipping file as dest newer or same date\n");
600 skipFile = TRUE;
602 CloseHandle(h);
606 /* See if exclude list provided. Note since filenames are case
607 insensitive, need to uppercase the filename before doing
608 strstr */
609 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
610 EXCLUDELIST *pos = excludeList;
611 WCHAR copyFromUpper[MAX_PATH];
613 /* Uppercase source filename */
614 lstrcpyW(copyFromUpper, copyFrom);
615 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
617 /* Loop through testing each exclude line */
618 while (pos) {
619 if (wcsstr(copyFromUpper, pos->name) != NULL) {
620 WINE_TRACE("Skipping file as matches exclude '%s'\n",
621 wine_dbgstr_w(pos->name));
622 skipFile = TRUE;
623 pos = NULL;
624 } else {
625 pos = pos->next;
630 /* Prompt each file if necessary */
631 if (!skipFile && (flags & OPT_SRCPROMPT)) {
632 DWORD count;
633 char answer[10];
634 BOOL answered = FALSE;
635 WCHAR yesChar[2];
636 WCHAR noChar[2];
638 /* Read the Y and N characters from the resource file */
639 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
640 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
642 while (!answered) {
643 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
644 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
645 &count, NULL);
647 answered = TRUE;
648 if (toupper(answer[0]) == noChar[0])
649 skipFile = TRUE;
650 else if (toupper(answer[0]) != yesChar[0])
651 answered = FALSE;
655 if (!skipFile &&
656 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
657 DWORD count;
658 char answer[10];
659 BOOL answered = FALSE;
660 WCHAR yesChar[2];
661 WCHAR allChar[2];
662 WCHAR noChar[2];
664 /* Read the A,Y and N characters from the resource file */
665 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
666 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
667 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
669 while (!answered) {
670 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
671 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
672 &count, NULL);
674 answered = TRUE;
675 if (toupper(answer[0]) == allChar[0])
676 flags |= OPT_NOPROMPT;
677 else if (toupper(answer[0]) == noChar[0])
678 skipFile = TRUE;
679 else if (toupper(answer[0]) != yesChar[0])
680 answered = FALSE;
684 /* See if it has to exist! */
685 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
686 skipFile = TRUE;
689 /* Output a status message */
690 if (!skipFile) {
691 if (flags & OPT_QUIET) {
692 /* Skip message */
693 } else if (flags & OPT_FULL) {
694 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
695 '%', 's', '\n', 0};
697 XCOPY_wprintf(infostr, copyFrom, copyTo);
698 } else {
699 const WCHAR infostr[] = {'%', 's', '\n', 0};
700 XCOPY_wprintf(infostr, copyFrom);
703 /* If allowing overwriting of read only files, remove any
704 write protection */
705 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
706 (flags & OPT_REPLACEREAD)) {
707 SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
710 copiedFile = TRUE;
711 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
712 /* Skip copy */
713 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
715 DWORD error = GetLastError();
716 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
717 copyFrom, copyTo, error);
718 XCOPY_FailMessage(error);
720 if (flags & OPT_IGNOREERRORS) {
721 skipFile = TRUE;
722 } else {
723 ret = RC_WRITEERROR;
724 goto cleanup;
728 /* If /M supplied, remove the archive bit after successful copy */
729 if (!skipFile) {
730 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
731 (flags & OPT_REMOVEARCH)) {
732 SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
734 filesCopied++;
739 /* Find next file */
740 findres = FindNextFileW(h, finddata);
742 FindClose(h);
744 /* Search 2 - do subdirs */
745 if (flags & OPT_RECURSIVE) {
746 lstrcpyW(inputpath, srcstem);
747 lstrcatW(inputpath, wchr_star);
748 findres = TRUE;
749 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
751 h = FindFirstFileW(inputpath, finddata);
752 while (h != INVALID_HANDLE_VALUE && findres) {
754 /* Only looking for dirs */
755 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
756 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
757 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
759 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
761 /* Make up recursive information */
762 lstrcpyW(inputpath, srcstem);
763 lstrcatW(inputpath, finddata->cFileName);
764 lstrcatW(inputpath, wchr_slash);
766 lstrcpyW(outputpath, deststem);
767 if (*destspec == 0x00) {
768 lstrcatW(outputpath, finddata->cFileName);
770 /* If /E is supplied, create the directory now */
771 if ((flags & OPT_EMPTYDIR) &&
772 !(flags & OPT_SIMULATE))
773 XCOPY_CreateDirectory(outputpath);
775 lstrcatW(outputpath, wchr_slash);
778 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
781 /* Find next one */
782 findres = FindNextFileW(h, finddata);
786 cleanup:
788 /* free up memory */
789 HeapFree(GetProcessHeap(), 0, finddata);
790 HeapFree(GetProcessHeap(), 0, inputpath);
791 HeapFree(GetProcessHeap(), 0, outputpath);
793 return ret;
796 /* =========================================================================
797 * Routine copied from cmd.exe md command -
798 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
799 * dir2 if they do not already exist.
800 * ========================================================================= */
801 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
803 int len;
804 WCHAR *new_path;
805 BOOL ret = TRUE;
807 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
808 lstrcpyW(new_path,path);
810 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
811 new_path[len - 1] = 0;
813 while (!CreateDirectoryW(new_path,NULL))
815 WCHAR *slash;
816 DWORD last_error = GetLastError();
817 if (last_error == ERROR_ALREADY_EXISTS)
818 break;
820 if (last_error != ERROR_PATH_NOT_FOUND)
822 ret = FALSE;
823 break;
826 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
828 ret = FALSE;
829 break;
832 len = slash - new_path;
833 new_path[len] = 0;
834 if (!XCOPY_CreateDirectory(new_path))
836 ret = FALSE;
837 break;
839 new_path[len] = '\\';
841 HeapFree(GetProcessHeap(),0,new_path);
842 return ret;
845 /* =========================================================================
846 * Process the /EXCLUDE: file list, building up a list of substrings to
847 * avoid copying
848 * Returns TRUE on any failure
849 * ========================================================================= */
850 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
852 WCHAR *filenameStart = parms;
854 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
855 excludeList = NULL;
857 while (*parms && *parms != ' ' && *parms != '/') {
859 /* If found '+' then process the file found so far */
860 if (*parms == '+') {
861 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
862 return TRUE;
864 filenameStart = parms+1;
866 parms++;
869 if (filenameStart != parms) {
870 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
871 return TRUE;
875 return FALSE;
878 /* =========================================================================
879 * Process a single file from the /EXCLUDE: file list, building up a list
880 * of substrings to avoid copying
881 * Returns TRUE on any failure
882 * ========================================================================= */
883 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
885 WCHAR endChar = *endOfName;
886 WCHAR buffer[MAXSTRING];
887 FILE *inFile = NULL;
888 const WCHAR readTextMode[] = {'r', 't', 0};
890 /* Null terminate the filename (temporarily updates the filename hence
891 parms not const) */
892 *endOfName = 0x00;
894 /* Open the file */
895 inFile = _wfopen(filename, readTextMode);
896 if (inFile == NULL) {
897 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
898 *endOfName = endChar;
899 return TRUE;
902 /* Process line by line */
903 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
904 EXCLUDELIST *thisEntry;
905 int length = lstrlenW(buffer);
907 /* Strip CRLF */
908 buffer[length-1] = 0x00;
910 /* If more than CRLF */
911 if (length > 1) {
912 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
913 thisEntry->next = excludeList;
914 excludeList = thisEntry;
915 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
916 (length * sizeof(WCHAR))+1);
917 lstrcpyW(thisEntry->name, buffer);
918 CharUpperBuffW(thisEntry->name, length);
919 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
923 /* See if EOF or error occurred */
924 if (!feof(inFile)) {
925 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
926 *endOfName = endChar;
927 return TRUE;
930 /* Revert the input string to original form, and cleanup + return */
931 *endOfName = endChar;
932 fclose(inFile);
933 return FALSE;
936 /* =========================================================================
937 * Load a string from the resource file, handling any error
938 * Returns string retrieved from resource file
939 * ========================================================================= */
940 static WCHAR *XCOPY_LoadMessage(UINT id) {
941 static WCHAR msg[MAXSTRING];
942 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
944 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
945 WINE_FIXME("LoadString failed with %d\n", GetLastError());
946 lstrcpyW(msg, failedMsg);
948 return msg;
951 /* =========================================================================
952 * Load a string for a system error and writes it to the screen
953 * Returns string retrieved from resource file
954 * ========================================================================= */
955 static void XCOPY_FailMessage(DWORD err) {
956 LPWSTR lpMsgBuf;
957 int status;
959 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
960 FORMAT_MESSAGE_FROM_SYSTEM,
961 NULL, err, 0,
962 (LPWSTR) &lpMsgBuf, 0, NULL);
963 if (!status) {
964 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
965 err, GetLastError());
966 } else {
967 const WCHAR infostr[] = {'%', 's', '\n', 0};
968 XCOPY_wprintf(infostr, lpMsgBuf);
969 LocalFree ((HLOCAL)lpMsgBuf);
973 /* =========================================================================
974 * Output a formatted unicode string. Ideally this will go to the console
975 * and hence required WriteConsoleW to output it, however if file i/o is
976 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
977 * ========================================================================= */
978 int XCOPY_wprintf(const WCHAR *format, ...) {
980 static WCHAR *output_bufW = NULL;
981 static char *output_bufA = NULL;
982 static BOOL toConsole = TRUE;
983 static BOOL traceOutput = FALSE;
984 #define MAX_WRITECONSOLE_SIZE 65535
986 va_list parms;
987 DWORD nOut;
988 int len;
989 DWORD res = 0;
992 * Allocate buffer to use when writing to console
993 * Note: Not freed - memory will be allocated once and released when
994 * xcopy ends
997 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
998 MAX_WRITECONSOLE_SIZE);
999 if (!output_bufW) {
1000 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1001 return 0;
1004 va_start(parms, format);
1005 len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
1006 va_end(parms);
1007 if (len < 0) {
1008 WINE_FIXME("String too long.\n");
1009 return 0;
1012 /* Try to write as unicode all the time we think its a console */
1013 if (toConsole) {
1014 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1015 output_bufW, len, &nOut, NULL);
1018 /* If writing to console has failed (ever) we assume its file
1019 i/o so convert to OEM codepage and output */
1020 if (!res) {
1021 BOOL usedDefaultChar = FALSE;
1022 DWORD convertedChars;
1024 toConsole = FALSE;
1027 * Allocate buffer to use when writing to file. Not freed, as above
1029 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1030 MAX_WRITECONSOLE_SIZE);
1031 if (!output_bufA) {
1032 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1033 return 0;
1036 /* Convert to OEM, then output */
1037 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1038 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1039 "?", &usedDefaultChar);
1040 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1041 &nOut, FALSE);
1044 /* Trace whether screen or console */
1045 if (!traceOutput) {
1046 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1047 traceOutput = TRUE;
1049 return nOut;