From 921edab75864dbcdc130bcf85866dfea12101b47 Mon Sep 17 00:00:00 2001
From: Jason Edmeades <us@edmeades.me.uk>
Date: Tue, 6 Mar 2007 20:04:15 +0000
Subject: [PATCH] Basic implementation of xcopy.

---
 .gitignore                 |   1 +
 Makefile.in                |   2 +
 configure                  |   3 +
 configure.ac               |   1 +
 programs/Makefile.in       |   6 +-
 programs/xcopy/Makefile.in |  17 ++
 programs/xcopy/xcopy.c     | 514 +++++++++++++++++++++++++++++++++++++
 7 files changed, 542 insertions(+), 2 deletions(-)
 create mode 100644 programs/xcopy/Makefile.in
 create mode 100644 programs/xcopy/xcopy.c

diff --git a/.gitignore b/.gitignore
index 2e6f73f9e30..4003e3e3f9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -874,6 +874,7 @@ programs/winver/winver
 programs/wordpad/rsrc.res
 programs/wordpad/toolbar.bmp
 programs/wordpad/wordpad
+programs/xcopy/xcopy
 server/wineserver
 server/wineserver.man
 tools/bin2res
diff --git a/Makefile.in b/Makefile.in
index f488e7d6556..265acf3151e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -467,6 +467,7 @@ ALL_MAKEFILES = \
 	programs/winhelp/Makefile \
 	programs/winver/Makefile \
 	programs/wordpad/Makefile \
+	programs/xcopy/Makefile \
 	server/Makefile \
 	tools/Makefile \
 	tools/widl/Makefile \
@@ -803,6 +804,7 @@ programs/winevdm/Makefile: programs/winevdm/Makefile.in programs/Makeprog.rules
 programs/winhelp/Makefile: programs/winhelp/Makefile.in programs/Makeprog.rules
 programs/winver/Makefile: programs/winver/Makefile.in programs/Makeprog.rules
 programs/wordpad/Makefile: programs/wordpad/Makefile.in programs/Makeprog.rules
+programs/xcopy/Makefile: programs/xcopy/Makefile.in programs/Makeprog.rules
 server/Makefile: server/Makefile.in Make.rules
 tools/Makefile: tools/Makefile.in Make.rules
 tools/widl/Makefile: tools/widl/Makefile.in Make.rules
diff --git a/configure b/configure
index d99a70ab61b..5b56377fd17 100755
--- a/configure
+++ b/configure
@@ -20859,6 +20859,8 @@ ac_config_files="$ac_config_files programs/winver/Makefile"
 
 ac_config_files="$ac_config_files programs/wordpad/Makefile"
 
+ac_config_files="$ac_config_files programs/xcopy/Makefile"
+
 ac_config_files="$ac_config_files server/Makefile"
 
 ac_config_files="$ac_config_files tools/Makefile"
@@ -21760,6 +21762,7 @@ do
     "programs/winhelp/Makefile") CONFIG_FILES="$CONFIG_FILES programs/winhelp/Makefile" ;;
     "programs/winver/Makefile") CONFIG_FILES="$CONFIG_FILES programs/winver/Makefile" ;;
     "programs/wordpad/Makefile") CONFIG_FILES="$CONFIG_FILES programs/wordpad/Makefile" ;;
+    "programs/xcopy/Makefile") CONFIG_FILES="$CONFIG_FILES programs/xcopy/Makefile" ;;
     "server/Makefile") CONFIG_FILES="$CONFIG_FILES server/Makefile" ;;
     "tools/Makefile") CONFIG_FILES="$CONFIG_FILES tools/Makefile" ;;
     "tools/widl/Makefile") CONFIG_FILES="$CONFIG_FILES tools/widl/Makefile" ;;
diff --git a/configure.ac b/configure.ac
index 95b3b8dc1ab..abd544c6df9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1827,6 +1827,7 @@ AC_CONFIG_FILES([programs/winevdm/Makefile])
 AC_CONFIG_FILES([programs/winhelp/Makefile])
 AC_CONFIG_FILES([programs/winver/Makefile])
 AC_CONFIG_FILES([programs/wordpad/Makefile])
+AC_CONFIG_FILES([programs/xcopy/Makefile])
 AC_CONFIG_FILES([server/Makefile])
 AC_CONFIG_FILES([tools/Makefile])
 AC_CONFIG_FILES([tools/widl/Makefile])
