xcopy: Add support for /U (target must exist).
[wine.git] / programs / xcopy / xcopy.c
blob2f2e7f91c84d69542c5348816a84764dd32e19cb
1 /*
2 * XCOPY - Wine-compatible xcopy program
4 * Copyright (C) 2007 J. Edmeades
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * Notes:
23 * Apparently, valid return codes are:
24 * 0 - OK
25 * 1 - No files found to copy
26 * 2 - CTRL+C during copy
27 * 4 - Initialization error, or invalid source specification
28 * 5 - Disk write error
32 #include <stdio.h>
33 #include <windows.h>
34 #include <wine/debug.h>
36 /* Local #defines */
37 #define RC_OK 0
38 #define RC_NOFILES 1
39 #define RC_CTRLC 2
40 #define RC_INITERROR 4
41 #define RC_WRITEERROR 5
43 #define OPT_ASSUMEDIR 0x00000001
44 #define OPT_RECURSIVE 0x00000002
45 #define OPT_EMPTYDIR 0x00000004
46 #define OPT_QUIET 0x00000008
47 #define OPT_FULL 0x00000010
48 #define OPT_SIMULATE 0x00000020
49 #define OPT_PAUSE 0x00000040
50 #define OPT_NOCOPY 0x00000080
51 #define OPT_NOPROMPT 0x00000100
52 #define OPT_SHORTNAME 0x00000200
53 #define OPT_MUSTEXIST 0x00000400
55 #define MAXSTRING 8192
57 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
59 /* Prototypes */
60 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
61 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
62 WCHAR *spec, WCHAR *srcspec, DWORD flags);
63 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
64 WCHAR *deststem, WCHAR *destspec,
65 DWORD flags);
66 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
68 /* Global variables */
69 static ULONG filesCopied = 0; /* Number of files copied */
70 static const WCHAR wchr_slash[] = {'\\', 0};
71 static const WCHAR wchr_star[] = {'*', 0};
72 static const WCHAR wchr_dot[] = {'.', 0};
73 static const WCHAR wchr_dotdot[] = {'.', '.', 0};
75 /* Constants (Mostly for widechars) */
78 /* To minimize stack usage during recursion, some temporary variables
79 made global */
80 static WCHAR copyFrom[MAX_PATH];
81 static WCHAR copyTo[MAX_PATH];
84 /* =========================================================================
85 main - Main entrypoint for the xcopy command
87 Processes the args, and drives the actual copying
88 ========================================================================= */
89 int main (int argc, char *argv[])
91 int rc = 0;
92 WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
93 WCHAR supplieddestination[MAX_PATH] = {0};
94 WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
95 WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
96 WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
97 WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
98 WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
99 DWORD flags = 0; /* Option flags */
100 LPWSTR *argvW = NULL;
101 const WCHAR PROMPTSTR1[] = {'/', 'Y', 0};
102 const WCHAR PROMPTSTR2[] = {'/', 'y', 0};
103 const WCHAR COPYCMD[] = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
106 * Parse the command line
109 /* overwrite the command line */
110 argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
112 /* Confirm at least one parameter */
113 if (argc < 2) {
114 printf("Invalid number of parameters - Use xcopy /? for help\n");
115 return RC_INITERROR;
118 /* Preinitialize flags based on COPYCMD */
119 if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
120 if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
121 wcsstr(copyCmd, PROMPTSTR2) != NULL) {
122 flags |= OPT_NOPROMPT;
126 /* Skip first arg, which is the program name */
127 argvW++;
129 while (argc > 1)
131 argc--;
132 WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
134 /* First non-switch parameter is source, second is destination */
135 if (*argvW[0] != '/') {
136 if (suppliedsource[0] == 0x00) {
137 lstrcpyW(suppliedsource, *argvW);
138 } else if (supplieddestination[0] == 0x00) {
139 lstrcpyW(supplieddestination, *argvW);
140 } else {
141 printf("Invalid number of parameters - Use xcopy /? for help\n");
142 return RC_INITERROR;
144 } else {
145 /* Process all the switch options */
146 switch (toupper(argvW[0][1])) {
147 case 'I': flags |= OPT_ASSUMEDIR; break;
148 case 'S': flags |= OPT_RECURSIVE; break;
149 case 'E': flags |= OPT_EMPTYDIR; break;
150 case 'Q': flags |= OPT_QUIET; break;
151 case 'F': flags |= OPT_FULL; break;
152 case 'L': flags |= OPT_SIMULATE; break;
153 case 'W': flags |= OPT_PAUSE; break;
154 case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
155 case 'Y': flags |= OPT_NOPROMPT; break;
156 case 'N': flags |= OPT_SHORTNAME; break;
157 case 'U': flags |= OPT_MUSTEXIST; break;
158 case '-': if (toupper(argvW[0][2])=='Y')
159 flags &= ~OPT_NOPROMPT; break;
160 default:
161 WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
164 argvW++;
167 /* Default the destination if not supplied */
168 if (supplieddestination[0] == 0x00)
169 lstrcpyW(supplieddestination, wchr_dot);
171 /* Trace out the supplied information */
172 WINE_TRACE("Supplied parameters:\n");
173 WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
174 WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
176 /* Extract required information from source specification */
177 rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
179 /* Extract required information from destination specification */
180 rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
181 destinationspec, sourcespec, flags);
183 /* Trace out the resulting information */
184 WINE_TRACE("Resolved parameters:\n");
185 WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
186 WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
187 WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
188 WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
190 /* Pause if necessary */
191 if (flags & OPT_PAUSE) {
192 DWORD count;
193 char pausestr[10];
195 printf("Press <enter> to begin copying\n");
196 ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
197 &count, NULL);
200 /* Now do the hard work... */
201 rc = XCOPY_DoCopy(sourcestem, sourcespec,
202 destinationstem, destinationspec,
203 flags);
205 /* Finished - print trailer and exit */
206 if (flags & OPT_SIMULATE) {
207 printf("%d file(s) would be copied\n", filesCopied);
208 } else if (!(flags & OPT_NOCOPY)) {
209 printf("%d file(s) copied\n", filesCopied);
211 if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
212 return rc;
217 /* =========================================================================
218 XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
219 converts it into a stem and a filespec
220 ========================================================================= */
221 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
223 WCHAR actualsource[MAX_PATH];
224 WCHAR *starPos;
225 WCHAR *questPos;
228 * Validate the source, expanding to full path ensuring it exists
230 if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
231 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
232 return RC_INITERROR;
236 * Work out the stem of the source
239 /* If no wildcard were supplied then the source is either a single
240 file or a directory - in which case thats the stem of the search,
241 otherwise split off the wildcards and use the higher level as the
242 stem */
243 lstrcpyW(stem, actualsource);
244 starPos = wcschr(stem, '*');
245 questPos = wcschr(stem, '?');
246 if (starPos || questPos) {
247 WCHAR *lastDir;
249 if (starPos) *starPos = 0x00;
250 if (questPos) *questPos = 0x00;
252 lastDir = wcsrchr(stem, '\\');
253 if (lastDir) *(lastDir+1) = 0x00;
254 else {
255 WINE_FIXME("Unexpected syntax error in source parameter\n");
256 return RC_INITERROR;
258 lstrcpyW(spec, actualsource + (lastDir - stem)+1);
259 } else {
261 DWORD attribs = GetFileAttributes(actualsource);
263 if (attribs == INVALID_FILE_ATTRIBUTES) {
264 LPWSTR lpMsgBuf;
265 DWORD lastError = GetLastError();
266 int status;
267 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
268 FORMAT_MESSAGE_FROM_SYSTEM,
269 NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
270 printf("%S\n", lpMsgBuf);
271 return RC_INITERROR;
273 /* Directory: */
274 } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
275 lstrcatW(stem, wchr_slash);
276 lstrcpyW(spec, wchr_star);
278 /* File: */
279 } else {
280 WCHAR drive[MAX_PATH];
281 WCHAR dir[MAX_PATH];
282 WCHAR fname[MAX_PATH];
283 WCHAR ext[MAX_PATH];
284 _wsplitpath(actualsource, drive, dir, fname, ext);
285 lstrcpyW(stem, drive);
286 lstrcatW(stem, dir);
287 lstrcpyW(spec, fname);
288 lstrcatW(spec, ext);
291 return RC_OK;
294 /* =========================================================================
295 XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
296 converts it into a stem
297 ========================================================================= */
298 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
299 WCHAR *srcspec, DWORD flags)
301 WCHAR actualdestination[MAX_PATH];
302 DWORD attribs;
303 BOOL isDir = FALSE;
306 * Validate the source, expanding to full path ensuring it exists
308 if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
309 WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
310 return RC_INITERROR;
313 /* Destination is either a directory or a file */
314 attribs = GetFileAttributes(actualdestination);
316 if (attribs == INVALID_FILE_ATTRIBUTES) {
318 /* If /I supplied and wildcard copy, assume directory */
319 if (flags & OPT_ASSUMEDIR &&
320 (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
322 isDir = TRUE;
324 } else {
325 DWORD count;
326 char answer[10] = "";
328 while (answer[0] != 'F' && answer[0] != 'D') {
329 printf("Is %S a filename or directory\n"
330 "on the target?\n"
331 "(F - File, D - Directory)\n", supplieddestination);
333 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
334 WINE_TRACE("User answer %c\n", answer[0]);
336 answer[0] = toupper(answer[0]);
339 if (answer[0] == 'D') {
340 isDir = TRUE;
341 } else {
342 isDir = FALSE;
345 } else {
346 isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
349 if (isDir) {
350 lstrcpyW(stem, actualdestination);
351 *spec = 0x00;
353 /* Ensure ends with a '\' */
354 if (stem[lstrlenW(stem)-1] != '\\') {
355 lstrcatW(stem, wchr_slash);
358 } else {
359 WCHAR drive[MAX_PATH];
360 WCHAR dir[MAX_PATH];
361 WCHAR fname[MAX_PATH];
362 WCHAR ext[MAX_PATH];
363 _wsplitpath(actualdestination, drive, dir, fname, ext);
364 lstrcpyW(stem, drive);
365 lstrcatW(stem, dir);
366 lstrcpyW(spec, fname);
367 lstrcatW(spec, ext);
369 return RC_OK;
372 /* =========================================================================
373 XCOPY_DoCopy - Recursive function to copy files based on input parms
374 of a stem and a spec
376 This works by using FindFirstFile supplying the source stem and spec.
377 If results are found, any non-directory ones are processed
378 Then, if /S or /E is supplied, another search is made just for
379 directories, and this function is called again for that directory
381 ========================================================================= */
382 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
383 WCHAR *deststem, WCHAR *destspec,
384 DWORD flags)
386 WIN32_FIND_DATA *finddata;
387 HANDLE h;
388 BOOL findres = TRUE;
389 WCHAR *inputpath, *outputpath;
390 BOOL copiedFile = FALSE;
391 DWORD destAttribs;
392 BOOL skipFile;
394 /* Allocate some working memory on heap to minimize footprint */
395 finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
396 inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
397 outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
399 /* Build the search info into a single parm */
400 lstrcpyW(inputpath, srcstem);
401 lstrcatW(inputpath, srcspec);
403 /* Search 1 - Look for matching files */
404 h = FindFirstFile(inputpath, finddata);
405 while (h != INVALID_HANDLE_VALUE && findres) {
407 skipFile = FALSE;
409 /* Ignore . and .. */
410 if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
411 lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
412 finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
414 WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
415 } else {
417 /* Get the filename information */
418 lstrcpyW(copyFrom, srcstem);
419 if (flags & OPT_SHORTNAME) {
420 lstrcatW(copyFrom, finddata->cAlternateFileName);
421 } else {
422 lstrcatW(copyFrom, finddata->cFileName);
425 lstrcpyW(copyTo, deststem);
426 if (*destspec == 0x00) {
427 if (flags & OPT_SHORTNAME) {
428 lstrcatW(copyTo, finddata->cAlternateFileName);
429 } else {
430 lstrcatW(copyTo, finddata->cFileName);
432 } else {
433 lstrcatW(copyTo, destspec);
436 /* Do the copy */
437 WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
438 wine_dbgstr_w(copyTo));
439 if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
441 /* See if file exists */
442 destAttribs = GetFileAttributesW(copyTo);
443 if (destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
444 DWORD count;
445 char answer[10];
446 BOOL answered = FALSE;
448 while (!answered) {
449 printf("Overwrite %S? (Yes|No|All)\n", copyTo);
450 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
451 &count, NULL);
453 answered = TRUE;
454 if (toupper(answer[0]) == 'A')
455 flags |= OPT_NOPROMPT;
456 else if (toupper(answer[0]) == 'N')
457 skipFile = TRUE;
458 else if (toupper(answer[0]) != 'Y')
459 answered = FALSE;
463 /* See if it has to exist! */
464 if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
465 skipFile = TRUE;
468 /* Output a status message */
469 if (!skipFile) {
470 if (flags & OPT_QUIET) {
471 /* Skip message */
472 } else if (flags & OPT_FULL) {
473 printf("%S -> %S\n", copyFrom, copyTo);
474 } else {
475 printf("%S\n", copyFrom);
478 copiedFile = TRUE;
479 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
480 /* Skip copy */
481 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
482 printf("Copying of '%S' to '%S' failed with r/c %d\n",
483 copyFrom, copyTo, GetLastError());
485 filesCopied++;
489 /* Find next file */
490 findres = FindNextFile(h, finddata);
492 FindClose(h);
494 /* Search 2 - do subdirs */
495 if (flags & OPT_RECURSIVE) {
496 lstrcpyW(inputpath, srcstem);
497 lstrcatW(inputpath, wchr_star);
498 findres = TRUE;
499 WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
501 h = FindFirstFile(inputpath, finddata);
502 while (h != INVALID_HANDLE_VALUE && findres) {
504 /* Only looking for dirs */
505 if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
506 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
507 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
509 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
511 /* Make up recursive information */
512 lstrcpyW(inputpath, srcstem);
513 lstrcatW(inputpath, finddata->cFileName);
514 lstrcatW(inputpath, wchr_slash);
516 lstrcpyW(outputpath, deststem);
517 if (*destspec == 0x00) {
518 lstrcatW(outputpath, finddata->cFileName);
520 /* If /E is supplied, create the directory now */
521 if ((flags & OPT_EMPTYDIR) &&
522 !(flags & OPT_SIMULATE))
523 XCOPY_CreateDirectory(outputpath);
525 lstrcatW(outputpath, wchr_slash);
528 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
531 /* Find next one */
532 findres = FindNextFile(h, finddata);
536 /* free up memory */
537 HeapFree(GetProcessHeap(), 0, finddata);
538 HeapFree(GetProcessHeap(), 0, inputpath);
539 HeapFree(GetProcessHeap(), 0, outputpath);
541 return 0;
544 /* =========================================================================
545 * Routine copied from cmd.exe md command -
546 * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
547 * dir2 if they do not already exist.
548 * ========================================================================= */
549 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
551 int len;
552 WCHAR *new_path;
553 BOOL ret = TRUE;
555 new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
556 lstrcpyW(new_path,path);
558 while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
559 new_path[len - 1] = 0;
561 while (!CreateDirectory(new_path,NULL))
563 WCHAR *slash;
564 DWORD last_error = GetLastError();
565 if (last_error == ERROR_ALREADY_EXISTS)
566 break;
568 if (last_error != ERROR_PATH_NOT_FOUND)
570 ret = FALSE;
571 break;
574 if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
576 ret = FALSE;
577 break;
580 len = slash - new_path;
581 new_path[len] = 0;
582 if (!XCOPY_CreateDirectory(new_path))
584 ret = FALSE;
585 break;
587 new_path[len] = '\\';
589 HeapFree(GetProcessHeap(),0,new_path);
590 return ret;