xcopy: Rearrange code to closely match windows ordering.
[wine] / programs / 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  * Notes:
23  * Apparently, valid return codes are:
24  *   0 - OK
25  *   1 - No files found to copy
26  *   2 - CTRL+C during copy
27  *   4 - Initialization error, or invalid source specification
28  *   5 - Disk write error
29  */
30
31
32 #include <stdio.h>
33 #include <windows.h>
34 #include <wine/debug.h>
35
36 /* Local #defines */
37 #define RC_OK         0
38 #define RC_NOFILES    1
39 #define RC_CTRLC      2
40 #define RC_INITERROR  4
41 #define RC_WRITEERROR 5
42
43 #define OPT_ASSUMEDIR    0x00000001
44 #define OPT_RECURSIVE    0x00000002
45 #define OPT_EMPTYDIR     0x00000004
46 #define OPT_QUIET        0x00000008
47 #define OPT_FULL         0x00000010
48 #define OPT_SIMULATE     0x00000020
49 #define OPT_PAUSE        0x00000040
50 #define OPT_NOCOPY       0x00000080
51 #define OPT_NOPROMPT     0x00000100
52 #define OPT_SHORTNAME    0x00000200
53 #define OPT_MUSTEXIST    0x00000400
54 #define OPT_REPLACEREAD  0x00000800
55 #define OPT_COPYHIDSYS   0x00001000
56 #define OPT_IGNOREERRORS 0x00002000
57 #define OPT_SRCPROMPT    0x00004000
58 #define OPT_ARCHIVEONLY  0x00008000
59 #define OPT_REMOVEARCH   0x00010000
60 #define OPT_EXCLUDELIST  0x00020000
61 #define OPT_DATERANGE    0x00040000
62 #define OPT_DATENEWER    0x00080000
63
64 #define MAXSTRING 8192
65
66 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
67
68 /* Prototypes */
69 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
70 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
71                                  WCHAR *spec, WCHAR *srcspec, DWORD flags);
72 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
73                         WCHAR *deststem, WCHAR *destspec,
74                         DWORD flags);
75 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
76 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
77 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
78
79 /* Typedefs */
80 typedef struct _EXCLUDELIST
81 {
82   struct _EXCLUDELIST *next;
83   WCHAR               *name;
84 } EXCLUDELIST;
85
86
87 /* Global variables */
88 static ULONG filesCopied           = 0;              /* Number of files copied  */
89 static EXCLUDELIST *excludeList    = NULL;           /* Excluded strings list   */
90 static FILETIME dateRange;                           /* Date range to copy after*/
91 static const WCHAR wchr_slash[]   = {'\\', 0};
92 static const WCHAR wchr_star[]    = {'*', 0};
93 static const WCHAR wchr_dot[]     = {'.', 0};
94 static const WCHAR wchr_dotdot[]  = {'.', '.', 0};
95
96 /* Constants (Mostly for widechars) */
97
98
99 /* To minimize stack usage during recursion, some temporary variables
100    made global                                                        */
101 static WCHAR copyFrom[MAX_PATH];
102 static WCHAR copyTo[MAX_PATH];
103
104
105 /* =========================================================================
106    main - Main entrypoint for the xcopy command
107
108      Processes the args, and drives the actual copying
109    ========================================================================= */
110 int main (int argc, char *argv[])
111 {
112     int     rc = 0;
113     WCHAR   suppliedsource[MAX_PATH] = {0};   /* As supplied on the cmd line */
114     WCHAR   supplieddestination[MAX_PATH] = {0};
115     WCHAR   sourcestem[MAX_PATH] = {0};       /* Stem of source          */
116     WCHAR   sourcespec[MAX_PATH] = {0};       /* Filespec of source      */
117     WCHAR   destinationstem[MAX_PATH] = {0};  /* Stem of destination     */
118     WCHAR   destinationspec[MAX_PATH] = {0};  /* Filespec of destination */
119     WCHAR   copyCmd[MAXSTRING];               /* COPYCMD env var         */
120     DWORD   flags = 0;                        /* Option flags            */
121     LPWSTR *argvW = NULL;
122     const WCHAR PROMPTSTR1[]  = {'/', 'Y', 0};
123     const WCHAR PROMPTSTR2[]  = {'/', 'y', 0};
124     const WCHAR COPYCMD[]  = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
125     const WCHAR EXCLUDE[]  = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
126
127     /*
128      * Parse the command line
129      */
130
131     /* overwrite the command line */
132     argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
133
134     /* Confirm at least one parameter */
135     if (argc < 2) {
136         printf("Invalid number of parameters - Use xcopy /? for help\n");
137         return RC_INITERROR;
138     }
139
140     /* Preinitialize flags based on COPYCMD */
141     if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
142         if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
143             wcsstr(copyCmd, PROMPTSTR2) != NULL) {
144             flags |= OPT_NOPROMPT;
145         }
146     }
147
148     /* Skip first arg, which is the program name */
149     argvW++;
150
151     while (argc > 1)
152     {
153         argc--;
154         WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
155
156         /* First non-switch parameter is source, second is destination */
157         if (*argvW[0] != '/') {
158             if (suppliedsource[0] == 0x00) {
159                 lstrcpyW(suppliedsource, *argvW);
160             } else if (supplieddestination[0] == 0x00) {
161                 lstrcpyW(supplieddestination, *argvW);
162             } else {
163                 printf("Invalid number of parameters - Use xcopy /? for help\n");
164                 return RC_INITERROR;
165             }
166         } else {
167             /* Process all the switch options
168                  Note: Windows docs say /P prompts when dest is created
169                        but tests show it is done for each src file
170                        regardless of the destination                   */
171             switch (toupper(argvW[0][1])) {
172             case 'I': flags |= OPT_ASSUMEDIR;     break;
173             case 'S': flags |= OPT_RECURSIVE;     break;
174             case 'Q': flags |= OPT_QUIET;         break;
175             case 'F': flags |= OPT_FULL;          break;
176             case 'L': flags |= OPT_SIMULATE;      break;
177             case 'W': flags |= OPT_PAUSE;         break;
178             case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
179             case 'Y': flags |= OPT_NOPROMPT;      break;
180             case 'N': flags |= OPT_SHORTNAME;     break;
181             case 'U': flags |= OPT_MUSTEXIST;     break;
182             case 'R': flags |= OPT_REPLACEREAD;   break;
183             case 'H': flags |= OPT_COPYHIDSYS;    break;
184             case 'C': flags |= OPT_IGNOREERRORS;  break;
185             case 'P': flags |= OPT_SRCPROMPT;     break;
186             case 'A': flags |= OPT_ARCHIVEONLY;   break;
187             case 'M': flags |= OPT_ARCHIVEONLY |
188                                OPT_REMOVEARCH;    break;
189
190             /* E can be /E or /EXCLUDE */
191             case 'E': if (CompareString (LOCALE_USER_DEFAULT,
192                                          NORM_IGNORECASE | SORT_STRINGSORT,
193                                          &argvW[0][1], 8,
194                                          EXCLUDE, -1) == 2) {
195                         if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
196                           LPWSTR lpMsgBuf;
197                           int status;
198
199                           status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
200                                                   FORMAT_MESSAGE_FROM_SYSTEM,
201                                                   NULL, ERROR_INVALID_PARAMETER, 0,
202                                                   (LPTSTR) &lpMsgBuf, 0, NULL);
203                           if (!status) {
204                             WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
205                                        ERROR_INVALID_PARAMETER, GetLastError());
206                           } else {
207                             printf("%S\n", lpMsgBuf);
208                             LocalFree ((HLOCAL)lpMsgBuf);
209                           }
210                           return RC_INITERROR;
211                         } else flags |= OPT_EXCLUDELIST;
212                       } else flags |= OPT_EMPTYDIR;
213                       break;
214
215             /* D can be /D or /D: */
216             case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
217                           SYSTEMTIME st;
218                           WCHAR     *pos = &argvW[0][3];
219                           BOOL       isError = FALSE;
220                           memset(&st, 0x00, sizeof(st));
221
222                           /* Parse the arg : Month */
223                           st.wMonth = _wtol(pos);
224                           while (*pos && isdigit(*pos)) pos++;
225                           if (*pos++ != '-') isError = TRUE;
226
227                           /* Parse the arg : Day */
228                           if (!isError) {
229                               st.wDay = _wtol(pos);
230                               while (*pos && isdigit(*pos)) pos++;
231                               if (*pos++ != '-') isError = TRUE;
232                           }
233
234                           /* Parse the arg : Day */
235                           if (!isError) {
236                               st.wYear = _wtol(pos);
237                               if (st.wYear < 100) st.wYear+=2000;
238                           }
239
240                           if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
241                               SYSTEMTIME st;
242                               WCHAR datestring[32], timestring[32];
243
244                               flags |= OPT_DATERANGE;
245
246                               /* Debug info: */
247                               FileTimeToSystemTime (&dateRange, &st);
248                               GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
249                                           sizeof(datestring));
250                               GetTimeFormat (0, TIME_NOSECONDS, &st,
251                                           NULL, timestring, sizeof(timestring));
252
253                               WINE_TRACE("Date being used is: %s %s\n",
254                                          wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
255                           } else {
256                               LPWSTR lpMsgBuf;
257                               int status;
258
259                               status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
260                                                       FORMAT_MESSAGE_FROM_SYSTEM,
261                                                       NULL, ERROR_INVALID_PARAMETER, 0,
262                                                       (LPTSTR) &lpMsgBuf, 0, NULL);
263                               if (!status) {
264                                 WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
265                                            ERROR_INVALID_PARAMETER, GetLastError());
266                               } else {
267                                 printf("%S\n", lpMsgBuf);
268                                 LocalFree ((HLOCAL)lpMsgBuf);
269                               }
270                               return RC_INITERROR;
271                           }
272                       } else {
273                           flags |= OPT_DATENEWER;
274                       }
275                       break;
276
277             case '-': if (toupper(argvW[0][2])=='Y')
278                           flags &= ~OPT_NOPROMPT; break;
279             default:
280               WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
281             }
282         }
283         argvW++;
284     }
285
286     /* Default the destination if not supplied */
287     if (supplieddestination[0] == 0x00)
288         lstrcpyW(supplieddestination, wchr_dot);
289
290     /* Trace out the supplied information */
291     WINE_TRACE("Supplied parameters:\n");
292     WINE_TRACE("Source      : '%s'\n", wine_dbgstr_w(suppliedsource));
293     WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
294
295     /* Extract required information from source specification */
296     rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
297
298     /* Extract required information from destination specification */
299     rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
300                                destinationspec, sourcespec, flags);
301
302     /* Trace out the resulting information */
303     WINE_TRACE("Resolved parameters:\n");
304     WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
305     WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
306     WINE_TRACE("Dest   Stem : '%s'\n", wine_dbgstr_w(destinationstem));
307     WINE_TRACE("Dest   Spec : '%s'\n", wine_dbgstr_w(destinationspec));
308
309     /* Pause if necessary */
310     if (flags & OPT_PAUSE) {
311         DWORD count;
312         char pausestr[10];
313
314         printf("Press <enter> to begin copying\n");
315         ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
316                   &count, NULL);
317     }
318
319     /* Now do the hard work... */
320     rc = XCOPY_DoCopy(sourcestem, sourcespec,
321                 destinationstem, destinationspec,
322                 flags);
323
324     /* Clear up exclude list allocated memory */
325     while (excludeList) {
326         EXCLUDELIST *pos = excludeList;
327         excludeList = excludeList -> next;
328         HeapFree(GetProcessHeap(), 0, pos->name);
329         HeapFree(GetProcessHeap(), 0, pos);
330     }
331
332     /* Finished - print trailer and exit */
333     if (flags & OPT_SIMULATE) {
334         printf("%d file(s) would be copied\n", filesCopied);
335     } else if (!(flags & OPT_NOCOPY)) {
336         printf("%d file(s) copied\n", filesCopied);
337     }
338     if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
339     return rc;
340
341 }
342
343
344 /* =========================================================================
345    XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
346      converts it into a stem and a filespec
347    ========================================================================= */
348 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
349 {
350     WCHAR             actualsource[MAX_PATH];
351     WCHAR            *starPos;
352     WCHAR            *questPos;
353
354     /*
355      * Validate the source, expanding to full path ensuring it exists
356      */
357     if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
358         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
359         return RC_INITERROR;
360     }
361
362     /*
363      * Work out the stem of the source
364      */
365
366     /* If no wildcard were supplied then the source is either a single
367        file or a directory - in which case thats the stem of the search,
368        otherwise split off the wildcards and use the higher level as the
369        stem                                                              */
370     lstrcpyW(stem, actualsource);
371     starPos = wcschr(stem, '*');
372     questPos = wcschr(stem, '?');
373     if (starPos || questPos) {
374         WCHAR *lastDir;
375
376         if (starPos) *starPos = 0x00;
377         if (questPos) *questPos = 0x00;
378
379         lastDir = wcsrchr(stem, '\\');
380         if (lastDir) *(lastDir+1) = 0x00;
381         else {
382             WINE_FIXME("Unexpected syntax error in source parameter\n");
383             return RC_INITERROR;
384         }
385         lstrcpyW(spec, actualsource + (lastDir - stem)+1);
386     } else {
387
388         DWORD attribs = GetFileAttributes(actualsource);
389
390         if (attribs == INVALID_FILE_ATTRIBUTES) {
391             LPWSTR lpMsgBuf;
392             DWORD lastError = GetLastError();
393             int status;
394             status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
395                                     FORMAT_MESSAGE_FROM_SYSTEM,
396                                     NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
397             printf("%S\n", lpMsgBuf);
398             return RC_INITERROR;
399
400         /* Directory: */
401         } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
402             lstrcatW(stem, wchr_slash);
403             lstrcpyW(spec, wchr_star);
404
405         /* File: */
406         } else {
407             WCHAR drive[MAX_PATH];
408             WCHAR dir[MAX_PATH];
409             WCHAR fname[MAX_PATH];
410             WCHAR ext[MAX_PATH];
411             _wsplitpath(actualsource, drive, dir, fname, ext);
412             lstrcpyW(stem, drive);
413             lstrcatW(stem, dir);
414             lstrcpyW(spec, fname);
415             lstrcatW(spec, ext);
416         }
417     }
418     return RC_OK;
419 }
420
421 /* =========================================================================
422    XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
423      converts it into a stem
424    ========================================================================= */
425 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
426                                  WCHAR *srcspec, DWORD flags)
427 {
428     WCHAR  actualdestination[MAX_PATH];
429     DWORD attribs;
430     BOOL isDir = FALSE;
431
432     /*
433      * Validate the source, expanding to full path ensuring it exists
434      */
435     if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
436         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
437         return RC_INITERROR;
438     }
439
440     /* Destination is either a directory or a file */
441     attribs = GetFileAttributes(actualdestination);
442
443     if (attribs == INVALID_FILE_ATTRIBUTES) {
444
445         /* If /I supplied and wildcard copy, assume directory */
446         if (flags & OPT_ASSUMEDIR &&
447             (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
448
449             isDir = TRUE;
450
451         } else {
452             DWORD count;
453             char  answer[10] = "";
454
455             while (answer[0] != 'F' && answer[0] != 'D') {
456                 printf("Is %S a filename or directory\n"
457                        "on the target?\n"
458                        "(F - File, D - Directory)\n", supplieddestination);
459
460                 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
461                 WINE_TRACE("User answer %c\n", answer[0]);
462
463                 answer[0] = toupper(answer[0]);
464             }
465
466             if (answer[0] == 'D') {
467                 isDir = TRUE;
468             } else {
469                 isDir = FALSE;
470             }
471         }
472     } else {
473         isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
474     }
475
476     if (isDir) {
477         lstrcpyW(stem, actualdestination);
478         *spec = 0x00;
479
480         /* Ensure ends with a '\' */
481         if (stem[lstrlenW(stem)-1] != '\\') {
482             lstrcatW(stem, wchr_slash);
483         }
484
485     } else {
486         WCHAR drive[MAX_PATH];
487         WCHAR dir[MAX_PATH];
488         WCHAR fname[MAX_PATH];
489         WCHAR ext[MAX_PATH];
490         _wsplitpath(actualdestination, drive, dir, fname, ext);
491         lstrcpyW(stem, drive);
492         lstrcatW(stem, dir);
493         lstrcpyW(spec, fname);
494         lstrcatW(spec, ext);
495     }
496     return RC_OK;
497 }
498
499 /* =========================================================================
500    XCOPY_DoCopy - Recursive function to copy files based on input parms
501      of a stem and a spec
502
503       This works by using FindFirstFile supplying the source stem and spec.
504       If results are found, any non-directory ones are processed
505       Then, if /S or /E is supplied, another search is made just for
506       directories, and this function is called again for that directory
507
508    ========================================================================= */
509 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
510                         WCHAR *deststem, WCHAR *destspec,
511                         DWORD flags)
512 {
513     WIN32_FIND_DATA *finddata;
514     HANDLE          h;
515     BOOL            findres = TRUE;
516     WCHAR           *inputpath, *outputpath;
517     BOOL            copiedFile = FALSE;
518     DWORD           destAttribs, srcAttribs;
519     BOOL            skipFile;
520     LPVOID          lpMsgBuf;
521     DWORD           error_code;
522     int             status;
523
524     /* Allocate some working memory on heap to minimize footprint */
525     finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
526     inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
527     outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
528
529     /* Build the search info into a single parm */
530     lstrcpyW(inputpath, srcstem);
531     lstrcatW(inputpath, srcspec);
532
533     /* Search 1 - Look for matching files */
534     h = FindFirstFile(inputpath, finddata);
535     while (h != INVALID_HANDLE_VALUE && findres) {
536
537         skipFile = FALSE;
538
539         /* Ignore . and .. */
540         if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
541             lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
542             finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
543
544             WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
545         } else {
546
547             /* Get the filename information */
548             lstrcpyW(copyFrom, srcstem);
549             if (flags & OPT_SHORTNAME) {
550               lstrcatW(copyFrom, finddata->cAlternateFileName);
551             } else {
552               lstrcatW(copyFrom, finddata->cFileName);
553             }
554
555             lstrcpyW(copyTo, deststem);
556             if (*destspec == 0x00) {
557                 if (flags & OPT_SHORTNAME) {
558                     lstrcatW(copyTo, finddata->cAlternateFileName);
559                 } else {
560                     lstrcatW(copyTo, finddata->cFileName);
561                 }
562             } else {
563                 lstrcatW(copyTo, destspec);
564             }
565
566             /* Do the copy */
567             WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
568                                                       wine_dbgstr_w(copyTo));
569             if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
570
571             /* See if allowed to copy it */
572             srcAttribs = GetFileAttributesW(copyFrom);
573             WINE_TRACE("Source attribs: %d\n", srcAttribs);
574
575             if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
576                 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
577
578                 if (!(flags & OPT_COPYHIDSYS)) {
579                     skipFile = TRUE;
580                 }
581             }
582
583             if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
584                 (flags & OPT_ARCHIVEONLY)) {
585                 skipFile = TRUE;
586             }
587
588             /* See if file exists */
589             destAttribs = GetFileAttributesW(copyTo);
590             WINE_TRACE("Dest attribs: %d\n", srcAttribs);
591
592             /* Check date ranges if a destination file already exists */
593             if (!skipFile && (flags & OPT_DATERANGE) &&
594                 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
595                 WINE_TRACE("Skipping file as modified date too old\n");
596                 skipFile = TRUE;
597             }
598
599             /* If just /D supplied, only overwrite if src newer than dest */
600             if (!skipFile && (flags & OPT_DATENEWER) &&
601                (destAttribs != INVALID_FILE_ATTRIBUTES)) {
602                 HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
603                                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
604                                       NULL);
605                 if (h != INVALID_HANDLE_VALUE) {
606                     FILETIME writeTime;
607                     GetFileTime(h, NULL, NULL, &writeTime);
608
609                     if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
610                         WINE_TRACE("Skipping file as dest newer or same date\n");
611                         skipFile = TRUE;
612                     }
613                     CloseHandle(h);
614                 }
615             }
616
617             /* See if exclude list provided. Note since filenames are case
618                insensitive, need to uppercase the filename before doing
619                strstr                                                     */
620             if (!skipFile && (flags & OPT_EXCLUDELIST)) {
621                 EXCLUDELIST *pos = excludeList;
622                 WCHAR copyFromUpper[MAX_PATH];
623
624                 /* Uppercase source filename */
625                 lstrcpyW(copyFromUpper, copyFrom);
626                 CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
627
628                 /* Loop through testing each exclude line */
629                 while (pos) {
630                     if (wcsstr(copyFromUpper, pos->name) != NULL) {
631                         WINE_TRACE("Skipping file as matches exclude '%s'\n",
632                                    wine_dbgstr_w(pos->name));
633                         skipFile = TRUE;
634                         pos = NULL;
635                     } else {
636                         pos = pos->next;
637                     }
638                 }
639             }
640
641             /* Prompt each file if necessary */
642             if (!skipFile && (flags & OPT_SRCPROMPT)) {
643                 DWORD count;
644                 char  answer[10];
645                 BOOL  answered = FALSE;
646
647                 while (!answered) {
648                     printf("%S? (Yes|No)\n", copyFrom);
649                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
650                               &count, NULL);
651
652                     answered = TRUE;
653                     if (toupper(answer[0]) == 'N')
654                         skipFile = TRUE;
655                     else if (toupper(answer[0]) != 'Y')
656                         answered = FALSE;
657                 }
658             }
659
660             if (!skipFile &&
661                 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
662                 DWORD count;
663                 char  answer[10];
664                 BOOL  answered = FALSE;
665
666                 while (!answered) {
667                     printf("Overwrite %S? (Yes|No|All)\n", copyTo);
668                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
669                               &count, NULL);
670
671                     answered = TRUE;
672                     if (toupper(answer[0]) == 'A')
673                         flags |= OPT_NOPROMPT;
674                     else if (toupper(answer[0]) == 'N')
675                         skipFile = TRUE;
676                     else if (toupper(answer[0]) != 'Y')
677                         answered = FALSE;
678                 }
679             }
680
681             /* See if it has to exist! */
682             if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
683                 skipFile = TRUE;
684             }
685
686             /* Output a status message */
687             if (!skipFile) {
688                 if (flags & OPT_QUIET) {
689                     /* Skip message */
690                 } else if (flags & OPT_FULL) {
691                     printf("%S -> %S\n", copyFrom, copyTo);
692                 } else {
693                     printf("%S\n", copyFrom);
694                 }
695
696                 /* If allowing overwriting of read only files, remove any
697                    write protection                                       */
698                 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
699                     (flags & OPT_REPLACEREAD)) {
700                     SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
701                 }
702
703                 copiedFile = TRUE;
704                 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
705                     /* Skip copy */
706                 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
707                     printf("Copying of '%S' to '%S' failed with r/c %d\n",
708                            copyFrom, copyTo, GetLastError());
709
710                     error_code = GetLastError ();
711                     status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
712                                             FORMAT_MESSAGE_FROM_SYSTEM,
713                                             NULL, error_code, 0,
714                                             (LPTSTR) &lpMsgBuf, 0, NULL);
715                     if (!status) {
716                         WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
717                                    error_code, GetLastError());
718                     } else {
719                         printf("%S\n", lpMsgBuf);
720                         LocalFree ((HLOCAL)lpMsgBuf);
721                     }
722                     if (flags & OPT_IGNOREERRORS) {
723                         skipFile = TRUE;
724                     } else {
725                         return RC_WRITEERROR;
726                     }
727                 }
728
729                 /* If /M supplied, remove the archive bit after successful copy */
730                 if (!skipFile) {
731                     if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
732                         (flags & OPT_REMOVEARCH)) {
733                         SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
734                     }
735                     filesCopied++;
736                 }
737             }
738         }
739
740         /* Find next file */
741         findres = FindNextFile(h, finddata);
742     }
743     FindClose(h);
744
745     /* Search 2 - do subdirs */
746     if (flags & OPT_RECURSIVE) {
747         lstrcpyW(inputpath, srcstem);
748         lstrcatW(inputpath, wchr_star);
749         findres = TRUE;
750         WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
751
752         h = FindFirstFile(inputpath, finddata);
753         while (h != INVALID_HANDLE_VALUE && findres) {
754
755             /* Only looking for dirs */
756             if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
757                 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
758                 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
759
760                 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
761
762                 /* Make up recursive information */
763                 lstrcpyW(inputpath, srcstem);
764                 lstrcatW(inputpath, finddata->cFileName);
765                 lstrcatW(inputpath, wchr_slash);
766
767                 lstrcpyW(outputpath, deststem);
768                 if (*destspec == 0x00) {
769                     lstrcatW(outputpath, finddata->cFileName);
770
771                     /* If /E is supplied, create the directory now */
772                     if ((flags & OPT_EMPTYDIR) &&
773                         !(flags & OPT_SIMULATE))
774                         XCOPY_CreateDirectory(outputpath);
775
776                     lstrcatW(outputpath, wchr_slash);
777                 }
778
779                 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
780             }
781
782             /* Find next one */
783             findres = FindNextFile(h, finddata);
784         }
785     }
786
787     /* free up memory */
788     HeapFree(GetProcessHeap(), 0, finddata);
789     HeapFree(GetProcessHeap(), 0, inputpath);
790     HeapFree(GetProcessHeap(), 0, outputpath);
791
792     return 0;
793 }
794
795 /* =========================================================================
796  * Routine copied from cmd.exe md command -
797  * This works recursivly. 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         printf("Failed to open '%S'\n", 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         thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
910         thisEntry->next = excludeList;
911         excludeList = thisEntry;
912         thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
913                                     (length * sizeof(WCHAR))+1);
914         lstrcpyW(thisEntry->name, buffer);
915         CharUpperBuff(thisEntry->name, length);
916         WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
917     }
918
919     /* See if EOF or error occurred */
920     if (!feof(inFile)) {
921         printf("Failed during reading of '%S'\n", filename);
922         *endOfName = endChar;
923         return TRUE;
924     }
925
926     /* Revert the input string to original form, and cleanup + return */
927     *endOfName = endChar;
928     fclose(inFile);
929     return FALSE;
930 }