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
23 * Apparently, valid return codes are:
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
34 #include <wine/debug.h>
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
);
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
,
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
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
[])
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 */
114 printf("Invalid number of parameters - Use xcopy /? for help\n");
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 */
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
);
141 printf("Invalid number of parameters - Use xcopy /? for help\n");
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;
161 WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*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
) {
195 printf("Press <enter> to begin copying\n");
196 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), pausestr
, sizeof(pausestr
),
200 /* Now do the hard work... */
201 rc
= XCOPY_DoCopy(sourcestem
, sourcespec
,
202 destinationstem
, destinationspec
,
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
;
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
];
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());
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
243 lstrcpyW(stem
, actualsource
);
244 starPos
= wcschr(stem
, '*');
245 questPos
= wcschr(stem
, '?');
246 if (starPos
|| questPos
) {
249 if (starPos
) *starPos
= 0x00;
250 if (questPos
) *questPos
= 0x00;
252 lastDir
= wcsrchr(stem
, '\\');
253 if (lastDir
) *(lastDir
+1) = 0x00;
255 WINE_FIXME("Unexpected syntax error in source parameter\n");
258 lstrcpyW(spec
, actualsource
+ (lastDir
- stem
)+1);
261 DWORD attribs
= GetFileAttributes(actualsource
);
263 if (attribs
== INVALID_FILE_ATTRIBUTES
) {
265 DWORD lastError
= GetLastError();
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
);
274 } else if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
275 lstrcatW(stem
, wchr_slash
);
276 lstrcpyW(spec
, wchr_star
);
280 WCHAR drive
[MAX_PATH
];
282 WCHAR fname
[MAX_PATH
];
284 _wsplitpath(actualsource
, drive
, dir
, fname
, ext
);
285 lstrcpyW(stem
, drive
);
287 lstrcpyW(spec
, fname
);
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
];
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());
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
, '*'))) {
326 char answer
[10] = "";
328 while (answer
[0] != 'F' && answer
[0] != 'D') {
329 printf("Is %S a filename or directory\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') {
346 isDir
= (attribs
& FILE_ATTRIBUTE_DIRECTORY
);
350 lstrcpyW(stem
, actualdestination
);
353 /* Ensure ends with a '\' */
354 if (stem
[lstrlenW(stem
)-1] != '\\') {
355 lstrcatW(stem
, wchr_slash
);
359 WCHAR drive
[MAX_PATH
];
361 WCHAR fname
[MAX_PATH
];
363 _wsplitpath(actualdestination
, drive
, dir
, fname
, ext
);
364 lstrcpyW(stem
, drive
);
366 lstrcpyW(spec
, fname
);
372 /* =========================================================================
373 XCOPY_DoCopy - Recursive function to copy files based on input parms
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
,
386 WIN32_FIND_DATA
*finddata
;
389 WCHAR
*inputpath
, *outputpath
;
390 BOOL copiedFile
= FALSE
;
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
) {
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
));
417 /* Get the filename information */
418 lstrcpyW(copyFrom
, srcstem
);
419 if (flags
& OPT_SHORTNAME
) {
420 lstrcatW(copyFrom
, finddata
->cAlternateFileName
);
422 lstrcatW(copyFrom
, finddata
->cFileName
);
425 lstrcpyW(copyTo
, deststem
);
426 if (*destspec
== 0x00) {
427 if (flags
& OPT_SHORTNAME
) {
428 lstrcatW(copyTo
, finddata
->cAlternateFileName
);
430 lstrcatW(copyTo
, finddata
->cFileName
);
433 lstrcatW(copyTo
, destspec
);
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
)) {
446 BOOL answered
= FALSE
;
449 printf("Overwrite %S? (Yes|No|All)\n", copyTo
);
450 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
454 if (toupper(answer
[0]) == 'A')
455 flags
|= OPT_NOPROMPT
;
456 else if (toupper(answer
[0]) == 'N')
458 else if (toupper(answer
[0]) != 'Y')
463 /* See if it has to exist! */
464 if (destAttribs
== INVALID_FILE_ATTRIBUTES
&& (flags
& OPT_MUSTEXIST
)) {
468 /* Output a status message */
470 if (flags
& OPT_QUIET
) {
472 } else if (flags
& OPT_FULL
) {
473 printf("%S -> %S\n", copyFrom
, copyTo
);
475 printf("%S\n", copyFrom
);
479 if (flags
& OPT_SIMULATE
|| flags
& OPT_NOCOPY
) {
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());
490 findres
= FindNextFile(h
, finddata
);
494 /* Search 2 - do subdirs */
495 if (flags
& OPT_RECURSIVE
) {
496 lstrcpyW(inputpath
, srcstem
);
497 lstrcatW(inputpath
, wchr_star
);
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
);
532 findres
= FindNextFile(h
, finddata
);
537 HeapFree(GetProcessHeap(), 0, finddata
);
538 HeapFree(GetProcessHeap(), 0, inputpath
);
539 HeapFree(GetProcessHeap(), 0, outputpath
);
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
)
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
))
564 DWORD last_error
= GetLastError();
565 if (last_error
== ERROR_ALREADY_EXISTS
)
568 if (last_error
!= ERROR_PATH_NOT_FOUND
)
574 if (!(slash
= wcsrchr(new_path
,'\\')) && ! (slash
= wcsrchr(new_path
,'/')))
580 len
= slash
- new_path
;
582 if (!XCOPY_CreateDirectory(new_path
))
587 new_path
[len
] = '\\';
589 HeapFree(GetProcessHeap(),0,new_path
);