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