Lars Martin Hambro <lars_martin4 AT hotmail DOT com>
[reactos.git] / reactos / 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 "xcopy.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
49
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, ...);
64
65 /* Typedefs */
66 typedef struct _EXCLUDELIST
67 {
68 struct _EXCLUDELIST *next;
69 WCHAR *name;
70 } EXCLUDELIST;
71
72
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};
81
82 /* Constants (Mostly for widechars) */
83
84
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];
89
90
91 /* =========================================================================
92 main - Main entrypoint for the xcopy command
93
94 Processes the args, and drives the actual copying
95 ========================================================================= */
96 int wmain (int argc, WCHAR *argvW[])
97 {
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};
111
112 /*
113 * Parse the command line
114 */
115
116 /* Confirm at least one parameter */
117 if (argc < 2) {
118 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
119 return RC_INITERROR;
120 }
121
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;
127 }
128 }
129
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;
136
137 /* Skip first arg, which is the program name */
138 argvW++;
139
140 while (argc > 1)
141 {
142 argc--;
143 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
144
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;
154 }
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;
178
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;
190
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));
197
198 /* Parse the arg : Month */
199 st.wMonth = _wtol(pos);
200 while (*pos && isdigit(*pos)) pos++;
201 if (*pos++ != '-') isError = TRUE;
202
203 /* Parse the arg : Day */
204 if (!isError) {
205 st.wDay = _wtol(pos);
206 while (*pos && isdigit(*pos)) pos++;
207 if (*pos++ != '-') isError = TRUE;
208 }
209
210 /* Parse the arg : Day */
211 if (!isError) {
212 st.wYear = _wtol(pos);
213 if (st.wYear < 100) st.wYear+=2000;
214 }
215
216 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
217 SYSTEMTIME st;
218 WCHAR datestring[32], timestring[32];
219
220 flags |= OPT_DATERANGE;
221
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));
228
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;
234 }
235 } else {
236 flags |= OPT_DATENEWER;
237 }
238 break;
239
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;
248 }
249 }
250 argvW++;
251 }
252
253 /* Default the destination if not supplied */
254 if (supplieddestination[0] == 0x00)
255 lstrcpyW(supplieddestination, wchr_dot);
256
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));
261
262 /* Extract required information from source specification */
263 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
264
265 /* Extract required information from destination specification */
266 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
267 destinationspec, sourcespec, flags);
268
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));
275
276 /* Pause if necessary */
277 if (flags & OPT_PAUSE) {
278 DWORD count;
279 char pausestr[10];
280
281 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
282 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
283 &count, NULL);
284 }
285
286 /* Now do the hard work... */
287 rc = XCOPY_DoCopy(sourcestem, sourcespec,
288 destinationstem, destinationspec,
289 flags);
290
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);
297 }
298
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);
304 }
305 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
306 return rc;
307
308 }
309
310
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)
317 {
318 WCHAR actualsource[MAX_PATH];
319 WCHAR *starPos;
320 WCHAR *questPos;
321 DWORD attribs;
322
323 /*
324 * Validate the source, expanding to full path ensuring it exists
325 */
326 if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
327 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
328 return RC_INITERROR;
329 }
330
331 /* If full names required, convert to using the full path */
332 if (flags & OPT_FULL) {
333 lstrcpyW(suppliedsource, actualsource);
334 }
335
336 /*
337 * Work out the stem of the source
338 */
339
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 */
348
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);
355 }
356
357 if (attribs == INVALID_FILE_ATTRIBUTES) {
358 XCOPY_FailMessage(GetLastError());
359 return RC_INITERROR;
360
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;
366
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);
372 }
373 lstrcpyW(spec, wchr_star);
374
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;
382
383 WINE_TRACE("Filename supplied\n");
384 lastDir = wcsrchr(suppliedsource, '\\');
385
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);
401 }
402 }
403
404 return RC_OK;
405 }
406
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)
413 {
414 WCHAR actualdestination[MAX_PATH];
415 DWORD attribs;
416 BOOL isDir = FALSE;
417
418 /*
419 * Validate the source, expanding to full path ensuring it exists
420 */
421 if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
422 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
423 return RC_INITERROR;
424 }
425
426 /* Destination is either a directory or a file */
427 attribs = GetFileAttributes(actualdestination);
428
429 if (attribs == INVALID_FILE_ATTRIBUTES) {
430
431 /* If /I supplied and wildcard copy, assume directory */
432 if (flags & OPT_ASSUMEDIR &&
433 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
434
435 isDir = TRUE;
436
437 } else {
438 DWORD count;
439 char answer[10] = "";
440 WCHAR fileChar[2];
441 WCHAR dirChar[2];
442
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));
446
447 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
448 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
449
450 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
451 WINE_TRACE("User answer %c\n", answer[0]);
452
453 answer[0] = toupper(answer[0]);
454 }
455
456 if (answer[0] == dirChar[0]) {
457 isDir = TRUE;
458 } else {
459 isDir = FALSE;
460 }
461 }
462 } else {
463 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
464 }
465
466 if (isDir) {
467 lstrcpyW(stem, actualdestination);
468 *spec = 0x00;
469
470 /* Ensure ends with a '\' */
471 if (stem[lstrlenW(stem)-1] != '\\') {
472 lstrcatW(stem, wchr_slash);
473 }
474
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);
485 }
486 return RC_OK;
487 }
488
489 /* =========================================================================
490 XCOPY_DoCopy - Recursive function to copy files based on input parms
491 of a stem and a spec
492
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
497
498 ========================================================================= */
499 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
500 WCHAR *deststem, WCHAR *destspec,
501 DWORD flags)
502 {
503 WIN32_FIND_DATA *finddata;
504 HANDLE h;
505 BOOL findres = TRUE;
506 WCHAR *inputpath, *outputpath;
507 BOOL copiedFile = FALSE;
508 DWORD destAttribs, srcAttribs;
509 BOOL skipFile;
510 int ret = 0;
511
512 /* Allocate some working memory on heap to minimize footprint */
513 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
514 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
515 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
516
517 /* Build the search info into a single parm */
518 lstrcpyW(inputpath, srcstem);
519 lstrcatW(inputpath, srcspec);
520
521 /* Search 1 - Look for matching files */
522 h = FindFirstFile(inputpath, finddata);
523 while (h != INVALID_HANDLE_VALUE && findres) {
524
525 skipFile = FALSE;
526
527 /* Ignore . and .. */
528 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
529 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
530 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
531
532 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
533 } else {
534
535 /* Get the filename information */
536 lstrcpyW(copyFrom, srcstem);
537 if (flags & OPT_SHORTNAME) {
538 lstrcatW(copyFrom, finddata->cAlternateFileName);
539 } else {
540 lstrcatW(copyFrom, finddata->cFileName);
541 }
542
543 lstrcpyW(copyTo, deststem);
544 if (*destspec == 0x00) {
545 if (flags & OPT_SHORTNAME) {
546 lstrcatW(copyTo, finddata->cAlternateFileName);
547 } else {
548 lstrcatW(copyTo, finddata->cFileName);
549 }
550 } else {
551 lstrcatW(copyTo, destspec);
552 }
553
554 /* Do the copy */
555 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
556 wine_dbgstr_w(copyTo));
557 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
558
559 /* See if allowed to copy it */
560 srcAttribs = GetFileAttributesW(copyFrom);
561 WINE_TRACE("Source attribs: %d\n", srcAttribs);
562
563 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
564 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
565
566 if (!(flags & OPT_COPYHIDSYS)) {
567 skipFile = TRUE;
568 }
569 }
570
571 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
572 (flags & OPT_ARCHIVEONLY)) {
573 skipFile = TRUE;
574 }
575
576 /* See if file exists */
577 destAttribs = GetFileAttributesW(copyTo);
578 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
579
580 /* Check date ranges if a destination file already exists */
581 if (!skipFile && (flags & OPT_DATERANGE) &&
582 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
583 WINE_TRACE("Skipping file as modified date too old\n");
584 skipFile = TRUE;
585 }
586
587 /* If just /D supplied, only overwrite if src newer than dest */
588 if (!skipFile && (flags & OPT_DATENEWER) &&
589 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
590 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
591 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
592 NULL);
593 if (h != INVALID_HANDLE_VALUE) {
594 FILETIME writeTime;
595 GetFileTime(h, NULL, NULL, &writeTime);
596
597 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
598 WINE_TRACE("Skipping file as dest newer or same date\n");
599 skipFile = TRUE;
600 }
601 CloseHandle(h);
602 }
603 }
604
605 /* See if exclude list provided. Note since filenames are case
606 insensitive, need to uppercase the filename before doing
607 strstr */
608 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
609 EXCLUDELIST *pos = excludeList;
610 WCHAR copyFromUpper[MAX_PATH];
611
612 /* Uppercase source filename */
613 lstrcpyW(copyFromUpper, copyFrom);
614 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
615
616 /* Loop through testing each exclude line */
617 while (pos) {
618 if (wcsstr(copyFromUpper, pos->name) != NULL) {
619 WINE_TRACE("Skipping file as matches exclude '%s'\n",
620 wine_dbgstr_w(pos->name));
621 skipFile = TRUE;
622 pos = NULL;
623 } else {
624 pos = pos->next;
625 }
626 }
627 }
628
629 /* Prompt each file if necessary */
630 if (!skipFile && (flags & OPT_SRCPROMPT)) {
631 DWORD count;
632 char answer[10];
633 BOOL answered = FALSE;
634 WCHAR yesChar[2];
635 WCHAR noChar[2];
636
637 /* Read the Y and N characters from the resource file */
638 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
639 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
640
641 while (!answered) {
642 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
643 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
644 &count, NULL);
645
646 answered = TRUE;
647 if (toupper(answer[0]) == noChar[0])
648 skipFile = TRUE;
649 else if (toupper(answer[0]) != yesChar[0])
650 answered = FALSE;
651 }
652 }
653
654 if (!skipFile &&
655 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
656 DWORD count;
657 char answer[10];
658 BOOL answered = FALSE;
659 WCHAR yesChar[2];
660 WCHAR allChar[2];
661 WCHAR noChar[2];
662
663 /* Read the A,Y and N characters from the resource file */
664 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
665 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
666 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
667
668 while (!answered) {
669 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
670 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
671 &count, NULL);
672
673 answered = TRUE;
674 if (toupper(answer[0]) == allChar[0])
675 flags |= OPT_NOPROMPT;
676 else if (toupper(answer[0]) == noChar[0])
677 skipFile = TRUE;
678 else if (toupper(answer[0]) != yesChar[0])
679 answered = FALSE;
680 }
681 }
682
683 /* See if it has to exist! */
684 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
685 skipFile = TRUE;
686 }
687
688 /* Output a status message */
689 if (!skipFile) {
690 if (flags & OPT_QUIET) {
691 /* Skip message */
692 } else if (flags & OPT_FULL) {
693 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
694 '%', 's', '\n', 0};
695
696 XCOPY_wprintf(infostr, copyFrom, copyTo);
697 } else {
698 const WCHAR infostr[] = {'%', 's', '\n', 0};
699 XCOPY_wprintf(infostr, copyFrom);
700 }
701
702 /* If allowing overwriting of read only files, remove any
703 write protection */
704 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
705 (flags & OPT_REPLACEREAD)) {
706 SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
707 }
708
709 copiedFile = TRUE;
710 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
711 /* Skip copy */
712 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
713
714 DWORD error = GetLastError();
715 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
716 copyFrom, copyTo, error);
717 XCOPY_FailMessage(error);
718
719 if (flags & OPT_IGNOREERRORS) {
720 skipFile = TRUE;
721 } else {
722 ret = RC_WRITEERROR;
723 goto cleanup;
724 }
725 }
726
727 /* If /M supplied, remove the archive bit after successful copy */
728 if (!skipFile) {
729 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
730 (flags & OPT_REMOVEARCH)) {
731 SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
732 }
733 filesCopied++;
734 }
735 }
736 }
737
738 /* Find next file */
739 findres = FindNextFile(h, finddata);
740 }
741 FindClose(h);
742
743 /* Search 2 - do subdirs */
744 if (flags & OPT_RECURSIVE) {
745 lstrcpyW(inputpath, srcstem);
746 lstrcatW(inputpath, wchr_star);
747 findres = TRUE;
748 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
749
750 h = FindFirstFile(inputpath, finddata);
751 while (h != INVALID_HANDLE_VALUE && findres) {
752
753 /* Only looking for dirs */
754 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
755 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
756 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
757
758 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
759
760 /* Make up recursive information */
761 lstrcpyW(inputpath, srcstem);
762 lstrcatW(inputpath, finddata->cFileName);
763 lstrcatW(inputpath, wchr_slash);
764
765 lstrcpyW(outputpath, deststem);
766 if (*destspec == 0x00) {
767 lstrcatW(outputpath, finddata->cFileName);
768
769 /* If /E is supplied, create the directory now */
770 if ((flags & OPT_EMPTYDIR) &&
771 !(flags & OPT_SIMULATE))
772 XCOPY_CreateDirectory(outputpath);
773
774 lstrcatW(outputpath, wchr_slash);
775 }
776
777 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
778 }
779
780 /* Find next one */
781 findres = FindNextFile(h, finddata);
782 }
783 }
784
785 cleanup:
786
787 /* free up memory */
788 HeapFree(GetProcessHeap(), 0, finddata);
789 HeapFree(GetProcessHeap(), 0, inputpath);
790 HeapFree(GetProcessHeap(), 0, outputpath);
791
792 return ret;
793 }
794
795 /* =========================================================================
796 * Routine copied from cmd.exe md command -
797 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
798 * dir2 if they do not already exist.
799 * ========================================================================= */
800 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
801 {
802 int len;
803 WCHAR *new_path;
804 BOOL ret = TRUE;
805
806 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
807 lstrcpyW(new_path,path);
808
809 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
810 new_path[len - 1] = 0;
811
812 while (!CreateDirectory(new_path,NULL))
813 {
814 WCHAR *slash;
815 DWORD last_error = GetLastError();
816 if (last_error == ERROR_ALREADY_EXISTS)
817 break;
818
819 if (last_error != ERROR_PATH_NOT_FOUND)
820 {
821 ret = FALSE;
822 break;
823 }
824
825 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
826 {
827 ret = FALSE;
828 break;
829 }
830
831 len = slash - new_path;
832 new_path[len] = 0;
833 if (!XCOPY_CreateDirectory(new_path))
834 {
835 ret = FALSE;
836 break;
837 }
838 new_path[len] = '\\';
839 }
840 HeapFree(GetProcessHeap(),0,new_path);
841 return ret;
842 }
843
844 /* =========================================================================
845 * Process the /EXCLUDE: file list, building up a list of substrings to
846 * avoid copying
847 * Returns TRUE on any failure
848 * ========================================================================= */
849 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
850
851 WCHAR *filenameStart = parms;
852
853 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
854 excludeList = NULL;
855
856 while (*parms && *parms != ' ' && *parms != '/') {
857
858 /* If found '+' then process the file found so far */
859 if (*parms == '+') {
860 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
861 return TRUE;
862 }
863 filenameStart = parms+1;
864 }
865 parms++;
866 }
867
868 if (filenameStart != parms) {
869 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
870 return TRUE;
871 }
872 }
873
874 return FALSE;
875 }
876
877 /* =========================================================================
878 * Process a single file from the /EXCLUDE: file list, building up a list
879 * of substrings to avoid copying
880 * Returns TRUE on any failure
881 * ========================================================================= */
882 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
883
884 WCHAR endChar = *endOfName;
885 WCHAR buffer[MAXSTRING];
886 FILE *inFile = NULL;
887 const WCHAR readTextMode[] = {'r', 't', 0};
888
889 /* Null terminate the filename (temporarily updates the filename hence
890 parms not const) */
891 *endOfName = 0x00;
892
893 /* Open the file */
894 inFile = _wfopen(filename, readTextMode);
895 if (inFile == NULL) {
896 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
897 *endOfName = endChar;
898 return TRUE;
899 }
900
901 /* Process line by line */
902 while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
903 EXCLUDELIST *thisEntry;
904 int length = lstrlenW(buffer);
905
906 /* Strip CRLF */
907 buffer[length-1] = 0x00;
908
909 /* If more than CRLF */
910 if (length > 1) {
911 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
912 thisEntry->next = excludeList;
913 excludeList = thisEntry;
914 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
915 (length * sizeof(WCHAR))+1);
916 lstrcpyW(thisEntry->name, buffer);
917 CharUpperBuff(thisEntry->name, length);
918 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
919 }
920 }
921
922 /* See if EOF or error occurred */
923 if (!feof(inFile)) {
924 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
925 *endOfName = endChar;
926 return TRUE;
927 }
928
929 /* Revert the input string to original form, and cleanup + return */
930 *endOfName = endChar;
931 fclose(inFile);
932 return FALSE;
933 }
934
935 /* =========================================================================
936 * Load a string from the resource file, handling any error
937 * Returns string retrieved from resource file
938 * ========================================================================= */
939 static WCHAR *XCOPY_LoadMessage(UINT id) {
940 static WCHAR msg[MAXSTRING];
941 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
942
943 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
944 WINE_FIXME("LoadString failed with %d\n", GetLastError());
945 lstrcpyW(msg, failedMsg);
946 }
947 return msg;
948 }
949
950 /* =========================================================================
951 * Load a string for a system error and writes it to the screen
952 * Returns string retrieved from resource file
953 * ========================================================================= */
954 static void XCOPY_FailMessage(DWORD err) {
955 LPWSTR lpMsgBuf;
956 int status;
957
958 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
959 FORMAT_MESSAGE_FROM_SYSTEM,
960 NULL, err, 0,
961 (LPTSTR) &lpMsgBuf, 0, NULL);
962 if (!status) {
963 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
964 err, GetLastError());
965 } else {
966 const WCHAR infostr[] = {'%', 's', '\n', 0};
967 XCOPY_wprintf(infostr, lpMsgBuf);
968 LocalFree ((HLOCAL)lpMsgBuf);
969 }
970 }
971
972 /* =========================================================================
973 * Output a formatted unicode string. Ideally this will go to the console
974 * and hence required WriteConsoleW to output it, however if file i/o is
975 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
976 * ========================================================================= */
977 int XCOPY_wprintf(const WCHAR *format, ...) {
978
979 static WCHAR *output_bufW = NULL;
980 static char *output_bufA = NULL;
981 static BOOL toConsole = TRUE;
982 static BOOL traceOutput = FALSE;
983 #define MAX_WRITECONSOLE_SIZE 65535
984
985 va_list parms;
986 DWORD len, nOut;
987 DWORD res = 0;
988
989 /*
990 * Allocate buffer to use when writing to console
991 * Note: Not freed - memory will be allocated once and released when
992 * xcopy ends
993 */
994
995 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
996 MAX_WRITECONSOLE_SIZE);
997 if (!output_bufW) {
998 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
999 return 0;
1000 }
1001
1002 /* Use wvsprintf to store output into unicode buffer */
1003 va_start(parms, format);
1004 len = vswprintf(output_bufW, format, parms);
1005 va_end(parms);
1006
1007 /* Try to write as unicode all the time we think its a console */
1008 if (toConsole) {
1009 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1010 output_bufW, len, &nOut, NULL);
1011 }
1012
1013 /* If writing to console has failed (ever) we assume its file
1014 i/o so convert to OEM codepage and output */
1015 if (!res) {
1016 BOOL usedDefaultChar = FALSE;
1017 DWORD convertedChars;
1018
1019 toConsole = FALSE;
1020
1021 /*
1022 * Allocate buffer to use when writing to file. Not freed, as above
1023 */
1024 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1025 MAX_WRITECONSOLE_SIZE);
1026 if (!output_bufA) {
1027 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1028 return 0;
1029 }
1030
1031 /* Convert to OEM, then output */
1032 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1033 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1034 "?", &usedDefaultChar);
1035 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1036 &nOut, FALSE);
1037 }
1038
1039 /* Trace whether screen or console */
1040 if (!traceOutput) {
1041 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1042 traceOutput = TRUE;
1043 }
1044 return nOut;
1045 }