~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Wine Cross Reference
wine/programs/xcopy/xcopy.c

Version: ~ [ wine-1.1.3 ] ~ [ wine-1.1.2 ] ~ [ wine-1.1.1 ] ~ [ wine-1.1.0 ] ~ [ wine-1.0 ] ~ [ wine-1.0-rc5 ] ~ [ wine-1.0-rc4 ] ~ [ wine-1.0-rc3 ] ~ [ wine-1.0-rc2 ] ~ [ wine-1.0-rc1 ] ~ [ wine-0.9.61 ] ~ [ wine-0.9.60 ] ~ [ wine-0.9.59 ] ~ [ wine-0.9.58 ] ~ [ wine-0.9.57 ] ~ [ wine-0.9.56 ] ~ [ wine-0.9.55 ] ~ [ wine-0.9.54 ] ~ [ wine-0.9.53 ] ~ [ wine-0.9.52 ] ~ [ wine-0.9.51 ] ~ [ wine-0.9.50 ] ~ [ wine-0.9.49 ] ~ [ wine-0.9.48 ] ~ [ wine-0.9.47 ] ~ [ wine-0.9.46 ] ~ [ wine-0.9.45 ] ~ [ wine-0.9.44 ] ~ [ wine-0.9.43 ] ~ [ wine-0.9.42 ] ~ [ wine-0.9.41 ] ~ [ wine-0.9.40 ] ~ [ wine-0.9.39 ] ~ [ wine-0.9.38 ] ~ [ wine-0.9.37 ] ~ [ wine-0.9.36 ] ~ [ wine-0.9.35 ] ~ [ wine-0.9.34 ] ~ [ wine-0.9.33 ] ~ [ wine-0.9.32 ] ~ [ wine-0.9.31 ] ~ [ wine-0.9.30 ] ~ [ wine-0.9.29 ] ~ [ wine-0.9.28 ] ~ [ wine-0.9.27 ] ~ [ wine-0.9.26 ] ~ [ wine-0.9.25 ] ~ [ wine-0.9.24 ] ~ [ wine-0.9.23 ] ~ [ wine-0.9.22 ] ~ [ wine-0.9.21 ] ~ [ wine-0.9.20 ] ~ [ wine-0.9.19 ] ~ [ wine-0.9.18 ] ~ [ wine-0.9.17 ] ~ [ wine-0.9.16 ] ~ [ wine-0.9.15 ] ~ [ wine-0.9.14 ] ~ [ wine-0.9.13 ] ~ [ wine-0.9.12 ] ~ [ wine-0.9.11 ] ~ [ wine-0.9.10 ] ~ [ wine-0.9.9 ] ~ [ wine-0.9.8 ] ~ [ wine-0.9.7 ] ~ [ wine-0.9.6 ] ~ [ wine-0.9.5 ] ~ [ wine-0.9.4 ] ~ [ wine-0.9.3 ] ~ [ wine-0.9.2 ] ~ [ wine-0.9.1 ] ~ [ wine-0.9 ] ~ [ wine20050930 ] ~ [ wine20050830 ] ~ [ wine20050725 ] ~ [ wine20050628 ] ~ [ wine20050524 ] ~ [ wine20050419 ] ~ [ wine20050310 ] ~ [ wine20050211 ] ~ [ wine20050111 ] ~ [ wine20041201 ] ~ [ wine20041019 ] ~ [ wine20040914 ] ~ [ wine20040813 ] ~ [ wine20040716 ] ~ [ wine20040615 ] ~ [ wine20040505 ] ~ [ wine20040408 ] ~ [ wine20040309 ] ~ [ wine20040213 ] ~ [ wine20040121 ] ~ [ wine20031212 ] ~ [ wine20031118 ] ~ [ wine20031016 ] ~ [ wine20030911 ] ~ [ wine20030813 ] ~ [ wine20030709 ] ~ [ wine20030618 ] ~ [ wine20030508 ] ~ [ wine20030408 ] ~ [ wine20030318 ] ~ [ wine20030219 ] ~ [ wine20030115 ] ~ [ wine20021219 ] ~ [ wine20021125 ] ~ [ wine20021031 ] ~ [ wine20021007 ] ~ [ wine20020904 ] ~ [ wine20020804 ] ~ [ wine20020710 ] ~ [ wine20020605 ] ~ [ wine20020509 ] ~ [ wine20020411 ] ~ [ wine20020310 ] ~ [ wine20020228 ] ~ [ wine20011226 ] ~ [ wine20011108 ] ~ [ wine20011004 ] ~ [ wine20010824 ] ~ [ wine20010731 ] ~ [ wine20010629 ] ~ [ wine20010510 ] ~ [ wine20010418 ] ~ [ wine20010326 ] ~ [ wine20010305 ] ~ [ wine20010216 ] ~ [ wine20010112 ] ~ [ wine20001222 ] ~ [ wine20001202 ] ~ [ wine20001026 ] ~ [ wine20001002 ] ~ [ wine20000909 ] ~ [ wine20000821 ] ~ [ wine20000801 ] ~ [ wine20000716 ] ~ [ wine20000326 ] ~ [ wine20000227 ] ~ [ wine20000130 ] ~ [ wine20000109 ] ~

  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)