- Merge from trunk up to r45543
[reactos.git] / base / applications / cmdutils / xcopy / xcopy.c
1 /*
2 * XCOPY - Wine-compatible xcopy program
3 *
4 * Copyright (C) 2007 J. Edmeades
5 *
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.
10 *
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.
15 *
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
19 */
20
21 /*
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
30 */
31
32 /*
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
40 */
41
42
43 #include <stdio.h>
44 #include <windows.h>
45 #include <wine/debug.h>
46 #include <wine/unicode.h>
47 #include "xcopy.h"
48
49 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
50
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, ...);
65
66 /* Typedefs */
67 typedef struct _EXCLUDELIST
68 {
69 struct _EXCLUDELIST *next;
70 WCHAR *name;
71 } EXCLUDELIST;
72
73
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};
82
83 /* Constants (Mostly for widechars) */
84
85
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];
90
91
92 /* =========================================================================
93 main - Main entrypoint for the xcopy command
94
95 Processes the args, and drives the actual copying
96 ========================================================================= */
97 int wmain (int argc, WCHAR *argvW[])
98 {
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};
112
113 /*
114 * Parse the command line
115 */
116
117 /* Confirm at least one parameter */
118 if (argc < 2) {
119 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
120 return RC_INITERROR;
121 }
122
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;
128 }
129 }
130
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;
137
138 /* Skip first arg, which is the program name */
139 argvW++;
140
141 while (argc > 1)
142 {
143 argc--;
144 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
145
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;
155 }
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;
179
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;
191
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));
198
199 /* Parse the arg : Month */
200 st.wMonth = _wtol(pos);
201 while (*pos && isdigit(*pos)) pos++;
202 if (*pos++ != '-') isError = TRUE;
203
204 /* Parse the arg : Day */
205 if (!isError) {
206 st.wDay = _wtol(pos);
207 while (*pos && isdigit(*pos)) pos++;
208 if (*pos++ != '-') isError = TRUE;
209 }
210
211 /* Parse the arg : Day */
212 if (!isError) {
213 st.wYear = _wtol(pos);
214 if (st.wYear < 100) st.wYear+=2000;
215 }
216
217 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
218 SYSTEMTIME st;
219 WCHAR datestring[32], timestring[32];
220
221 flags |= OPT_DATERANGE;
222
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));
229
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;
235 }
236 } else {
237 flags |= OPT_DATENEWER;
238 }
239 break;
240
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;
249 }
250 }
251 argvW++;
252 }
253
254 /* Default the destination if not supplied */
255 if (supplieddestination[0] == 0x00)
256 lstrcpyW(supplieddestination, wchr_dot);
257
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));
262
263 /* Extract required information from source specification */
264 XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
265
266 /* Extract required information from destination specification */
267 XCOPY_ProcessDestParm(supplieddestination, destinationstem,
268 destinationspec, sourcespec, flags);
269
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));
276
277 /* Pause if necessary */
278 if (flags & OPT_PAUSE) {
279 DWORD count;
280 char pausestr[10];
281
282 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
283 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
284 &count, NULL);
285 }
286
287 /* Now do the hard work... */
288 rc = XCOPY_DoCopy(sourcestem, sourcespec,
289 destinationstem, destinationspec,
290 flags);
291
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);
298 }
299
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);
305 }
306 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
307 return rc;
308
309 }
310
311
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)
318 {
319 WCHAR actualsource[MAX_PATH];
320 WCHAR *starPos;
321 WCHAR *questPos;
322 DWORD attribs;
323
324 /*
325 * Validate the source, expanding to full path ensuring it exists
326 */
327 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
328 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
329 return RC_INITERROR;
330 }
331
332 /* If full names required, convert to using the full path */
333 if (flags & OPT_FULL) {
334 lstrcpyW(suppliedsource, actualsource);
335 }
336
337 /*
338 * Work out the stem of the source
339 */
340
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 */
349
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);
356 }
357
358 if (attribs == INVALID_FILE_ATTRIBUTES) {
359 XCOPY_FailMessage(GetLastError());
360 return RC_INITERROR;
361
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;
367
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);
373 }
374 lstrcpyW(spec, wchr_star);
375
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;
383
384 WINE_TRACE("Filename supplied\n");
385 lastDir = wcsrchr(suppliedsource, '\\');
386
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);
402 }
403 }
404
405 return RC_OK;
406 }
407
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)
414 {
415 WCHAR actualdestination[MAX_PATH];
416 DWORD attribs;
417 BOOL isDir = FALSE;
418
419 /*
420 * Validate the source, expanding to full path ensuring it exists
421 */
422 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
423 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
424 return RC_INITERROR;
425 }
426
427 /* Destination is either a directory or a file */
428 attribs = GetFileAttributesW(actualdestination);
429
430 if (attribs == INVALID_FILE_ATTRIBUTES) {
431
432 /* If /I supplied and wildcard copy, assume directory */
433 if (flags & OPT_ASSUMEDIR &&
434 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
435
436 isDir = TRUE;
437
438 } else {
439 DWORD count;
440 char answer[10] = "";
441 WCHAR fileChar[2];
442 WCHAR dirChar[2];
443
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));
447
448 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
449 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
450
451 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
452 WINE_TRACE("User answer %c\n", answer[0]);
453
454 answer[0] = toupper(answer[0]);
455 }
456
457 if (answer[0] == dirChar[0]) {
458 isDir = TRUE;
459 } else {
460 isDir = FALSE;
461 }
462 }
463 } else {
464 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
465 }
466
467 if (isDir) {
468 lstrcpyW(stem, actualdestination);
469 *spec = 0x00;
470
471 /* Ensure ends with a '\' */
472 if (stem[lstrlenW(stem)-1] != '\\') {
473 lstrcatW(stem, wchr_slash);
474 }
475
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);
486 }
487 return RC_OK;
488 }
489
490 /* =========================================================================
491 XCOPY_DoCopy - Recursive function to copy files based on input parms
492 of a stem and a spec
493
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
498
499 ========================================================================= */
500 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
501 WCHAR *deststem, WCHAR *destspec,
502 DWORD flags)
503 {
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;
512
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));
517
518 /* Build the search info into a single parm */
519 lstrcpyW(inputpath, srcstem);
520 lstrcatW(inputpath, srcspec);
521
522 /* Search 1 - Look for matching files */
523 h = FindFirstFileW(inputpath, finddata);
524 while (h != INVALID_HANDLE_VALUE && findres) {
525
526 skipFile = FALSE;
527
528 /* Ignore . and .. */
529 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
530 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
531 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
532
533 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
534 } else {
535
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);
542 }
543
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);
550 }
551 } else {
552 lstrcatW(copyTo, destspec);
553 }
554
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);
559
560 /* See if allowed to copy it */
561 srcAttribs = GetFileAttributesW(copyFrom);
562 WINE_TRACE("Source attribs: %d\n", srcAttribs);
563
564 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
565 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
566
567 if (!(flags & OPT_COPYHIDSYS)) {
568 skipFile = TRUE;
569 }
570 }
571
572 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
573 (flags & OPT_ARCHIVEONLY)) {
574 skipFile = TRUE;
575 }
576
577 /* See if file exists */
578 destAttribs = GetFileAttributesW(copyTo);
579 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
580
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;
586 }
587
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);
597
598 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
599 WINE_TRACE("Skipping file as dest newer or same date\n");
600 skipFile = TRUE;
601 }
602 CloseHandle(h);
603 }
604 }
605
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];
612
613 /* Uppercase source filename */
614 lstrcpyW(copyFromUpper, copyFrom);
615 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
616
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;
626 }
627 }
628 }
629
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];
637
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));
641
642 while (!answered) {
643 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
644 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
645 &count, NULL);
646
647 answered = TRUE;
648 if (toupper(answer[0]) == noChar[0])
649 skipFile = TRUE;
650 else if (toupper(answer[0]) != yesChar[0])
651 answered = FALSE;
652 }
653 }
654
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];
663
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));
668
669 while (!answered) {
670 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
671 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
672 &count, NULL);
673
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;
681 }
682 }
683
684 /* See if it has to exist! */
685 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
686 skipFile = TRUE;
687 }
688
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};
696
697 XCOPY_wprintf(infostr, copyFrom, copyTo);
698 } else {
699 const WCHAR infostr[] = {'%', 's', '\n', 0};
700 XCOPY_wprintf(infostr, copyFrom);
701 }
702
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);
708 }
709
710 copiedFile = TRUE;
711 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
712 /* Skip copy */
713 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
714
715 DWORD error = GetLastError();
716 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
717 copyFrom, copyTo, error);
718 XCOPY_FailMessage(error);
719
720 if (flags & OPT_IGNOREERRORS) {
721 skipFile = TRUE;
722 } else {
723 ret = RC_WRITEERROR;
724 goto cleanup;
725 }
726 }
727
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));
733 }
734 filesCopied++;
735 }
736 }
737 }
738
739 /* Find next file */
740 findres = FindNextFileW(h, finddata);
741 }
742 FindClose(h);
743
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));
750
751 h = FindFirstFileW(inputpath, finddata);
752 while (h != INVALID_HANDLE_VALUE && findres) {
753
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)) {
758
759 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
760
761 /* Make up recursive information */
762 lstrcpyW(inputpath, srcstem);
763 lstrcatW(inputpath, finddata->cFileName);
764 lstrcatW(inputpath, wchr_slash);
765
766 lstrcpyW(outputpath, deststem);
767 if (*destspec == 0x00) {
768 lstrcatW(outputpath, finddata->cFileName);
769
770 /* If /E is supplied, create the directory now */
771 if ((flags & OPT_EMPTYDIR) &&
772 !(flags & OPT_SIMULATE))
773 XCOPY_CreateDirectory(outputpath);
774
775 lstrcatW(outputpath, wchr_slash);
776 }
777
778 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
779 }
780
781 /* Find next one */
782 findres = FindNextFileW(h, finddata);
783 }
784 }
785
786 cleanup:
787
788 /* free up memory */
789 HeapFree(GetProcessHeap(), 0, finddata);
790 HeapFree(GetProcessHeap(), 0, inputpath);
791 HeapFree(GetProcessHeap(), 0, outputpath);
792
793 return ret;
794 }
795
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)
802 {
803 int len;
804 WCHAR *new_path;
805 BOOL ret = TRUE;
806
807 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
808 lstrcpyW(new_path,path);
809
810 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
811 new_path[len - 1] = 0;
812
813 while (!CreateDirectoryW(new_path,NULL))
814 {
815 WCHAR *slash;
816 DWORD last_error = GetLastError();
817 if (last_error == ERROR_ALREADY_EXISTS)
818 break;
819
820 if (last_error != ERROR_PATH_NOT_FOUND)
821 {
822 ret = FALSE;
823 break;
824 }
825
826 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
827 {
828 ret = FALSE;
829 break;
830 }
831
832 len = slash - new_path;
833 new_path[len] = 0;
834 if (!XCOPY_CreateDirectory(new_path))
835 {
836 ret = FALSE;
837 break;
838 }
839 new_path[len] = '\\';
840 }
841 HeapFree(GetProcessHeap(),0,new_path);
842 return ret;
843 }
844
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) {
851
852 WCHAR *filenameStart = parms;
853
854 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
855 excludeList = NULL;
856
857 while (*parms && *parms != ' ' && *parms != '/') {
858
859 /* If found '+' then process the file found so far */
860 if (*parms == '+') {
861 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
862 return TRUE;
863 }
864 filenameStart = parms+1;
865 }
866 parms++;
867 }
868
869 if (filenameStart != parms) {
870 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
871 return TRUE;
872 }
873 }
874
875 return FALSE;
876 }
877
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) {
884
885 WCHAR endChar = *endOfName;
886 WCHAR buffer[MAXSTRING];
887 FILE *inFile = NULL;
888 const WCHAR readTextMode[] = {'r', 't', 0};
889
890 /* Null terminate the filename (temporarily updates the filename hence
891 parms not const) */
892 *endOfName = 0x00;
893
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;
900 }
901
902 /* Process line by line */
903 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
904 EXCLUDELIST *thisEntry;
905 int length = lstrlenW(buffer);
906
907 /* Strip CRLF */
908 buffer[length-1] = 0x00;
909
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));
920 }
921 }
922
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;
928 }
929
930 /* Revert the input string to original form, and cleanup + return */
931 *endOfName = endChar;
932 fclose(inFile);
933 return FALSE;
934 }
935
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};
943
944 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
945 WINE_FIXME("LoadString failed with %d\n", GetLastError());
946 lstrcpyW(msg, failedMsg);
947 }
948 return msg;
949 }
950
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;
958
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);
970 }
971 }
972
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, ...) {
979
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
985
986 va_list parms;
987 DWORD nOut;
988 int len;
989 DWORD res = 0;
990
991 /*
992 * Allocate buffer to use when writing to console
993 * Note: Not freed - memory will be allocated once and released when
994 * xcopy ends
995 */
996
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;
1002 }
1003
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;
1010 }
1011
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);
1016 }
1017
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;
1023
1024 toConsole = FALSE;
1025
1026 /*
1027 * Allocate buffer to use when writing to file. Not freed, as above
1028 */
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;
1034 }
1035
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);
1042 }
1043
1044 /* Trace whether screen or console */
1045 if (!traceOutput) {
1046 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1047 traceOutput = TRUE;
1048 }
1049 return nOut;
1050 }