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