daf269b99b79f32e2816751342272b3b2d09fc17
[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 <wine/unicode.h>
47 #include "xcopy.h"
48
49 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
50
51 /* Prototypes */
52 static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
53 WCHAR *supplieddestination, DWORD *flags);
54 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
55 WCHAR *spec, DWORD flags);
56 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
57 WCHAR *spec, WCHAR *srcspec, DWORD flags);
58 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
59 WCHAR *deststem, WCHAR *destspec,
60 DWORD flags);
61 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
62 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
63 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
64 static WCHAR *XCOPY_LoadMessage(UINT id);
65 static void XCOPY_FailMessage(DWORD err);
66 static int XCOPY_wprintf(const WCHAR *format, ...);
67
68 /* Typedefs */
69 typedef struct _EXCLUDELIST
70 {
71 struct _EXCLUDELIST *next;
72 WCHAR *name;
73 } EXCLUDELIST;
74
75
76 /* Global variables */
77 static ULONG filesCopied = 0; /* Number of files copied */
78 static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
79 static FILETIME dateRange; /* Date range to copy after*/
80 static const WCHAR wchr_slash[] = {'\\', 0};
81 static const WCHAR wchr_star[] = {'*', 0};
82 static const WCHAR wchr_dot[] = {'.', 0};
83 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
84
85 /* Constants (Mostly for widechars) */
86
87
88 /* To minimize stack usage during recursion, some temporary variables
89 made global */
90 static WCHAR copyFrom[MAX_PATH];
91 static WCHAR copyTo[MAX_PATH];
92
93
94 /* =========================================================================
95 main - Main entrypoint for the xcopy command
96
97 Processes the args, and drives the actual copying
98 ========================================================================= */
99 int wmain (int argc, WCHAR *argvW[])
100 {
101 int rc = 0;
102 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
103 WCHAR supplieddestination[MAX_PATH] = {0};
104 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
105 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
106 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
107 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
108 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
109 DWORD flags = 0; /* Option flags */
110 const WCHAR PROMPTSTR1[] = {'/', 'Y', 0};
111 const WCHAR PROMPTSTR2[] = {'/', 'y', 0};
112 const WCHAR COPYCMD[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
113
114 /* Preinitialize flags based on COPYCMD */
115 if (GetEnvironmentVariableW(COPYCMD, copyCmd, MAXSTRING)) {
116 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
117 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
118 flags |= OPT_NOPROMPT;
119 }
120 }
121
122 /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
123 wine, but on windows these can be normal files. At least one installer
124 uses files such as .packlist and (validly) expects them to be copied.
125 Under wine, if we do not copy hidden files by default then they get
126 lose */
127 flags |= OPT_COPYHIDSYS;
128
129 /*
130 * Parse the command line
131 */
132 if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
133 &flags)) != RC_OK) {
134 if (rc == RC_HELP)
135 return RC_OK;
136 else
137 return rc;
138 }
139
140 /* Trace out the supplied information */
141 WINE_TRACE("Supplied parameters:\n");
142 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
143 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
144
145 /* Extract required information from source specification */
146 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
147 if (rc != RC_OK) return rc;
148
149 /* Extract required information from destination specification */
150 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
151 destinationspec, sourcespec, flags);
152 if (rc != RC_OK) return rc;
153
154 /* Trace out the resulting information */
155 WINE_TRACE("Resolved parameters:\n");
156 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
157 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
158 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
159 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
160
161 /* Pause if necessary */
162 if (flags & OPT_PAUSE) {
163 DWORD count;
164 char pausestr[10];
165
166 XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
167 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
168 &count, NULL);
169 }
170
171 /* Now do the hard work... */
172 rc = XCOPY_DoCopy(sourcestem, sourcespec,
173 destinationstem, destinationspec,
174 flags);
175
176 /* Clear up exclude list allocated memory */
177 while (excludeList) {
178 EXCLUDELIST *pos = excludeList;
179 excludeList = excludeList -> next;
180 HeapFree(GetProcessHeap(), 0, pos->name);
181 HeapFree(GetProcessHeap(), 0, pos);
182 }
183
184 /* Finished - print trailer and exit */
185 if (flags & OPT_SIMULATE) {
186 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
187 } else if (!(flags & OPT_NOCOPY)) {
188 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
189 }
190 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
191 return rc;
192
193 }
194
195 /* =========================================================================
196 XCOPY_ParseCommandLine - Parses the command line
197 ========================================================================= */
198 static BOOL is_whitespace(WCHAR c)
199 {
200 return c == ' ' || c == '\t';
201 }
202
203 static WCHAR *skip_whitespace(WCHAR *p)
204 {
205 for (; *p && is_whitespace(*p); p++);
206 return p;
207 }
208
209 /* Windows XCOPY uses a simplified command line parsing algorithm
210 that lacks the escaped-quote logic of build_argv(), because
211 literal double quotes are illegal in any of its arguments.
212 Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
213 static int find_end_of_word(const WCHAR *word, WCHAR **end)
214 {
215 BOOL in_quotes = 0;
216 const WCHAR *ptr = word;
217 for (;;) {
218 for (; *ptr != '\0' && *ptr != '"' &&
219 (in_quotes || !is_whitespace(*ptr)); ptr++);
220 if (*ptr == '"') {
221 in_quotes = !in_quotes;
222 ptr++;
223 }
224 /* Odd number of double quotes is illegal for XCOPY */
225 if (in_quotes && *ptr == '\0')
226 return RC_INITERROR;
227 if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
228 break;
229 }
230 *end = (WCHAR*)ptr;
231 return RC_OK;
232 }
233
234 /* Remove all double quotes from a word */
235 static void strip_quotes(WCHAR *word, WCHAR **end)
236 {
237 WCHAR *rp, *wp;
238 for (rp = word, wp = word; *rp != '\0'; rp++) {
239 if (*rp == '"')
240 continue;
241 if (wp < rp)
242 *wp = *rp;
243 wp++;
244 }
245 *wp = '\0';
246 *end = wp;
247 }
248
249 static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
250 WCHAR *supplieddestination, DWORD *pflags)
251 {
252 const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
253 DWORD flags = *pflags;
254 WCHAR *cmdline, *word, *end, *next;
255 int rc = RC_INITERROR;
256
257 cmdline = _wcsdup(GetCommandLineW());
258 if (cmdline == NULL)
259 return rc;
260
261 /* Skip first arg, which is the program name */
262 if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
263 goto out;
264 word = skip_whitespace(word);
265
266 while (*word)
267 {
268 WCHAR first;
269 if ((rc = find_end_of_word(word, &end)) != RC_OK)
270 goto out;
271
272 next = skip_whitespace(end);
273 first = word[0];
274 *end = '\0';
275 strip_quotes(word, &end);
276 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
277
278 /* First non-switch parameter is source, second is destination */
279 if (first != '/') {
280 if (suppliedsource[0] == 0x00) {
281 lstrcpyW(suppliedsource, word);
282 } else if (supplieddestination[0] == 0x00) {
283 lstrcpyW(supplieddestination, word);
284 } else {
285 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
286 goto out;
287 }
288 } else {
289 /* Process all the switch options
290 Note: Windows docs say /P prompts when dest is created
291 but tests show it is done for each src file
292 regardless of the destination */
293 switch (toupper(word[1])) {
294 case 'I': flags |= OPT_ASSUMEDIR; break;
295 case 'S': flags |= OPT_RECURSIVE; break;
296 case 'Q': flags |= OPT_QUIET; break;
297 case 'F': flags |= OPT_FULL; break;
298 case 'L': flags |= OPT_SIMULATE; break;
299 case 'W': flags |= OPT_PAUSE; break;
300 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
301 case 'Y': flags |= OPT_NOPROMPT; break;
302 case 'N': flags |= OPT_SHORTNAME; break;
303 case 'U': flags |= OPT_MUSTEXIST; break;
304 case 'R': flags |= OPT_REPLACEREAD; break;
305 case 'H': flags |= OPT_COPYHIDSYS; break;
306 case 'C': flags |= OPT_IGNOREERRORS; break;
307 case 'P': flags |= OPT_SRCPROMPT; break;
308 case 'A': flags |= OPT_ARCHIVEONLY; break;
309 case 'M': flags |= OPT_ARCHIVEONLY |
310 OPT_REMOVEARCH; break;
311
312 /* E can be /E or /EXCLUDE */
313 case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
314 NORM_IGNORECASE | SORT_STRINGSORT,
315 &word[1], 8,
316 EXCLUDE, -1) == 2) {
317 if (XCOPY_ProcessExcludeList(&word[9])) {
318 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
319 goto out;
320 } else flags |= OPT_EXCLUDELIST;
321 } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
322 break;
323
324 /* D can be /D or /D: */
325 case 'D': if (word[2]==':' && isdigit(word[3])) {
326 SYSTEMTIME st;
327 WCHAR *pos = &word[3];
328 BOOL isError = FALSE;
329 memset(&st, 0x00, sizeof(st));
330
331 /* Parse the arg : Month */
332 st.wMonth = _wtol(pos);
333 while (*pos && isdigit(*pos)) pos++;
334 if (*pos++ != '-') isError = TRUE;
335
336 /* Parse the arg : Day */
337 if (!isError) {
338 st.wDay = _wtol(pos);
339 while (*pos && isdigit(*pos)) pos++;
340 if (*pos++ != '-') isError = TRUE;
341 }
342
343 /* Parse the arg : Day */
344 if (!isError) {
345 st.wYear = _wtol(pos);
346 while (*pos && isdigit(*pos)) pos++;
347 if (st.wYear < 100) st.wYear+=2000;
348 }
349
350 if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
351 SYSTEMTIME st;
352 WCHAR datestring[32], timestring[32];
353
354 flags |= OPT_DATERANGE;
355
356 /* Debug info: */
357 FileTimeToSystemTime (&dateRange, &st);
358 GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
359 sizeof(datestring)/sizeof(WCHAR));
360 GetTimeFormatW(0, TIME_NOSECONDS, &st,
361 NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
362
363 WINE_TRACE("Date being used is: %s %s\n",
364 wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
365 } else {
366 XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
367 goto out;
368 }
369 } else {
370 flags |= OPT_DATENEWER;
371 }
372 break;
373
374 case '-': if (toupper(word[2])=='Y')
375 flags &= ~OPT_NOPROMPT; break;
376 case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
377 rc = RC_HELP;
378 goto out;
379 default:
380 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
381 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
382 goto out;
383 }
384 }
385 word = next;
386 }
387
388 /* Default the destination if not supplied */
389 if (supplieddestination[0] == 0x00)
390 lstrcpyW(supplieddestination, wchr_dot);
391
392 *pflags = flags;
393 rc = RC_OK;
394
395 out:
396 free(cmdline);
397 return rc;
398 }
399
400
401 /* =========================================================================
402 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
403 converts it into a stem and a filespec
404 ========================================================================= */
405 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
406 WCHAR *spec, DWORD flags)
407 {
408 WCHAR actualsource[MAX_PATH];
409 WCHAR *starPos;
410 WCHAR *questPos;
411 DWORD attribs;
412
413 /*
414 * Validate the source, expanding to full path ensuring it exists
415 */
416 if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
417 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
418 return RC_INITERROR;
419 }
420
421 /* If full names required, convert to using the full path */
422 if (flags & OPT_FULL) {
423 lstrcpyW(suppliedsource, actualsource);
424 }
425
426 /*
427 * Work out the stem of the source
428 */
429
430 /* If a directory is supplied, use that as-is (either fully or
431 partially qualified)
432 If a filename is supplied + a directory or drive path, use that
433 as-is
434 Otherwise
435 If no directory or path specified, add eg. C:
436 stem is Drive/Directory is bit up to last \ (or first :)
437 spec is bit after that */
438
439 starPos = wcschr(suppliedsource, '*');
440 questPos = wcschr(suppliedsource, '?');
441 if (starPos || questPos) {
442 attribs = 0x00; /* Ensures skips invalid or directory check below */
443 } else {
444 attribs = GetFileAttributesW(actualsource);
445 }
446
447 if (attribs == INVALID_FILE_ATTRIBUTES) {
448 XCOPY_FailMessage(GetLastError());
449 return RC_INITERROR;
450
451 /* Directory:
452 stem should be exactly as supplied plus a '\', unless it was
453 eg. C: in which case no slash required */
454 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
455 WCHAR lastChar;
456
457 WINE_TRACE("Directory supplied\n");
458 lstrcpyW(stem, suppliedsource);
459 lastChar = stem[lstrlenW(stem)-1];
460 if (lastChar != '\\' && lastChar != ':') {
461 lstrcatW(stem, wchr_slash);
462 }
463 lstrcpyW(spec, wchr_star);
464
465 /* File or wildcard search:
466 stem should be:
467 Up to and including last slash if directory path supplied
468 If c:filename supplied, just the c:
469 Otherwise stem should be the current drive letter + ':' */
470 } else {
471 WCHAR *lastDir;
472
473 WINE_TRACE("Filename supplied\n");
474 lastDir = wcsrchr(suppliedsource, '\\');
475
476 if (lastDir) {
477 lstrcpyW(stem, suppliedsource);
478 stem[(lastDir-suppliedsource) + 1] = 0x00;
479 lstrcpyW(spec, (lastDir+1));
480 } else if (suppliedsource[1] == ':') {
481 lstrcpyW(stem, suppliedsource);
482 stem[2] = 0x00;
483 lstrcpyW(spec, suppliedsource+2);
484 } else {
485 WCHAR curdir[MAXSTRING];
486 GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
487 stem[0] = curdir[0];
488 stem[1] = curdir[1];
489 stem[2] = 0x00;
490 lstrcpyW(spec, suppliedsource);
491 }
492 }
493
494 return RC_OK;
495 }
496
497 /* =========================================================================
498 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
499 converts it into a stem
500 ========================================================================= */
501 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
502 WCHAR *srcspec, DWORD flags)
503 {
504 WCHAR actualdestination[MAX_PATH];
505 DWORD attribs;
506 BOOL isDir = FALSE;
507
508 /*
509 * Validate the source, expanding to full path ensuring it exists
510 */
511 if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
512 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
513 return RC_INITERROR;
514 }
515
516 /* Destination is either a directory or a file */
517 attribs = GetFileAttributesW(actualdestination);
518
519 if (attribs == INVALID_FILE_ATTRIBUTES) {
520
521 /* If /I supplied and wildcard copy, assume directory */
522 /* Also if destination ends with backslash */
523 if ((flags & OPT_ASSUMEDIR &&
524 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) ||
525 (supplieddestination[lstrlenW(supplieddestination)-1] == '\\')) {
526
527 isDir = TRUE;
528
529 } else {
530 DWORD count;
531 char answer[10] = "";
532 WCHAR fileChar[2];
533 WCHAR dirChar[2];
534
535 /* Read the F and D characters from the resource file */
536 wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
537 wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
538
539 while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
540 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
541
542 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
543 WINE_TRACE("User answer %c\n", answer[0]);
544
545 answer[0] = toupper(answer[0]);
546 }
547
548 if (answer[0] == dirChar[0]) {
549 isDir = TRUE;
550 } else {
551 isDir = FALSE;
552 }
553 }
554 } else {
555 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
556 }
557
558 if (isDir) {
559 lstrcpyW(stem, actualdestination);
560 *spec = 0x00;
561
562 /* Ensure ends with a '\' */
563 if (stem[lstrlenW(stem)-1] != '\\') {
564 lstrcatW(stem, wchr_slash);
565 }
566
567 } else {
568 WCHAR drive[MAX_PATH];
569 WCHAR dir[MAX_PATH];
570 WCHAR fname[MAX_PATH];
571 WCHAR ext[MAX_PATH];
572 _wsplitpath(actualdestination, drive, dir, fname, ext);
573 lstrcpyW(stem, drive);
574 lstrcatW(stem, dir);
575 lstrcpyW(spec, fname);
576 lstrcatW(spec, ext);
577 }
578 return RC_OK;
579 }
580
581 /* =========================================================================
582 XCOPY_DoCopy - Recursive function to copy files based on input parms
583 of a stem and a spec
584
585 This works by using FindFirstFile supplying the source stem and spec.
586 If results are found, any non-directory ones are processed
587 Then, if /S or /E is supplied, another search is made just for
588 directories, and this function is called again for that directory
589
590 ========================================================================= */
591 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
592 WCHAR *deststem, WCHAR *destspec,
593 DWORD flags)
594 {
595 WIN32_FIND_DATAW *finddata;
596 HANDLE h;
597 BOOL findres = TRUE;
598 WCHAR *inputpath, *outputpath;
599 BOOL copiedFile = FALSE;
600 DWORD destAttribs, srcAttribs;
601 BOOL skipFile;
602 int ret = 0;
603
604 /* Allocate some working memory on heap to minimize footprint */
605 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
606 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
607 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
608
609 /* Build the search info into a single parm */
610 lstrcpyW(inputpath, srcstem);
611 lstrcatW(inputpath, srcspec);
612
613 /* Search 1 - Look for matching files */
614 h = FindFirstFileW(inputpath, finddata);
615 while (h != INVALID_HANDLE_VALUE && findres) {
616
617 skipFile = FALSE;
618
619 /* Ignore . and .. */
620 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
621 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
622 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
623
624 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
625 } else {
626
627 /* Get the filename information */
628 lstrcpyW(copyFrom, srcstem);
629 if (flags & OPT_SHORTNAME) {
630 lstrcatW(copyFrom, finddata->cAlternateFileName);
631 } else {
632 lstrcatW(copyFrom, finddata->cFileName);
633 }
634
635 lstrcpyW(copyTo, deststem);
636 if (*destspec == 0x00) {
637 if (flags & OPT_SHORTNAME) {
638 lstrcatW(copyTo, finddata->cAlternateFileName);
639 } else {
640 lstrcatW(copyTo, finddata->cFileName);
641 }
642 } else {
643 lstrcatW(copyTo, destspec);
644 }
645
646 /* Do the copy */
647 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
648 wine_dbgstr_w(copyTo));
649 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
650
651 /* See if allowed to copy it */
652 srcAttribs = GetFileAttributesW(copyFrom);
653 WINE_TRACE("Source attribs: %d\n", srcAttribs);
654
655 if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
656 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
657
658 if (!(flags & OPT_COPYHIDSYS)) {
659 skipFile = TRUE;
660 }
661 }
662
663 if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
664 (flags & OPT_ARCHIVEONLY)) {
665 skipFile = TRUE;
666 }
667
668 /* See if file exists */
669 destAttribs = GetFileAttributesW(copyTo);
670 WINE_TRACE("Dest attribs: %d\n", srcAttribs);
671
672 /* Check date ranges if a destination file already exists */
673 if (!skipFile && (flags & OPT_DATERANGE) &&
674 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
675 WINE_TRACE("Skipping file as modified date too old\n");
676 skipFile = TRUE;
677 }
678
679 /* If just /D supplied, only overwrite if src newer than dest */
680 if (!skipFile && (flags & OPT_DATENEWER) &&
681 (destAttribs != INVALID_FILE_ATTRIBUTES)) {
682 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
683 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
684 NULL);
685 if (h != INVALID_HANDLE_VALUE) {
686 FILETIME writeTime;
687 GetFileTime(h, NULL, NULL, &writeTime);
688
689 if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
690 WINE_TRACE("Skipping file as dest newer or same date\n");
691 skipFile = TRUE;
692 }
693 CloseHandle(h);
694 }
695 }
696
697 /* See if exclude list provided. Note since filenames are case
698 insensitive, need to uppercase the filename before doing
699 strstr */
700 if (!skipFile && (flags & OPT_EXCLUDELIST)) {
701 EXCLUDELIST *pos = excludeList;
702 WCHAR copyFromUpper[MAX_PATH];
703
704 /* Uppercase source filename */
705 lstrcpyW(copyFromUpper, copyFrom);
706 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
707
708 /* Loop through testing each exclude line */
709 while (pos) {
710 if (wcsstr(copyFromUpper, pos->name) != NULL) {
711 WINE_TRACE("Skipping file as matches exclude '%s'\n",
712 wine_dbgstr_w(pos->name));
713 skipFile = TRUE;
714 pos = NULL;
715 } else {
716 pos = pos->next;
717 }
718 }
719 }
720
721 /* Prompt each file if necessary */
722 if (!skipFile && (flags & OPT_SRCPROMPT)) {
723 DWORD count;
724 char answer[10];
725 BOOL answered = FALSE;
726 WCHAR yesChar[2];
727 WCHAR noChar[2];
728
729 /* Read the Y and N characters from the resource file */
730 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
731 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
732
733 while (!answered) {
734 XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
735 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
736 &count, NULL);
737
738 answered = TRUE;
739 if (toupper(answer[0]) == noChar[0])
740 skipFile = TRUE;
741 else if (toupper(answer[0]) != yesChar[0])
742 answered = FALSE;
743 }
744 }
745
746 if (!skipFile &&
747 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
748 DWORD count;
749 char answer[10];
750 BOOL answered = FALSE;
751 WCHAR yesChar[2];
752 WCHAR allChar[2];
753 WCHAR noChar[2];
754
755 /* Read the A,Y and N characters from the resource file */
756 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
757 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
758 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
759
760 while (!answered) {
761 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
762 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
763 &count, NULL);
764
765 answered = TRUE;
766 if (toupper(answer[0]) == allChar[0])
767 flags |= OPT_NOPROMPT;
768 else if (toupper(answer[0]) == noChar[0])
769 skipFile = TRUE;
770 else if (toupper(answer[0]) != yesChar[0])
771 answered = FALSE;
772 }
773 }
774
775 /* See if it has to exist! */
776 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
777 skipFile = TRUE;
778 }
779
780 /* Output a status message */
781 if (!skipFile) {
782 if (flags & OPT_QUIET) {
783 /* Skip message */
784 } else if (flags & OPT_FULL) {
785 const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
786 '%', 's', '\n', 0};
787
788 XCOPY_wprintf(infostr, copyFrom, copyTo);
789 } else {
790 const WCHAR infostr[] = {'%', 's', '\n', 0};
791 XCOPY_wprintf(infostr, copyFrom);
792 }
793
794 /* If allowing overwriting of read only files, remove any
795 write protection */
796 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
797 (flags & OPT_REPLACEREAD)) {
798 SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
799 }
800
801 copiedFile = TRUE;
802 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
803 /* Skip copy */
804 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
805
806 DWORD error = GetLastError();
807 XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
808 copyFrom, copyTo, error);
809 XCOPY_FailMessage(error);
810
811 if (flags & OPT_IGNOREERRORS) {
812 skipFile = TRUE;
813 } else {
814 ret = RC_WRITEERROR;
815 goto cleanup;
816 }
817 }
818
819 /* If /M supplied, remove the archive bit after successful copy */
820 if (!skipFile) {
821 if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
822 (flags & OPT_REMOVEARCH)) {
823 SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
824 }
825 filesCopied++;
826 }
827 }
828 }
829
830 /* Find next file */
831 findres = FindNextFileW(h, finddata);
832 }
833 FindClose(h);
834
835 /* Search 2 - do subdirs */
836 if (flags & OPT_RECURSIVE) {
837 lstrcpyW(inputpath, srcstem);
838 lstrcatW(inputpath, wchr_star);
839 findres = TRUE;
840 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
841
842 h = FindFirstFileW(inputpath, finddata);
843 while (h != INVALID_HANDLE_VALUE && findres) {
844
845 /* Only looking for dirs */
846 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
847 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
848 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
849
850 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
851
852 /* Make up recursive information */
853 lstrcpyW(inputpath, srcstem);
854 lstrcatW(inputpath, finddata->cFileName);
855 lstrcatW(inputpath, wchr_slash);
856
857 lstrcpyW(outputpath, deststem);
858 if (*destspec == 0x00) {
859 lstrcatW(outputpath, finddata->cFileName);
860
861 /* If /E is supplied, create the directory now */
862 if ((flags & OPT_EMPTYDIR) &&
863 !(flags & OPT_SIMULATE))
864 XCOPY_CreateDirectory(outputpath);
865
866 lstrcatW(outputpath, wchr_slash);
867 }
868
869 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
870 }
871
872 /* Find next one */
873 findres = FindNextFileW(h, finddata);
874 }
875 }
876
877 cleanup:
878
879 /* free up memory */
880 HeapFree(GetProcessHeap(), 0, finddata);
881 HeapFree(GetProcessHeap(), 0, inputpath);
882 HeapFree(GetProcessHeap(), 0, outputpath);
883
884 return ret;
885 }
886
887 /* =========================================================================
888 * Routine copied from cmd.exe md command -
889 * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
890 * dir2 if they do not already exist.
891 * ========================================================================= */
892 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
893 {
894 int len;
895 WCHAR *new_path;
896 BOOL ret = TRUE;
897
898 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
899 lstrcpyW(new_path,path);
900
901 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
902 new_path[len - 1] = 0;
903
904 while (!CreateDirectoryW(new_path,NULL))
905 {
906 WCHAR *slash;
907 DWORD last_error = GetLastError();
908 if (last_error == ERROR_ALREADY_EXISTS)
909 break;
910
911 if (last_error != ERROR_PATH_NOT_FOUND)
912 {
913 ret = FALSE;
914 break;
915 }
916
917 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
918 {
919 ret = FALSE;
920 break;
921 }
922
923 len = slash - new_path;
924 new_path[len] = 0;
925 if (!XCOPY_CreateDirectory(new_path))
926 {
927 ret = FALSE;
928 break;
929 }
930 new_path[len] = '\\';
931 }
932 HeapFree(GetProcessHeap(),0,new_path);
933 return ret;
934 }
935
936 /* =========================================================================
937 * Process the /EXCLUDE: file list, building up a list of substrings to
938 * avoid copying
939 * Returns TRUE on any failure
940 * ========================================================================= */
941 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
942
943 WCHAR *filenameStart = parms;
944
945 WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
946 excludeList = NULL;
947
948 while (*parms && *parms != ' ' && *parms != '/') {
949
950 /* If found '+' then process the file found so far */
951 if (*parms == '+') {
952 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
953 return TRUE;
954 }
955 filenameStart = parms+1;
956 }
957 parms++;
958 }
959
960 if (filenameStart != parms) {
961 if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
962 return TRUE;
963 }
964 }
965
966 return FALSE;
967 }
968
969 /* =========================================================================
970 * Process a single file from the /EXCLUDE: file list, building up a list
971 * of substrings to avoid copying
972 * Returns TRUE on any failure
973 * ========================================================================= */
974 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
975
976 WCHAR endChar = *endOfName;
977 WCHAR buffer[MAXSTRING];
978 FILE *inFile = NULL;
979 const WCHAR readTextMode[] = {'r', 't', 0};
980
981 /* Null terminate the filename (temporarily updates the filename hence
982 parms not const) */
983 *endOfName = 0x00;
984
985 /* Open the file */
986 inFile = _wfopen(filename, readTextMode);
987 if (inFile == NULL) {
988 XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
989 *endOfName = endChar;
990 return TRUE;
991 }
992
993 /* Process line by line */
994 while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
995 EXCLUDELIST *thisEntry;
996 int length = lstrlenW(buffer);
997
998 /* Strip CRLF */
999 buffer[length-1] = 0x00;
1000
1001 /* If more than CRLF */
1002 if (length > 1) {
1003 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
1004 thisEntry->next = excludeList;
1005 excludeList = thisEntry;
1006 thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
1007 (length * sizeof(WCHAR))+1);
1008 lstrcpyW(thisEntry->name, buffer);
1009 CharUpperBuffW(thisEntry->name, length);
1010 WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
1011 }
1012 }
1013
1014 /* See if EOF or error occurred */
1015 if (!feof(inFile)) {
1016 XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
1017 *endOfName = endChar;
1018 return TRUE;
1019 }
1020
1021 /* Revert the input string to original form, and cleanup + return */
1022 *endOfName = endChar;
1023 fclose(inFile);
1024 return FALSE;
1025 }
1026
1027 /* =========================================================================
1028 * Load a string from the resource file, handling any error
1029 * Returns string retrieved from resource file
1030 * ========================================================================= */
1031 static WCHAR *XCOPY_LoadMessage(UINT id) {
1032 static WCHAR msg[MAXSTRING];
1033 const WCHAR failedMsg[] = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
1034
1035 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1036 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1037 lstrcpyW(msg, failedMsg);
1038 }
1039 return msg;
1040 }
1041
1042 /* =========================================================================
1043 * Load a string for a system error and writes it to the screen
1044 * Returns string retrieved from resource file
1045 * ========================================================================= */
1046 static void XCOPY_FailMessage(DWORD err) {
1047 LPWSTR lpMsgBuf;
1048 int status;
1049
1050 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
1051 FORMAT_MESSAGE_FROM_SYSTEM,
1052 NULL, err, 0,
1053 (LPWSTR) &lpMsgBuf, 0, NULL);
1054 if (!status) {
1055 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
1056 err, GetLastError());
1057 } else {
1058 const WCHAR infostr[] = {'%', 's', '\n', 0};
1059 XCOPY_wprintf(infostr, lpMsgBuf);
1060 LocalFree ((HLOCAL)lpMsgBuf);
1061 }
1062 }
1063
1064 /* =========================================================================
1065 * Output a formatted unicode string. Ideally this will go to the console
1066 * and hence required WriteConsoleW to output it, however if file i/o is
1067 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1068 * ========================================================================= */
1069 int XCOPY_wprintf(const WCHAR *format, ...) {
1070
1071 static WCHAR *output_bufW = NULL;
1072 static char *output_bufA = NULL;
1073 static BOOL toConsole = TRUE;
1074 static BOOL traceOutput = FALSE;
1075 #define MAX_WRITECONSOLE_SIZE 65535
1076
1077 va_list parms;
1078 DWORD nOut;
1079 int len;
1080 DWORD res = 0;
1081
1082 /*
1083 * Allocate buffer to use when writing to console
1084 * Note: Not freed - memory will be allocated once and released when
1085 * xcopy ends
1086 */
1087
1088 if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
1089 MAX_WRITECONSOLE_SIZE);
1090 if (!output_bufW) {
1091 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1092 return 0;
1093 }
1094
1095 va_start(parms, format);
1096 len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
1097 va_end(parms);
1098 if (len < 0) {
1099 WINE_FIXME("String too long.\n");
1100 return 0;
1101 }
1102
1103 /* Try to write as unicode all the time we think its a console */
1104 if (toConsole) {
1105 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1106 output_bufW, len, &nOut, NULL);
1107 }
1108
1109 /* If writing to console has failed (ever) we assume its file
1110 i/o so convert to OEM codepage and output */
1111 if (!res) {
1112 BOOL usedDefaultChar = FALSE;
1113 DWORD convertedChars;
1114
1115 toConsole = FALSE;
1116
1117 /*
1118 * Allocate buffer to use when writing to file. Not freed, as above
1119 */
1120 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1121 MAX_WRITECONSOLE_SIZE);
1122 if (!output_bufA) {
1123 WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
1124 return 0;
1125 }
1126
1127 /* Convert to OEM, then output */
1128 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
1129 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1130 "?", &usedDefaultChar);
1131 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1132 &nOut, FALSE);
1133 }
1134
1135 /* Trace whether screen or console */
1136 if (!traceOutput) {
1137 WINE_TRACE("Writing to console? (%d)\n", toConsole);
1138 traceOutput = TRUE;
1139 }
1140 return nOut;
1141 }