xcopy: Add support for /P (Prompt).
[wine] / programs / xcopy / xcopy.c
1 /*
2  * XCOPY - Wine-compatible xcopy program
3  *
4  * Copyright (C) 2007 J. Edmeades
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22  * 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
29  */
30
31
32 #include <stdio.h>
33 #include <windows.h>
34 #include <wine/debug.h>
35
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
42
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
54 #define OPT_REPLACEREAD  0x00000800
55 #define OPT_COPYHIDSYS   0x00001000
56 #define OPT_IGNOREERRORS 0x00002000
57 #define OPT_SRCPROMPT    0x00004000
58
59 #define MAXSTRING 8192
60
61 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
62
63 /* Prototypes */
64 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
65 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
66                                  WCHAR *spec, WCHAR *srcspec, DWORD flags);
67 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
68                         WCHAR *deststem, WCHAR *destspec,
69                         DWORD flags);
70 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
71
72 /* Global variables */
73 static ULONG filesCopied           = 0;              /* Number of files copied  */
74 static const WCHAR wchr_slash[]   = {'\\', 0};
75 static const WCHAR wchr_star[]    = {'*', 0};
76 static const WCHAR wchr_dot[]     = {'.', 0};
77 static const WCHAR wchr_dotdot[]  = {'.', '.', 0};
78
79 /* Constants (Mostly for widechars) */
80
81
82 /* To minimize stack usage during recursion, some temporary variables
83    made global                                                        */
84 static WCHAR copyFrom[MAX_PATH];
85 static WCHAR copyTo[MAX_PATH];
86
87
88 /* =========================================================================
89    main - Main entrypoint for the xcopy command
90
91      Processes the args, and drives the actual copying
92    ========================================================================= */
93 int main (int argc, char *argv[])
94 {
95     int     rc = 0;
96     WCHAR   suppliedsource[MAX_PATH] = {0};   /* As supplied on the cmd line */
97     WCHAR   supplieddestination[MAX_PATH] = {0};
98     WCHAR   sourcestem[MAX_PATH] = {0};       /* Stem of source          */
99     WCHAR   sourcespec[MAX_PATH] = {0};       /* Filespec of source      */
100     WCHAR   destinationstem[MAX_PATH] = {0};  /* Stem of destination     */
101     WCHAR   destinationspec[MAX_PATH] = {0};  /* Filespec of destination */
102     WCHAR   copyCmd[MAXSTRING];               /* COPYCMD env var         */
103     DWORD   flags = 0;                        /* Option flags            */
104     LPWSTR *argvW = NULL;
105     const WCHAR PROMPTSTR1[]  = {'/', 'Y', 0};
106     const WCHAR PROMPTSTR2[]  = {'/', 'y', 0};
107     const WCHAR COPYCMD[]  = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
108
109     /*
110      * Parse the command line
111      */
112
113     /* overwrite the command line */
114     argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
115
116     /* Confirm at least one parameter */
117     if (argc < 2) {
118         printf("Invalid number of parameters - Use xcopy /? for help\n");
119         return RC_INITERROR;
120     }
121
122     /* Preinitialize flags based on COPYCMD */
123     if (GetEnvironmentVariable(COPYCMD, copyCmd, MAXSTRING)) {
124         if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
125             wcsstr(copyCmd, PROMPTSTR2) != NULL) {
126             flags |= OPT_NOPROMPT;
127         }
128     }
129
130     /* Skip first arg, which is the program name */
131     argvW++;
132
133     while (argc > 1)
134     {
135         argc--;
136         WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
137
138         /* First non-switch parameter is source, second is destination */
139         if (*argvW[0] != '/') {
140             if (suppliedsource[0] == 0x00) {
141                 lstrcpyW(suppliedsource, *argvW);
142             } else if (supplieddestination[0] == 0x00) {
143                 lstrcpyW(supplieddestination, *argvW);
144             } else {
145                 printf("Invalid number of parameters - Use xcopy /? for help\n");
146                 return RC_INITERROR;
147             }
148         } else {
149             /* Process all the switch options
150                  Note: Windows docs say /P prompts when dest is created
151                        but tests show it is done for each src file
152                        regardless of the destination                   */
153             switch (toupper(argvW[0][1])) {
154             case 'I': flags |= OPT_ASSUMEDIR;     break;
155             case 'S': flags |= OPT_RECURSIVE;     break;
156             case 'E': flags |= OPT_EMPTYDIR;      break;
157             case 'Q': flags |= OPT_QUIET;         break;
158             case 'F': flags |= OPT_FULL;          break;
159             case 'L': flags |= OPT_SIMULATE;      break;
160             case 'W': flags |= OPT_PAUSE;         break;
161             case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
162             case 'Y': flags |= OPT_NOPROMPT;      break;
163             case 'N': flags |= OPT_SHORTNAME;     break;
164             case 'U': flags |= OPT_MUSTEXIST;     break;
165             case 'R': flags |= OPT_REPLACEREAD;   break;
166             case 'H': flags |= OPT_COPYHIDSYS;    break;
167             case 'C': flags |= OPT_IGNOREERRORS;  break;
168             case 'P': flags |= OPT_SRCPROMPT;     break;
169             case '-': if (toupper(argvW[0][2])=='Y')
170                           flags &= ~OPT_NOPROMPT; break;
171             default:
172               WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
173             }
174         }
175         argvW++;
176     }
177
178     /* Default the destination if not supplied */
179     if (supplieddestination[0] == 0x00)
180         lstrcpyW(supplieddestination, wchr_dot);
181
182     /* Trace out the supplied information */
183     WINE_TRACE("Supplied parameters:\n");
184     WINE_TRACE("Source      : '%s'\n", wine_dbgstr_w(suppliedsource));
185     WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
186
187     /* Extract required information from source specification */
188     rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
189
190     /* Extract required information from destination specification */
191     rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
192                                destinationspec, sourcespec, flags);
193
194     /* Trace out the resulting information */
195     WINE_TRACE("Resolved parameters:\n");
196     WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
197     WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
198     WINE_TRACE("Dest   Stem : '%s'\n", wine_dbgstr_w(destinationstem));
199     WINE_TRACE("Dest   Spec : '%s'\n", wine_dbgstr_w(destinationspec));
200
201     /* Pause if necessary */
202     if (flags & OPT_PAUSE) {
203         DWORD count;
204         char pausestr[10];
205
206         printf("Press <enter> to begin copying\n");
207         ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
208                   &count, NULL);
209     }
210
211     /* Now do the hard work... */
212     rc = XCOPY_DoCopy(sourcestem, sourcespec,
213                 destinationstem, destinationspec,
214                 flags);
215
216     /* Finished - print trailer and exit */
217     if (flags & OPT_SIMULATE) {
218         printf("%d file(s) would be copied\n", filesCopied);
219     } else if (!(flags & OPT_NOCOPY)) {
220         printf("%d file(s) copied\n", filesCopied);
221     }
222     if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
223     return rc;
224
225 }
226
227
228 /* =========================================================================
229    XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
230      converts it into a stem and a filespec
231    ========================================================================= */
232 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
233 {
234     WCHAR             actualsource[MAX_PATH];
235     WCHAR            *starPos;
236     WCHAR            *questPos;
237
238     /*
239      * Validate the source, expanding to full path ensuring it exists
240      */
241     if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
242         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
243         return RC_INITERROR;
244     }
245
246     /*
247      * Work out the stem of the source
248      */
249
250     /* If no wildcard were supplied then the source is either a single
251        file or a directory - in which case thats the stem of the search,
252        otherwise split off the wildcards and use the higher level as the
253        stem                                                              */
254     lstrcpyW(stem, actualsource);
255     starPos = wcschr(stem, '*');
256     questPos = wcschr(stem, '?');
257     if (starPos || questPos) {
258         WCHAR *lastDir;
259
260         if (starPos) *starPos = 0x00;
261         if (questPos) *questPos = 0x00;
262
263         lastDir = wcsrchr(stem, '\\');
264         if (lastDir) *(lastDir+1) = 0x00;
265         else {
266             WINE_FIXME("Unexpected syntax error in source parameter\n");
267             return RC_INITERROR;
268         }
269         lstrcpyW(spec, actualsource + (lastDir - stem)+1);
270     } else {
271
272         DWORD attribs = GetFileAttributes(actualsource);
273
274         if (attribs == INVALID_FILE_ATTRIBUTES) {
275             LPWSTR lpMsgBuf;
276             DWORD lastError = GetLastError();
277             int status;
278             status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
279                                     FORMAT_MESSAGE_FROM_SYSTEM,
280                                     NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
281             printf("%S\n", lpMsgBuf);
282             return RC_INITERROR;
283
284         /* Directory: */
285         } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
286             lstrcatW(stem, wchr_slash);
287             lstrcpyW(spec, wchr_star);
288
289         /* File: */
290         } else {
291             WCHAR drive[MAX_PATH];
292             WCHAR dir[MAX_PATH];
293             WCHAR fname[MAX_PATH];
294             WCHAR ext[MAX_PATH];
295             _wsplitpath(actualsource, drive, dir, fname, ext);
296             lstrcpyW(stem, drive);
297             lstrcatW(stem, dir);
298             lstrcpyW(spec, fname);
299             lstrcatW(spec, ext);
300         }
301     }
302     return RC_OK;
303 }
304
305 /* =========================================================================
306    XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
307      converts it into a stem
308    ========================================================================= */
309 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
310                                  WCHAR *srcspec, DWORD flags)
311 {
312     WCHAR  actualdestination[MAX_PATH];
313     DWORD attribs;
314     BOOL isDir = FALSE;
315
316     /*
317      * Validate the source, expanding to full path ensuring it exists
318      */
319     if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
320         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
321         return RC_INITERROR;
322     }
323
324     /* Destination is either a directory or a file */
325     attribs = GetFileAttributes(actualdestination);
326
327     if (attribs == INVALID_FILE_ATTRIBUTES) {
328
329         /* If /I supplied and wildcard copy, assume directory */
330         if (flags & OPT_ASSUMEDIR &&
331             (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
332
333             isDir = TRUE;
334
335         } else {
336             DWORD count;
337             char  answer[10] = "";
338
339             while (answer[0] != 'F' && answer[0] != 'D') {
340                 printf("Is %S a filename or directory\n"
341                        "on the target?\n"
342                        "(F - File, D - Directory)\n", supplieddestination);
343
344                 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
345                 WINE_TRACE("User answer %c\n", answer[0]);
346
347                 answer[0] = toupper(answer[0]);
348             }
349
350             if (answer[0] == 'D') {
351                 isDir = TRUE;
352             } else {
353                 isDir = FALSE;
354             }
355         }
356     } else {
357         isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
358     }
359
360     if (isDir) {
361         lstrcpyW(stem, actualdestination);
362         *spec = 0x00;
363
364         /* Ensure ends with a '\' */
365         if (stem[lstrlenW(stem)-1] != '\\') {
366             lstrcatW(stem, wchr_slash);
367         }
368
369     } else {
370         WCHAR drive[MAX_PATH];
371         WCHAR dir[MAX_PATH];
372         WCHAR fname[MAX_PATH];
373         WCHAR ext[MAX_PATH];
374         _wsplitpath(actualdestination, drive, dir, fname, ext);
375         lstrcpyW(stem, drive);
376         lstrcatW(stem, dir);
377         lstrcpyW(spec, fname);
378         lstrcatW(spec, ext);
379     }
380     return RC_OK;
381 }
382
383 /* =========================================================================
384    XCOPY_DoCopy - Recursive function to copy files based on input parms
385      of a stem and a spec
386
387       This works by using FindFirstFile supplying the source stem and spec.
388       If results are found, any non-directory ones are processed
389       Then, if /S or /E is supplied, another search is made just for
390       directories, and this function is called again for that directory
391
392    ========================================================================= */
393 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
394                         WCHAR *deststem, WCHAR *destspec,
395                         DWORD flags)
396 {
397     WIN32_FIND_DATA *finddata;
398     HANDLE          h;
399     BOOL            findres = TRUE;
400     WCHAR           *inputpath, *outputpath;
401     BOOL            copiedFile = FALSE;
402     DWORD           destAttribs, srcAttribs;
403     BOOL            skipFile;
404     LPVOID          lpMsgBuf;
405     DWORD           error_code;
406     int             status;
407
408     /* Allocate some working memory on heap to minimize footprint */
409     finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
410     inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
411     outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
412
413     /* Build the search info into a single parm */
414     lstrcpyW(inputpath, srcstem);
415     lstrcatW(inputpath, srcspec);
416
417     /* Search 1 - Look for matching files */
418     h = FindFirstFile(inputpath, finddata);
419     while (h != INVALID_HANDLE_VALUE && findres) {
420
421         skipFile = FALSE;
422
423         /* Ignore . and .. */
424         if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
425             lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
426             finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
427
428             WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
429         } else {
430
431             /* Get the filename information */
432             lstrcpyW(copyFrom, srcstem);
433             if (flags & OPT_SHORTNAME) {
434               lstrcatW(copyFrom, finddata->cAlternateFileName);
435             } else {
436               lstrcatW(copyFrom, finddata->cFileName);
437             }
438
439             lstrcpyW(copyTo, deststem);
440             if (*destspec == 0x00) {
441                 if (flags & OPT_SHORTNAME) {
442                     lstrcatW(copyTo, finddata->cAlternateFileName);
443                 } else {
444                     lstrcatW(copyTo, finddata->cFileName);
445                 }
446             } else {
447                 lstrcatW(copyTo, destspec);
448             }
449
450             /* Do the copy */
451             WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
452                                                       wine_dbgstr_w(copyTo));
453             if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
454
455             /* See if allowed to copy it */
456             srcAttribs = GetFileAttributesW(copyFrom);
457             if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
458                 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
459
460                 if (!(flags & OPT_COPYHIDSYS)) {
461                     skipFile = TRUE;
462                 }
463             }
464
465             /* Prompt each file if necessary */
466             if (!skipFile && (flags & OPT_SRCPROMPT)) {
467                 DWORD count;
468                 char  answer[10];
469                 BOOL  answered = FALSE;
470
471                 while (!answered) {
472                     printf("%S? (Yes|No)\n", copyFrom);
473                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
474                               &count, NULL);
475
476                     answered = TRUE;
477                     if (toupper(answer[0]) == 'N')
478                         skipFile = TRUE;
479                     else if (toupper(answer[0]) != 'Y')
480                         answered = FALSE;
481                 }
482             }
483
484             /* See if file exists */
485             destAttribs = GetFileAttributesW(copyTo);
486             if (!skipFile &&
487                 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
488                 DWORD count;
489                 char  answer[10];
490                 BOOL  answered = FALSE;
491
492                 while (!answered) {
493                     printf("Overwrite %S? (Yes|No|All)\n", copyTo);
494                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
495                               &count, NULL);
496
497                     answered = TRUE;
498                     if (toupper(answer[0]) == 'A')
499                         flags |= OPT_NOPROMPT;
500                     else if (toupper(answer[0]) == 'N')
501                         skipFile = TRUE;
502                     else if (toupper(answer[0]) != 'Y')
503                         answered = FALSE;
504                 }
505             }
506
507             /* See if it has to exist! */
508             if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
509                 skipFile = TRUE;
510             }
511
512             /* Output a status message */
513             if (!skipFile) {
514                 if (flags & OPT_QUIET) {
515                     /* Skip message */
516                 } else if (flags & OPT_FULL) {
517                     printf("%S -> %S\n", copyFrom, copyTo);
518                 } else {
519                     printf("%S\n", copyFrom);
520                 }
521
522                 /* If allowing overwriting of read only files, remove any
523                    write protection                                       */
524                 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
525                     (flags & OPT_REPLACEREAD)) {
526                     SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
527                 }
528
529                 copiedFile = TRUE;
530                 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
531                     /* Skip copy */
532                 } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
533                     printf("Copying of '%S' to '%S' failed with r/c %d\n",
534                            copyFrom, copyTo, GetLastError());
535
536                     error_code = GetLastError ();
537                     status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
538                                             FORMAT_MESSAGE_FROM_SYSTEM,
539                                             NULL, error_code, 0,
540                                             (LPTSTR) &lpMsgBuf, 0, NULL);
541                     if (!status) {
542                         WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
543                                    error_code, GetLastError());
544                     } else {
545                         printf("%S\n", lpMsgBuf);
546                         LocalFree ((HLOCAL)lpMsgBuf);
547                     }
548                     if (flags & OPT_IGNOREERRORS) {
549                         skipFile = TRUE;
550                     } else {
551                         return RC_WRITEERROR;
552                     }
553                 }
554                 filesCopied++;
555             }
556         }
557
558         /* Find next file */
559         findres = FindNextFile(h, finddata);
560     }
561     FindClose(h);
562
563     /* Search 2 - do subdirs */
564     if (flags & OPT_RECURSIVE) {
565         lstrcpyW(inputpath, srcstem);
566         lstrcatW(inputpath, wchr_star);
567         findres = TRUE;
568         WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
569
570         h = FindFirstFile(inputpath, finddata);
571         while (h != INVALID_HANDLE_VALUE && findres) {
572
573             /* Only looking for dirs */
574             if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
575                 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
576                 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
577
578                 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
579
580                 /* Make up recursive information */
581                 lstrcpyW(inputpath, srcstem);
582                 lstrcatW(inputpath, finddata->cFileName);
583                 lstrcatW(inputpath, wchr_slash);
584
585                 lstrcpyW(outputpath, deststem);
586                 if (*destspec == 0x00) {
587                     lstrcatW(outputpath, finddata->cFileName);
588
589                     /* If /E is supplied, create the directory now */
590                     if ((flags & OPT_EMPTYDIR) &&
591                         !(flags & OPT_SIMULATE))
592                         XCOPY_CreateDirectory(outputpath);
593
594                     lstrcatW(outputpath, wchr_slash);
595                 }
596
597                 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
598             }
599
600             /* Find next one */
601             findres = FindNextFile(h, finddata);
602         }
603     }
604
605     /* free up memory */
606     HeapFree(GetProcessHeap(), 0, finddata);
607     HeapFree(GetProcessHeap(), 0, inputpath);
608     HeapFree(GetProcessHeap(), 0, outputpath);
609
610     return 0;
611 }
612
613 /* =========================================================================
614  * Routine copied from cmd.exe md command -
615  * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
616  * dir2 if they do not already exist.
617  * ========================================================================= */
618 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
619 {
620     int len;
621     WCHAR *new_path;
622     BOOL ret = TRUE;
623
624     new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
625     lstrcpyW(new_path,path);
626
627     while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
628         new_path[len - 1] = 0;
629
630     while (!CreateDirectory(new_path,NULL))
631     {
632         WCHAR *slash;
633         DWORD last_error = GetLastError();
634         if (last_error == ERROR_ALREADY_EXISTS)
635             break;
636
637         if (last_error != ERROR_PATH_NOT_FOUND)
638         {
639             ret = FALSE;
640             break;
641         }
642
643         if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
644         {
645             ret = FALSE;
646             break;
647         }
648
649         len = slash - new_path;
650         new_path[len] = 0;
651         if (!XCOPY_CreateDirectory(new_path))
652         {
653             ret = FALSE;
654             break;
655         }
656         new_path[len] = '\\';
657     }
658     HeapFree(GetProcessHeap(),0,new_path);
659     return ret;
660 }