diff --git a/programs/Makefile.in b/programs/Makefile.in
index 8b61cdeceb4..1dbf5d4c813 100644
--- a/programs/Makefile.in
+++ b/programs/Makefile.in
@@ -41,7 +41,8 @@ SUBDIRS = \
 	winevdm \
 	winhelp \
 	winver \
-	wordpad
+	wordpad \
+	xcopy
 
 # Sub-directories to run make install into
 INSTALLSUBDIRS = \
@@ -77,7 +78,8 @@ INSTALLSUBDIRS = \
 	winevdm \
 	winhelp \
 	winver \
-	wordpad
+	wordpad \
+	xcopy
 
 # Programs to install in bin directory
 INSTALLPROGS = \
diff --git a/programs/xcopy/Makefile.in b/programs/xcopy/Makefile.in
new file mode 100644
index 00000000000..d54597ccb72
--- /dev/null
+++ b/programs/xcopy/Makefile.in
@@ -0,0 +1,17 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+MODULE    = xcopy.exe
+APPMODE   = -mconsole
+IMPORTS   = shell32 user32 msvcrt kernel32
+EXTRADEFS = -DUNICODE
+EXTRAINCL = -I$(TOPSRCDIR)/include/msvcrt
+MODCFLAGS = @BUILTINFLAG@
+
+C_SRCS = \
+	xcopy.c
+
+@MAKE_PROG_RULES@
+
+@DEPENDENCIES@  # everything below this line is overwritten by make depend
diff --git a/programs/xcopy/xcopy.c b/programs/xcopy/xcopy.c
new file mode 100644
index 00000000000..718923e64ed
--- /dev/null
+++ b/programs/xcopy/xcopy.c
@@ -0,0 +1,514 @@
+/*
+ * XCOPY - Wine-compatible xcopy program
+ *
+ * Copyright (C) 2007 J. Edmeades
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * Notes:
+ * Apparently, valid return codes are:
+ *   0 - OK
+ *   1 - No files found to copy
+ *   2 - CTRL+C during copy
+ *   4 - Initialization error, or invalid source specification
+ *   5 - Disk write error
+ */
+
+
+#include <stdio.h>
+#include <windows.h>
+#include <wine/debug.h>
+
+/* Local #defines */
+#define RC_OK         0
+#define RC_NOFILES    1
+#define RC_CTRLC      2
+#define RC_INITERROR  4
+#define RC_WRITEERROR 5
+
+#define OPT_ASSUMEDIR    0x00000001
+#define OPT_RECURSIVE    0x00000002
+#define OPT_EMPTYDIR     0x00000004
+#define OPT_QUIET        0x00000008
+#define OPT_FULL         0x00000010
+#define OPT_SIMULATE     0x00000020
+
+WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
+
+/* Prototypes */
+static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec);
+static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
+                                 WCHAR *spec, WCHAR *srcspec, DWORD flags);
+static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
+                        WCHAR *deststem, WCHAR *destspec,
+                        DWORD flags);
+static BOOL XCOPY_CreateDirectory(const WCHAR* path);
+
+/* Global variables */
+static ULONG filesCopied           = 0;              /* Number of files copied  */
+static const WCHAR wchr_slash[]   = {'\\', 0};
+static const WCHAR wchr_star[]    = {'*', 0};
+static const WCHAR wchr_dot[]     = {'.', 0};
+static const WCHAR wchr_dotdot[]  = {'.', '.', 0};
+
+/* Constants (Mostly for widechars) */
+
+
+/* To minimize stack usage during recursion, some temporary variables
+   made global                                                        */
+static WCHAR copyFrom[MAX_PATH];
+static WCHAR copyTo[MAX_PATH];
+
+
+/* =========================================================================
+   main - Main entrypoint for the xcopy command
+
+     Processes the args, and drives the actual copying
+   ========================================================================= */
+int main (int argc, char *argv[])
+{
+    int     rc = 0;
+    WCHAR   suppliedsource[MAX_PATH] = {0};   /* As supplied on the cmd line */
+    WCHAR   supplieddestination[MAX_PATH] = {0};
+    WCHAR   sourcestem[MAX_PATH] = {0};       /* Stem of source          */
+    WCHAR   sourcespec[MAX_PATH] = {0};       /* Filespec of source      */
+    WCHAR   destinationstem[MAX_PATH] = {0};  /* Stem of destination     */
+    WCHAR   destinationspec[MAX_PATH] = {0};  /* Filespec of destination */
+    DWORD   flags = 0;                        /* Option flags            */
+    LPWSTR *argvW = NULL;
+    /*
+     * Parse the command line
+     */
+
+    /* overwrite the command line */
+    argvW = CommandLineToArgvW( GetCommandLineW(), &argc );
+
+    /* Confirm at least one parameter */
+    if (argc < 2) {
+        printf("Invalid number of parameters - Use xcopy /? for help\n");
+        return RC_INITERROR;
+    }
+
+    /* Skip first arg, which is the program name */
+    argvW++;
+
+    while (argc > 1)
+    {
+        argc--;
+        WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(*argvW));
+
+        /* First non-switch parameter is source, second is destination */
+        if (*argvW[0] != '/') {
+            if (suppliedsource[0] == 0x00) {
+                lstrcpyW(suppliedsource, *argvW);
+            } else if (supplieddestination[0] == 0x00) {
+                lstrcpyW(supplieddestination, *argvW);
+            } else {
+                printf("Invalid number of parameters - Use xcopy /? for help\n");
+                return RC_INITERROR;
+            }
+        } else {
+            /* Process all the switch options */
+            switch (toupper(argvW[0][1])) {
+            case 'I': flags |= OPT_ASSUMEDIR;     break;
+            case 'S': flags |= OPT_RECURSIVE;     break;
+            case 'E': flags |= OPT_EMPTYDIR;      break;
+            case 'Q': flags |= OPT_QUIET;         break;
+            case 'F': flags |= OPT_FULL;          break;
+            case 'L': flags |= OPT_SIMULATE;      break;
+            default:
+              WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
+            }
+        }
+        argvW++;
+    }
+
+    /* Default the destination if not supplied */
+    if (supplieddestination[0] == 0x00)
+        lstrcpyW(supplieddestination, wchr_dot);
+
+    /* Trace out the supplied information */
+    WINE_TRACE("Supplied parameters:\n");
+    WINE_TRACE("Source      : '%s'\n", wine_dbgstr_w(suppliedsource));
+    WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
+
+    /* Extract required information from source specification */
+    rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec);
+
+    /* Extract required information from destination specification */
+    rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
+                               destinationspec, sourcespec, flags);
+
+    /* Trace out the resulting information */
+    WINE_TRACE("Resolved parameters:\n");
+    WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
+    WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
+    WINE_TRACE("Dest   Stem : '%s'\n", wine_dbgstr_w(destinationstem));
+    WINE_TRACE("Dest   Spec : '%s'\n", wine_dbgstr_w(destinationspec));
+
+    /* Now do the hard work... */
+    rc = XCOPY_DoCopy(sourcestem, sourcespec,
+                destinationstem, destinationspec,
+                flags);
+
+
+    /* Finished - print trailer and exit */
+    if (flags & OPT_SIMULATE) {
+        printf("%d file(s) would be copied\n", filesCopied);
+    } else {
+        printf("%d file(s) copied\n", filesCopied);
+    }
+    if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
+    return rc;
+
+}
+
+
+/* =========================================================================
+   XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
+     converts it into a stem and a filespec
+   ========================================================================= */
+static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec)
+{
+    WCHAR             actualsource[MAX_PATH];
+    WCHAR            *starPos;
+    WCHAR            *questPos;
+
+    /*
+     * Validate the source, expanding to full path ensuring it exists
+     */
+    if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
+        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
+        return RC_INITERROR;
+    }
+
+    /*
+     * Work out the stem of the source
+     */
+
+    /* If no wildcard were supplied then the source is either a single
+       file or a directory - in which case thats the stem of the search,
+       otherwise split off the wildcards and use the higher level as the
+       stem                                                              */
+    lstrcpyW(stem, actualsource);
+    starPos = wcschr(stem, '*');
+    questPos = wcschr(stem, '?');
+    if (starPos || questPos) {
+        WCHAR *lastDir;
+
+        if (starPos) *starPos = 0x00;
+        if (questPos) *questPos = 0x00;
+
+        lastDir = wcsrchr(stem, '\\');
+        if (lastDir) *(lastDir+1) = 0x00;
+        else {
+            WINE_FIXME("Unexpected syntax error in source parameter\n");
+            return RC_INITERROR;
+        }
+        lstrcpyW(spec, actualsource + (lastDir - stem)+1);
+    } else {
+
+        DWORD attribs = GetFileAttributes(actualsource);
+
+        if (attribs == INVALID_FILE_ATTRIBUTES) {
+            LPWSTR lpMsgBuf;
+            DWORD lastError = GetLastError();
+            int status;
+            status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                                    FORMAT_MESSAGE_FROM_SYSTEM,
+                                    NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
+            printf("%S\n", lpMsgBuf);
+            return RC_INITERROR;
+
+        /* Directory: */
+        } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
+            lstrcatW(stem, wchr_slash);
+            lstrcpyW(spec, wchr_star);
+
+        /* File: */
+        } else {
+            WCHAR drive[MAX_PATH];
+            WCHAR dir[MAX_PATH];
+            WCHAR fname[MAX_PATH];
+            WCHAR ext[MAX_PATH];
+            _wsplitpath(actualsource, drive, dir, fname, ext);
+            lstrcpyW(stem, drive);
+            lstrcatW(stem, dir);
+            lstrcpyW(spec, fname);
+            lstrcatW(spec, ext);
+        }
+    }
+    return RC_OK;
+}
+
+/* =========================================================================
+   XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
+     converts it into a stem
+   ========================================================================= */
+static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
+                                 WCHAR *srcspec, DWORD flags)
+{
+    WCHAR  actualdestination[MAX_PATH];
+    DWORD attribs;
+    BOOL isDir = FALSE;
+
+    /*
+     * Validate the source, expanding to full path ensuring it exists
+     */
+    if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
+        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
+        return RC_INITERROR;
+    }
+
+    /* Destination is either a directory or a file */
+    attribs = GetFileAttributes(actualdestination);
+
+    if (attribs == INVALID_FILE_ATTRIBUTES) {
+
+        /* If /I supplied and wildcard copy, assume directory */
+        if (flags & OPT_ASSUMEDIR &&
+            (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) {
+
+            isDir = TRUE;
+
+        } else {
+            DWORD count;
+            char  answer[10] = "";
+
+            while (answer[0] != 'F' && answer[0] != 'D') {
+                printf("Is %S a filename or directory\n"
+                       "on the target?\n"
+                       "(F - File, D - Directory)\n", supplieddestination);
+
+                ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
+                WINE_TRACE("User answer %c\n", answer[0]);
+
+                answer[0] = toupper(answer[0]);
+            }
+
+            if (answer[0] == 'D') {
+                isDir = TRUE;
+            } else {
+                isDir = FALSE;
+            }
+        }
+    } else {
+        isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
+    }
+
+    if (isDir) {
+        lstrcpyW(stem, actualdestination);
+        *spec = 0x00;
+
+        /* Ensure ends with a '\' */
+        if (stem[lstrlenW(stem)-1] != '\\') {
+            lstrcatW(stem, wchr_slash);
+        }
+
+    } else {
+        WCHAR drive[MAX_PATH];
+        WCHAR dir[MAX_PATH];
+        WCHAR fname[MAX_PATH];
+        WCHAR ext[MAX_PATH];
+        _wsplitpath(actualdestination, drive, dir, fname, ext);
+        lstrcpyW(stem, drive);
+        lstrcatW(stem, dir);
+        lstrcpyW(spec, fname);
+        lstrcatW(spec, ext);
+    }
+    return RC_OK;
+}
+
+/* =========================================================================
+   XCOPY_DoCopy - Recursive function to copy files based on input parms
+     of a stem and a spec
+
+      This works by using FindFirstFile supplying the source stem and spec.
+      If results are found, any non-directory ones are processed
+      Then, if /S or /E is supplied, another search is made just for
+      directories, and this function is called again for that directory
+
+   ========================================================================= */
+static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
+                        WCHAR *deststem, WCHAR *destspec,
+                        DWORD flags)
+{
+    WIN32_FIND_DATA *finddata;
+    HANDLE          h;
+    BOOL            findres = TRUE;
+    WCHAR           *inputpath, *outputpath;
+    BOOL            copiedFile = FALSE;
+
+    /* Allocate some working memory on heap to minimize footprint */
+    finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATA));
+    inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
+    outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
+
+    /* Build the search info into a single parm */
+    lstrcpyW(inputpath, srcstem);
+    lstrcatW(inputpath, srcspec);
+
+    /* Search 1 - Look for matching files */
+    h = FindFirstFile(inputpath, finddata);
+    while (h != INVALID_HANDLE_VALUE && findres) {
+
+        /* Ignore . and .. */
+        if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
+            lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
+            finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+
+            WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
+        } else {
+
+            /* Get the filename information */
+            lstrcpyW(copyFrom, srcstem);
+            lstrcatW(copyFrom, finddata->cFileName);
+
+            lstrcpyW(copyTo, deststem);
+            if (*destspec == 0x00) {
+                lstrcatW(copyTo, finddata->cFileName);
+            } else {
+                lstrcatW(copyTo, destspec);
+            }
+
+            /* Do the copy */
+            WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
+                                                      wine_dbgstr_w(copyTo));
+            if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
+
+            /* Output a status message */
+            if (flags & OPT_QUIET) {
+                /* Skip message */
+            } else if (flags & OPT_FULL) {
+                printf("%S -> %S\n", copyFrom, copyTo);
+            } else {
+                printf("%S\n", copyFrom);
+            }
+
+            copiedFile = TRUE;
+            if (flags & OPT_SIMULATE) {
+                /* Skip copy as just simulating */
+            } else if (CopyFile(copyFrom, copyTo, TRUE) == 0) {
+                printf("Copying of '%S' to '%S' failed with r/c %d\n",
+                       copyFrom, copyTo, GetLastError());
+            }
+            filesCopied++;
+        }
+
+        /* Find next file */
+        findres = FindNextFile(h, finddata);
+    }
+    FindClose(h);
+
+    /* Search 2 - do subdirs */
+    if (flags & OPT_RECURSIVE) {
+        lstrcpyW(inputpath, srcstem);
+        lstrcatW(inputpath, wchr_star);
+        findres = TRUE;
+        WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
+
+        h = FindFirstFile(inputpath, finddata);
+        while (h != INVALID_HANDLE_VALUE && findres) {
+
+            /* Only looking for dirs */
+            if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+                (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
+                (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
+
+                WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
+
+                /* Make up recursive information */
+                lstrcpyW(inputpath, srcstem);
+                lstrcatW(inputpath, finddata->cFileName);
+                lstrcatW(inputpath, wchr_slash);
+
+                lstrcpyW(outputpath, deststem);
+                if (*destspec == 0x00) {
+                    lstrcatW(outputpath, finddata->cFileName);
+
+                    /* If /E is supplied, create the directory now */
+                    if ((flags & OPT_EMPTYDIR) &&
+                        !(flags & OPT_SIMULATE))
+                        XCOPY_CreateDirectory(outputpath);
+
+                    lstrcatW(outputpath, wchr_slash);
+                }
+
+                XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
+            }
+
+            /* Find next one */
+            findres = FindNextFile(h, finddata);
+        }
+    }
+
+    /* free up memory */
+    HeapFree(GetProcessHeap(), 0, finddata);
+    HeapFree(GetProcessHeap(), 0, inputpath);
+    HeapFree(GetProcessHeap(), 0, outputpath);
+
+    return 0;
+}
+
+/* =========================================================================
+ * Routine copied from cmd.exe md command -
+ * This works recursivly. so creating dir1\dir2\dir3 will create dir1 and
+ * dir2 if they do not already exist.
+ * ========================================================================= */
+static BOOL XCOPY_CreateDirectory(const WCHAR* path)
+{
+    int len;
+    WCHAR *new_path;
+    BOOL ret = TRUE;
+
+    new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
+    lstrcpyW(new_path,path);
+
+    while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
+        new_path[len - 1] = 0;
+
+    while (!CreateDirectory(new_path,NULL))
+    {
+        WCHAR *slash;
+        DWORD last_error = GetLastError();
+        if (last_error == ERROR_ALREADY_EXISTS)
+            break;
+
+        if (last_error != ERROR_PATH_NOT_FOUND)
+        {
+            ret = FALSE;
+            break;
+        }
+
+        if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
+        {
+            ret = FALSE;
+            break;
+        }
+
+        len = slash - new_path;
+        new_path[len] = 0;
+        if (!XCOPY_CreateDirectory(new_path))
+        {
+            ret = FALSE;
+            break;
+        }
+        new_path[len] = '\\';
+    }
+    HeapFree(GetProcessHeap(),0,new_path);
+    return ret;
+}
-- 
GitLab