[XCOPY]
authorAmine Khaldi <amine.khaldi@reactos.org>
Sun, 12 Feb 2012 13:33:30 +0000 (13:33 +0000)
committerAmine Khaldi <amine.khaldi@reactos.org>
Sun, 12 Feb 2012 13:33:30 +0000 (13:33 +0000)
* Sync to Wine 1.3.37.

svn path=/trunk/; revision=55560

reactos/base/applications/cmdutils/xcopy/xcopy.c
reactos/media/doc/README.WINE

index daf269b..03eb899 100644 (file)
 
 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
 
-/* Prototypes */
-static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
-                                  WCHAR *supplieddestination, DWORD *flags);
-static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
-                                   WCHAR *spec, DWORD flags);
-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);
-static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
-static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
-static WCHAR *XCOPY_LoadMessage(UINT id);
-static void XCOPY_FailMessage(DWORD err);
-static int XCOPY_wprintf(const WCHAR *format, ...);
 
 /* Typedefs */
 typedef struct _EXCLUDELIST
@@ -82,8 +66,6 @@ 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                                                        */
@@ -92,490 +74,262 @@ static WCHAR copyTo[MAX_PATH];
 
 
 /* =========================================================================
-   main - Main entrypoint for the xcopy command
-
-     Processes the args, and drives the actual copying
-   ========================================================================= */
-int wmain (int argc, WCHAR *argvW[])
-{
-    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 */
-    WCHAR   copyCmd[MAXSTRING];               /* COPYCMD env var         */
-    DWORD   flags = 0;                        /* Option flags            */
-    const WCHAR PROMPTSTR1[]  = {'/', 'Y', 0};
-    const WCHAR PROMPTSTR2[]  = {'/', 'y', 0};
-    const WCHAR COPYCMD[]  = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
+ * Load a string from the resource file, handling any error
+ * Returns string retrieved from resource file
+ * ========================================================================= */
+static WCHAR *XCOPY_LoadMessage(UINT id) {
+    static WCHAR msg[MAXSTRING];
+    const WCHAR failedMsg[]  = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
 
-    /* Preinitialize flags based on COPYCMD */
-    if (GetEnvironmentVariableW(COPYCMD, copyCmd, MAXSTRING)) {
-        if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
-            wcsstr(copyCmd, PROMPTSTR2) != NULL) {
-            flags |= OPT_NOPROMPT;
-        }
+    if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
+       WINE_FIXME("LoadString failed with %d\n", GetLastError());
+       lstrcpyW(msg, failedMsg);
     }
+    return msg;
+}
 
-    /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
-       wine, but on windows these can be normal files. At least one installer
-       uses files such as .packlist and (validly) expects them to be copied.
-       Under wine, if we do not copy hidden files by default then they get
-       lose                                                                   */
-    flags |= OPT_COPYHIDSYS;
+/* =========================================================================
+ * Output a formatted unicode string. Ideally this will go to the console
+ *  and hence required WriteConsoleW to output it, however if file i/o is
+ *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
+ * ========================================================================= */
+static int __cdecl XCOPY_wprintf(const WCHAR *format, ...) {
+
+    static WCHAR *output_bufW = NULL;
+    static char  *output_bufA = NULL;
+    static BOOL  toConsole    = TRUE;
+    static BOOL  traceOutput  = FALSE;
+#define MAX_WRITECONSOLE_SIZE 65535
+
+    __ms_va_list parms;
+    DWORD   nOut;
+    int len;
+    DWORD   res = 0;
 
     /*
-     * Parse the command line
+     * Allocate buffer to use when writing to console
+     * Note: Not freed - memory will be allocated once and released when
+     *         xcopy ends
      */
-    if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
-                                     &flags)) != RC_OK) {
-        if (rc == RC_HELP)
-            return RC_OK;
-        else
-            return rc;
-    }
 
-    /* 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, flags);
-    if (rc != RC_OK) return rc;
+    if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
+                                              MAX_WRITECONSOLE_SIZE);
+    if (!output_bufW) {
+      WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
+      return 0;
+    }
 
-    /* Extract required information from destination specification */
-    rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
-                               destinationspec, sourcespec, flags);
-    if (rc != RC_OK) return rc;
+    __ms_va_start(parms, format);
+    SetLastError(NO_ERROR);
+    len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_bufW,
+                   MAX_WRITECONSOLE_SIZE/sizeof(*output_bufW), &parms);
+    __ms_va_end(parms);
+    if (len == 0 && GetLastError() != NO_ERROR) {
+      WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
+      return 0;
+    }
 
-    /* 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));
+    /* Try to write as unicode whenever we think it's a console */
+    if (toConsole) {
+      res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
+                          output_bufW, len, &nOut, NULL);
+    }
 
-    /* Pause if necessary */
-    if (flags & OPT_PAUSE) {
-        DWORD count;
-        char pausestr[10];
+    /* If writing to console has failed (ever) we assume it's file
+       i/o so convert to OEM codepage and output                  */
+    if (!res) {
+      BOOL usedDefaultChar = FALSE;
+      DWORD convertedChars;
 
-        XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
-        ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
-                  &count, NULL);
-    }
+      toConsole = FALSE;
 
-    /* Now do the hard work... */
-    rc = XCOPY_DoCopy(sourcestem, sourcespec,
-                destinationstem, destinationspec,
-                flags);
+      /*
+       * Allocate buffer to use when writing to file. Not freed, as above
+       */
+      if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
+                                                MAX_WRITECONSOLE_SIZE);
+      if (!output_bufA) {
+        WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
+        return 0;
+      }
 
-    /* Clear up exclude list allocated memory */
-    while (excludeList) {
-        EXCLUDELIST *pos = excludeList;
-        excludeList = excludeList -> next;
-        HeapFree(GetProcessHeap(), 0, pos->name);
-        HeapFree(GetProcessHeap(), 0, pos);
+      /* Convert to OEM, then output */
+      convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
+                          len, output_bufA, MAX_WRITECONSOLE_SIZE,
+                          "?", &usedDefaultChar);
+      WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
+                &nOut, FALSE);
     }
 
-    /* Finished - print trailer and exit */
-    if (flags & OPT_SIMULATE) {
-        XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
-    } else if (!(flags & OPT_NOCOPY)) {
-        XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
+    /* Trace whether screen or console */
+    if (!traceOutput) {
+      WINE_TRACE("Writing to console? (%d)\n", toConsole);
+      traceOutput = TRUE;
     }
-    if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
-    return rc;
-
+    return nOut;
 }
 
 /* =========================================================================
-   XCOPY_ParseCommandLine - Parses the command line
-   ========================================================================= */
-static BOOL is_whitespace(WCHAR c)
-{
-    return c == ' ' || c == '\t';
-}
-
-static WCHAR *skip_whitespace(WCHAR *p)
-{
-    for (; *p && is_whitespace(*p); p++);
-    return p;
-}
+ * Load a string for a system error and writes it to the screen
+ * Returns string retrieved from resource file
+ * ========================================================================= */
+static void XCOPY_FailMessage(DWORD err) {
+    LPWSTR lpMsgBuf;
+    int status;
 
-/* Windows XCOPY uses a simplified command line parsing algorithm
-   that lacks the escaped-quote logic of build_argv(), because
-   literal double quotes are illegal in any of its arguments.
-   Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
-static int find_end_of_word(const WCHAR *word, WCHAR **end)
-{
-    BOOL in_quotes = 0;
-    const WCHAR *ptr = word;
-    for (;;) {
-        for (; *ptr != '\0' && *ptr != '"' &&
-                 (in_quotes || !is_whitespace(*ptr)); ptr++);
-        if (*ptr == '"') {
-            in_quotes = !in_quotes;
-            ptr++;
-        }
-        /* Odd number of double quotes is illegal for XCOPY */
-        if (in_quotes && *ptr == '\0')
-            return RC_INITERROR;
-        if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
-            break;
+    status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                            FORMAT_MESSAGE_FROM_SYSTEM,
+                            NULL, err, 0,
+                            (LPWSTR) &lpMsgBuf, 0, NULL);
+    if (!status) {
+      WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
+                 err, GetLastError());
+    } else {
+      const WCHAR infostr[] = {'%', '1', '\n', 0};
+      XCOPY_wprintf(infostr, lpMsgBuf);
+      LocalFree ((HLOCAL)lpMsgBuf);
     }
-    *end = (WCHAR*)ptr;
-    return RC_OK;
 }
 
-/* Remove all double quotes from a word */
-static void strip_quotes(WCHAR *word, WCHAR **end)
-{
-    WCHAR *rp, *wp;
-    for (rp = word, wp = word; *rp != '\0'; rp++) {
-        if (*rp == '"')
-            continue;
-        if (wp < rp)
-            *wp = *rp;
-        wp++;
-    }
-    *wp = '\0';
-    *end = wp;
-}
 
-static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
-                                  WCHAR *supplieddestination, DWORD *pflags)
+/* =========================================================================
+ * Routine copied from cmd.exe md command -
+ * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
+ * dir2 if they do not already exist.
+ * ========================================================================= */
+static BOOL XCOPY_CreateDirectory(const WCHAR* path)
 {
-    const WCHAR EXCLUDE[]  = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
-    DWORD flags = *pflags;
-    WCHAR *cmdline, *word, *end, *next;
-    int rc = RC_INITERROR;
+    int len;
+    WCHAR *new_path;
+    BOOL ret = TRUE;
 
-    cmdline = _wcsdup(GetCommandLineW());
-    if (cmdline == NULL)
-        return rc;
+    new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
+    lstrcpyW(new_path,path);
 
-    /* Skip first arg, which is the program name */
-    if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
-        goto out;
-    word = skip_whitespace(word);
+    while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
+        new_path[len - 1] = 0;
 
-    while (*word)
+    while (!CreateDirectoryW(new_path,NULL))
     {
-        WCHAR first;
-        if ((rc = find_end_of_word(word, &end)) != RC_OK)
-            goto out;
+        WCHAR *slash;
+        DWORD last_error = GetLastError();
+        if (last_error == ERROR_ALREADY_EXISTS)
+            break;
 
-        next = skip_whitespace(end);
-        first = word[0];
-        *end = '\0';
-        strip_quotes(word, &end);
-        WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
-
-        /* First non-switch parameter is source, second is destination */
-        if (first != '/') {
-            if (suppliedsource[0] == 0x00) {
-                lstrcpyW(suppliedsource, word);
-            } else if (supplieddestination[0] == 0x00) {
-                lstrcpyW(supplieddestination, word);
-            } else {
-                XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
-                goto out;
-            }
-        } else {
-            /* Process all the switch options
-                 Note: Windows docs say /P prompts when dest is created
-                       but tests show it is done for each src file
-                       regardless of the destination                   */
-            switch (toupper(word[1])) {
-            case 'I': flags |= OPT_ASSUMEDIR;     break;
-            case 'S': flags |= OPT_RECURSIVE;     break;
-            case 'Q': flags |= OPT_QUIET;         break;
-            case 'F': flags |= OPT_FULL;          break;
-            case 'L': flags |= OPT_SIMULATE;      break;
-            case 'W': flags |= OPT_PAUSE;         break;
-            case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
-            case 'Y': flags |= OPT_NOPROMPT;      break;
-            case 'N': flags |= OPT_SHORTNAME;     break;
-            case 'U': flags |= OPT_MUSTEXIST;     break;
-            case 'R': flags |= OPT_REPLACEREAD;   break;
-            case 'H': flags |= OPT_COPYHIDSYS;    break;
-            case 'C': flags |= OPT_IGNOREERRORS;  break;
-            case 'P': flags |= OPT_SRCPROMPT;     break;
-            case 'A': flags |= OPT_ARCHIVEONLY;   break;
-            case 'M': flags |= OPT_ARCHIVEONLY |
-                               OPT_REMOVEARCH;    break;
-
-            /* E can be /E or /EXCLUDE */
-            case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
-                                         NORM_IGNORECASE | SORT_STRINGSORT,
-                                         &word[1], 8,
-                                         EXCLUDE, -1) == 2) {
-                        if (XCOPY_ProcessExcludeList(&word[9])) {
-                          XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
-                          goto out;
-                        } else flags |= OPT_EXCLUDELIST;
-                      } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
-                      break;
-
-            /* D can be /D or /D: */
-            case 'D': if (word[2]==':' && isdigit(word[3])) {
-                          SYSTEMTIME st;
-                          WCHAR     *pos = &word[3];
-                          BOOL       isError = FALSE;
-                          memset(&st, 0x00, sizeof(st));
-
-                          /* Parse the arg : Month */
-                          st.wMonth = _wtol(pos);
-                          while (*pos && isdigit(*pos)) pos++;
-                          if (*pos++ != '-') isError = TRUE;
-
-                          /* Parse the arg : Day */
-                          if (!isError) {
-                              st.wDay = _wtol(pos);
-                              while (*pos && isdigit(*pos)) pos++;
-                              if (*pos++ != '-') isError = TRUE;
-                          }
-
-                          /* Parse the arg : Day */
-                          if (!isError) {
-                              st.wYear = _wtol(pos);
-                              while (*pos && isdigit(*pos)) pos++;
-                              if (st.wYear < 100) st.wYear+=2000;
-                          }
-
-                          if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
-                              SYSTEMTIME st;
-                              WCHAR datestring[32], timestring[32];
-
-                              flags |= OPT_DATERANGE;
-
-                              /* Debug info: */
-                              FileTimeToSystemTime (&dateRange, &st);
-                              GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
-                                             sizeof(datestring)/sizeof(WCHAR));
-                              GetTimeFormatW(0, TIME_NOSECONDS, &st,
-                                             NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
+        if (last_error != ERROR_PATH_NOT_FOUND)
+        {
+            ret = FALSE;
+            break;
+        }
 
-                              WINE_TRACE("Date being used is: %s %s\n",
-                                         wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
-                          } else {
-                              XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
-                              goto out;
-                          }
-                      } else {
-                          flags |= OPT_DATENEWER;
-                      }
-                      break;
+        if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
+        {
+            ret = FALSE;
+            break;
+        }
 
-            case '-': if (toupper(word[2])=='Y')
-                          flags &= ~OPT_NOPROMPT; break;
-            case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
-                      rc = RC_HELP;
-                      goto out;
-            default:
-                WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
-                XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
-                goto out;
-            }
+        len = slash - new_path;
+        new_path[len] = 0;
+        if (!XCOPY_CreateDirectory(new_path))
+        {
+            ret = FALSE;
+            break;
         }
-        word = next;
+        new_path[len] = '\\';
     }
-
-    /* Default the destination if not supplied */
-    if (supplieddestination[0] == 0x00)
-        lstrcpyW(supplieddestination, wchr_dot);
-
-    *pflags = flags;
-    rc = RC_OK;
-
- out:
-    free(cmdline);
-    return rc;
+    HeapFree(GetProcessHeap(),0,new_path);
+    return ret;
 }
 
-
 /* =========================================================================
-   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, DWORD flags)
-{
-    WCHAR             actualsource[MAX_PATH];
-    WCHAR            *starPos;
-    WCHAR            *questPos;
-    DWORD             attribs;
-
-    /*
-     * Validate the source, expanding to full path ensuring it exists
-     */
-    if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
-        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
-        return RC_INITERROR;
-    }
-
-    /* If full names required, convert to using the full path */
-    if (flags & OPT_FULL) {
-        lstrcpyW(suppliedsource, actualsource);
-    }
-
-    /*
-     * Work out the stem of the source
-     */
-
-    /* If a directory is supplied, use that as-is (either fully or
-          partially qualified)
-       If a filename is supplied + a directory or drive path, use that
-          as-is
-       Otherwise
-          If no directory or path specified, add eg. C:
-          stem is Drive/Directory is bit up to last \ (or first :)
-          spec is bit after that                                         */
-
-    starPos = wcschr(suppliedsource, '*');
-    questPos = wcschr(suppliedsource, '?');
-    if (starPos || questPos) {
-        attribs = 0x00;  /* Ensures skips invalid or directory check below */
-    } else {
-        attribs = GetFileAttributesW(actualsource);
-    }
-
-    if (attribs == INVALID_FILE_ATTRIBUTES) {
-        XCOPY_FailMessage(GetLastError());
-        return RC_INITERROR;
-
-    /* Directory:
-         stem should be exactly as supplied plus a '\', unless it was
-          eg. C: in which case no slash required */
-    } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
-        WCHAR lastChar;
-
-        WINE_TRACE("Directory supplied\n");
-        lstrcpyW(stem, suppliedsource);
-        lastChar = stem[lstrlenW(stem)-1];
-        if (lastChar != '\\' && lastChar != ':') {
-            lstrcatW(stem, wchr_slash);
-        }
-        lstrcpyW(spec, wchr_star);
+ * Process a single file from the /EXCLUDE: file list, building up a list
+ * of substrings to avoid copying
+ * Returns TRUE on any failure
+ * ========================================================================= */
+static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
 
-    /* File or wildcard search:
-         stem should be:
-           Up to and including last slash if directory path supplied
-           If c:filename supplied, just the c:
-           Otherwise stem should be the current drive letter + ':' */
-    } else {
-        WCHAR *lastDir;
+    WCHAR   endChar = *endOfName;
+    WCHAR   buffer[MAXSTRING];
+    FILE   *inFile  = NULL;
+    const WCHAR readTextMode[]  = {'r', 't', 0};
 
-        WINE_TRACE("Filename supplied\n");
-        lastDir   = wcsrchr(suppliedsource, '\\');
+    /* Null terminate the filename (temporarily updates the filename hence
+         parms not const)                                                 */
+    *endOfName = 0x00;
 
-        if (lastDir) {
-            lstrcpyW(stem, suppliedsource);
-            stem[(lastDir-suppliedsource) + 1] = 0x00;
-            lstrcpyW(spec, (lastDir+1));
-        } else if (suppliedsource[1] == ':') {
-            lstrcpyW(stem, suppliedsource);
-            stem[2] = 0x00;
-            lstrcpyW(spec, suppliedsource+2);
-        } else {
-            WCHAR curdir[MAXSTRING];
-            GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
-            stem[0] = curdir[0];
-            stem[1] = curdir[1];
-            stem[2] = 0x00;
-            lstrcpyW(spec, suppliedsource);
-        }
+    /* Open the file */
+    inFile = _wfopen(filename, readTextMode);
+    if (inFile == NULL) {
+        XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
+        *endOfName = endChar;
+        return TRUE;
     }
 
-    return RC_OK;
-}
+    /* Process line by line */
+    while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
+        EXCLUDELIST *thisEntry;
+        int length = lstrlenW(buffer);
 
-/* =========================================================================
-   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;
+        /* Strip CRLF */
+        buffer[length-1] = 0x00;
 
-    /*
-     * Validate the source, expanding to full path ensuring it exists
-     */
-    if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
-        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
-        return RC_INITERROR;
+        /* If more than CRLF */
+        if (length > 1) {
+          thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
+          thisEntry->next = excludeList;
+          excludeList = thisEntry;
+          thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
+                                      (length * sizeof(WCHAR))+1);
+          lstrcpyW(thisEntry->name, buffer);
+          CharUpperBuffW(thisEntry->name, length);
+          WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
+        }
     }
 
-    /* Destination is either a directory or a file */
-    attribs = GetFileAttributesW(actualdestination);
-
-    if (attribs == INVALID_FILE_ATTRIBUTES) {
-
-        /* If /I supplied and wildcard copy, assume directory */
-        /* Also if destination ends with backslash */
-        if ((flags & OPT_ASSUMEDIR &&
-            (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) ||
-            (supplieddestination[lstrlenW(supplieddestination)-1] == '\\')) {
-
-            isDir = TRUE;
+    /* See if EOF or error occurred */
+    if (!feof(inFile)) {
+        XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
+        *endOfName = endChar;
+        return TRUE;
+    }
 
-        } else {
-            DWORD count;
-            char  answer[10] = "";
-            WCHAR fileChar[2];
-            WCHAR dirChar[2];
+    /* Revert the input string to original form, and cleanup + return */
+    *endOfName = endChar;
+    fclose(inFile);
+    return FALSE;
+}
 
-            /* Read the F and D characters from the resource file */
-            wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
-            wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
+/* =========================================================================
+ * Process the /EXCLUDE: file list, building up a list of substrings to
+ * avoid copying
+ * Returns TRUE on any failure
+ * ========================================================================= */
+static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
 
-            while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
-                XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
+    WCHAR *filenameStart = parms;
 
-                ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
-                WINE_TRACE("User answer %c\n", answer[0]);
+    WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
+    excludeList = NULL;
 
-                answer[0] = toupper(answer[0]);
-            }
+    while (*parms && *parms != ' ' && *parms != '/') {
 
-            if (answer[0] == dirChar[0]) {
-                isDir = TRUE;
-            } else {
-                isDir = FALSE;
+        /* If found '+' then process the file found so far */
+        if (*parms == '+') {
+            if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
+                return TRUE;
             }
+            filenameStart = parms+1;
         }
-    } else {
-        isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
+        parms++;
     }
 
-    if (isDir) {
-        lstrcpyW(stem, actualdestination);
-        *spec = 0x00;
-
-        /* Ensure ends with a '\' */
-        if (stem[lstrlenW(stem)-1] != '\\') {
-            lstrcatW(stem, wchr_slash);
+    if (filenameStart != parms) {
+        if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
+            return TRUE;
         }
-
-    } 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;
+
+    return FALSE;
 }
 
 /* =========================================================================
@@ -782,12 +536,12 @@ static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
                 if (flags & OPT_QUIET) {
                     /* Skip message */
                 } else if (flags & OPT_FULL) {
-                    const WCHAR infostr[]   = {'%', 's', ' ', '-', '>', ' ',
-                                               '%', 's', '\n', 0};
+                    const WCHAR infostr[]   = {'%', '1', ' ', '-', '>', ' ',
+                                               '%', '2', '\n', 0};
 
                     XCOPY_wprintf(infostr, copyFrom, copyTo);
                 } else {
-                    const WCHAR infostr[] = {'%', 's', '\n', 0};
+                    const WCHAR infostr[] = {'%', '1', '\n', 0};
                     XCOPY_wprintf(infostr, copyFrom);
                 }
 
@@ -839,303 +593,536 @@ static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
         findres = TRUE;
         WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
 
-        h = FindFirstFileW(inputpath, finddata);
-        while (h != INVALID_HANDLE_VALUE && findres) {
+        h = FindFirstFileW(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 = FindNextFileW(h, finddata);
+        }
+    }
+
+cleanup:
+
+    /* free up memory */
+    HeapFree(GetProcessHeap(), 0, finddata);
+    HeapFree(GetProcessHeap(), 0, inputpath);
+    HeapFree(GetProcessHeap(), 0, outputpath);
+
+    return ret;
+}
+
+
+/* =========================================================================
+   XCOPY_ParseCommandLine - Parses the command line
+   ========================================================================= */
+static BOOL is_whitespace(WCHAR c)
+{
+    return c == ' ' || c == '\t';
+}
+
+static WCHAR *skip_whitespace(WCHAR *p)
+{
+    for (; *p && is_whitespace(*p); p++);
+    return p;
+}
+
+/* Windows XCOPY uses a simplified command line parsing algorithm
+   that lacks the escaped-quote logic of build_argv(), because
+   literal double quotes are illegal in any of its arguments.
+   Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
+static int find_end_of_word(const WCHAR *word, WCHAR **end)
+{
+    BOOL in_quotes = 0;
+    const WCHAR *ptr = word;
+    for (;;) {
+        for (; *ptr != '\0' && *ptr != '"' &&
+                 (in_quotes || !is_whitespace(*ptr)); ptr++);
+        if (*ptr == '"') {
+            in_quotes = !in_quotes;
+            ptr++;
+        }
+        /* Odd number of double quotes is illegal for XCOPY */
+        if (in_quotes && *ptr == '\0')
+            return RC_INITERROR;
+        if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
+            break;
+    }
+    *end = (WCHAR*)ptr;
+    return RC_OK;
+}
+
+/* Remove all double quotes from a word */
+static void strip_quotes(WCHAR *word, WCHAR **end)
+{
+    WCHAR *rp, *wp;
+    for (rp = word, wp = word; *rp != '\0'; rp++) {
+        if (*rp == '"')
+            continue;
+        if (wp < rp)
+            *wp = *rp;
+        wp++;
+    }
+    *wp = '\0';
+    *end = wp;
+}
+
+static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
+                                  WCHAR *supplieddestination, DWORD *pflags)
+{
+    const WCHAR EXCLUDE[]  = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
+    DWORD flags = *pflags;
+    WCHAR *cmdline, *word, *end, *next;
+    int rc = RC_INITERROR;
+
+    cmdline = _wcsdup(GetCommandLineW());
+    if (cmdline == NULL)
+        return rc;
+
+    /* Skip first arg, which is the program name */
+    if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
+        goto out;
+    word = skip_whitespace(word);
+
+    while (*word)
+    {
+        WCHAR first;
+        if ((rc = find_end_of_word(word, &end)) != RC_OK)
+            goto out;
+
+        next = skip_whitespace(end);
+        first = word[0];
+        *end = '\0';
+        strip_quotes(word, &end);
+        WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
+
+        /* First non-switch parameter is source, second is destination */
+        if (first != '/') {
+            if (suppliedsource[0] == 0x00) {
+                lstrcpyW(suppliedsource, word);
+            } else if (supplieddestination[0] == 0x00) {
+                lstrcpyW(supplieddestination, word);
+            } else {
+                XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
+                goto out;
+            }
+        } else {
+            /* Process all the switch options
+                 Note: Windows docs say /P prompts when dest is created
+                       but tests show it is done for each src file
+                       regardless of the destination                   */
+            switch (toupper(word[1])) {
+            case 'I': flags |= OPT_ASSUMEDIR;     break;
+            case 'S': flags |= OPT_RECURSIVE;     break;
+            case 'Q': flags |= OPT_QUIET;         break;
+            case 'F': flags |= OPT_FULL;          break;
+            case 'L': flags |= OPT_SIMULATE;      break;
+            case 'W': flags |= OPT_PAUSE;         break;
+            case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
+            case 'Y': flags |= OPT_NOPROMPT;      break;
+            case 'N': flags |= OPT_SHORTNAME;     break;
+            case 'U': flags |= OPT_MUSTEXIST;     break;
+            case 'R': flags |= OPT_REPLACEREAD;   break;
+            case 'H': flags |= OPT_COPYHIDSYS;    break;
+            case 'C': flags |= OPT_IGNOREERRORS;  break;
+            case 'P': flags |= OPT_SRCPROMPT;     break;
+            case 'A': flags |= OPT_ARCHIVEONLY;   break;
+            case 'M': flags |= OPT_ARCHIVEONLY |
+                               OPT_REMOVEARCH;    break;
+
+            /* E can be /E or /EXCLUDE */
+            case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
+                                         NORM_IGNORECASE | SORT_STRINGSORT,
+                                         &word[1], 8,
+                                         EXCLUDE, -1) == 2) {
+                        if (XCOPY_ProcessExcludeList(&word[9])) {
+                          XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+                          goto out;
+                        } else flags |= OPT_EXCLUDELIST;
+                      } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
+                      break;
+
+            /* D can be /D or /D: */
+            case 'D': if (word[2]==':' && isdigit(word[3])) {
+                          SYSTEMTIME st;
+                          WCHAR     *pos = &word[3];
+                          BOOL       isError = FALSE;
+                          memset(&st, 0x00, sizeof(st));
 
-            /* Only looking for dirs */
-            if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
-                (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
-                (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
+                          /* Parse the arg : Month */
+                          st.wMonth = _wtol(pos);
+                          while (*pos && isdigit(*pos)) pos++;
+                          if (*pos++ != '-') isError = TRUE;
 
-                WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
+                          /* Parse the arg : Day */
+                          if (!isError) {
+                              st.wDay = _wtol(pos);
+                              while (*pos && isdigit(*pos)) pos++;
+                              if (*pos++ != '-') isError = TRUE;
+                          }
 
-                /* Make up recursive information */
-                lstrcpyW(inputpath, srcstem);
-                lstrcatW(inputpath, finddata->cFileName);
-                lstrcatW(inputpath, wchr_slash);
+                          /* Parse the arg : Year */
+                          if (!isError) {
+                              st.wYear = _wtol(pos);
+                              while (*pos && isdigit(*pos)) pos++;
+                              if (st.wYear < 100) st.wYear+=2000;
+                          }
 
-                lstrcpyW(outputpath, deststem);
-                if (*destspec == 0x00) {
-                    lstrcatW(outputpath, finddata->cFileName);
+                          if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
+                              SYSTEMTIME st;
+                              WCHAR datestring[32], timestring[32];
 
-                    /* If /E is supplied, create the directory now */
-                    if ((flags & OPT_EMPTYDIR) &&
-                        !(flags & OPT_SIMULATE))
-                        XCOPY_CreateDirectory(outputpath);
+                              flags |= OPT_DATERANGE;
 
-                    lstrcatW(outputpath, wchr_slash);
-                }
+                              /* Debug info: */
+                              FileTimeToSystemTime (&dateRange, &st);
+                              GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
+                                             sizeof(datestring)/sizeof(WCHAR));
+                              GetTimeFormatW(0, TIME_NOSECONDS, &st,
+                                             NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
 
-                XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
-            }
+                              WINE_TRACE("Date being used is: %s %s\n",
+                                         wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
+                          } else {
+                              XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+                              goto out;
+                          }
+                      } else {
+                          flags |= OPT_DATENEWER;
+                      }
+                      break;
 
-            /* Find next one */
-            findres = FindNextFileW(h, finddata);
+            case '-': if (toupper(word[2])=='Y')
+                          flags &= ~OPT_NOPROMPT; break;
+            case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
+                      rc = RC_HELP;
+                      goto out;
+            default:
+                WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
+                XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
+                goto out;
+            }
         }
+        word = next;
     }
 
-cleanup:
+    /* Default the destination if not supplied */
+    if (supplieddestination[0] == 0x00)
+        lstrcpyW(supplieddestination, wchr_dot);
 
-    /* free up memory */
-    HeapFree(GetProcessHeap(), 0, finddata);
-    HeapFree(GetProcessHeap(), 0, inputpath);
-    HeapFree(GetProcessHeap(), 0, outputpath);
+    *pflags = flags;
+    rc = RC_OK;
 
-    return ret;
+ out:
+    free(cmdline);
+    return rc;
 }
 
+
 /* =========================================================================
- * Routine copied from cmd.exe md command -
- * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
- * dir2 if they do not already exist.
- * ========================================================================= */
-static BOOL XCOPY_CreateDirectory(const WCHAR* path)
+   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, DWORD flags)
 {
-    int len;
-    WCHAR *new_path;
-    BOOL ret = TRUE;
-
-    new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
-    lstrcpyW(new_path,path);
+    WCHAR             actualsource[MAX_PATH];
+    WCHAR            *starPos;
+    WCHAR            *questPos;
+    DWORD             attribs;
 
-    while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
-        new_path[len - 1] = 0;
+    /*
+     * Validate the source, expanding to full path ensuring it exists
+     */
+    if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
+        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
+        return RC_INITERROR;
+    }
 
-    while (!CreateDirectoryW(new_path,NULL))
-    {
-        WCHAR *slash;
-        DWORD last_error = GetLastError();
-        if (last_error == ERROR_ALREADY_EXISTS)
-            break;
+    /* If full names required, convert to using the full path */
+    if (flags & OPT_FULL) {
+        lstrcpyW(suppliedsource, actualsource);
+    }
 
-        if (last_error != ERROR_PATH_NOT_FOUND)
-        {
-            ret = FALSE;
-            break;
-        }
+    /*
+     * Work out the stem of the source
+     */
 
-        if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
-        {
-            ret = FALSE;
-            break;
-        }
+    /* If a directory is supplied, use that as-is (either fully or
+          partially qualified)
+       If a filename is supplied + a directory or drive path, use that
+          as-is
+       Otherwise
+          If no directory or path specified, add eg. C:
+          stem is Drive/Directory is bit up to last \ (or first :)
+          spec is bit after that                                         */
 
-        len = slash - new_path;
-        new_path[len] = 0;
-        if (!XCOPY_CreateDirectory(new_path))
-        {
-            ret = FALSE;
-            break;
-        }
-        new_path[len] = '\\';
+    starPos = wcschr(suppliedsource, '*');
+    questPos = wcschr(suppliedsource, '?');
+    if (starPos || questPos) {
+        attribs = 0x00;  /* Ensures skips invalid or directory check below */
+    } else {
+        attribs = GetFileAttributesW(actualsource);
     }
-    HeapFree(GetProcessHeap(),0,new_path);
-    return ret;
-}
 
-/* =========================================================================
- * Process the /EXCLUDE: file list, building up a list of substrings to
- * avoid copying
- * Returns TRUE on any failure
- * ========================================================================= */
-static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
+    if (attribs == INVALID_FILE_ATTRIBUTES) {
+        XCOPY_FailMessage(GetLastError());
+        return RC_INITERROR;
 
-    WCHAR *filenameStart = parms;
+    /* Directory:
+         stem should be exactly as supplied plus a '\', unless it was
+          eg. C: in which case no slash required */
+    } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
+        WCHAR lastChar;
 
-    WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
-    excludeList = NULL;
+        WINE_TRACE("Directory supplied\n");
+        lstrcpyW(stem, suppliedsource);
+        lastChar = stem[lstrlenW(stem)-1];
+        if (lastChar != '\\' && lastChar != ':') {
+            lstrcatW(stem, wchr_slash);
+        }
+        lstrcpyW(spec, wchr_star);
 
-    while (*parms && *parms != ' ' && *parms != '/') {
+    /* File or wildcard search:
+         stem should be:
+           Up to and including last slash if directory path supplied
+           If c:filename supplied, just the c:
+           Otherwise stem should be the current drive letter + ':' */
+    } else {
+        WCHAR *lastDir;
 
-        /* If found '+' then process the file found so far */
-        if (*parms == '+') {
-            if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
-                return TRUE;
-            }
-            filenameStart = parms+1;
-        }
-        parms++;
-    }
+        WINE_TRACE("Filename supplied\n");
+        lastDir   = wcsrchr(suppliedsource, '\\');
 
-    if (filenameStart != parms) {
-        if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
-            return TRUE;
+        if (lastDir) {
+            lstrcpyW(stem, suppliedsource);
+            stem[(lastDir-suppliedsource) + 1] = 0x00;
+            lstrcpyW(spec, (lastDir+1));
+        } else if (suppliedsource[1] == ':') {
+            lstrcpyW(stem, suppliedsource);
+            stem[2] = 0x00;
+            lstrcpyW(spec, suppliedsource+2);
+        } else {
+            WCHAR curdir[MAXSTRING];
+            GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
+            stem[0] = curdir[0];
+            stem[1] = curdir[1];
+            stem[2] = 0x00;
+            lstrcpyW(spec, suppliedsource);
         }
     }
 
-    return FALSE;
+    return RC_OK;
 }
 
 /* =========================================================================
- * Process a single file from the /EXCLUDE: file list, building up a list
- * of substrings to avoid copying
- * Returns TRUE on any failure
- * ========================================================================= */
-static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
+   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 (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
+        WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
+        return RC_INITERROR;
+    }
 
-    WCHAR   endChar = *endOfName;
-    WCHAR   buffer[MAXSTRING];
-    FILE   *inFile  = NULL;
-    const WCHAR readTextMode[]  = {'r', 't', 0};
+    /* Destination is either a directory or a file */
+    attribs = GetFileAttributesW(actualdestination);
 
-    /* Null terminate the filename (temporarily updates the filename hence
-         parms not const)                                                 */
-    *endOfName = 0x00;
+    if (attribs == INVALID_FILE_ATTRIBUTES) {
 
-    /* Open the file */
-    inFile = _wfopen(filename, readTextMode);
-    if (inFile == NULL) {
-        XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
-        *endOfName = endChar;
-        return TRUE;
-    }
+        /* If /I supplied and wildcard copy, assume directory */
+        /* Also if destination ends with backslash */
+        if ((flags & OPT_ASSUMEDIR &&
+            (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) ||
+            (supplieddestination[lstrlenW(supplieddestination)-1] == '\\')) {
 
-    /* Process line by line */
-    while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
-        EXCLUDELIST *thisEntry;
-        int length = lstrlenW(buffer);
+            isDir = TRUE;
 
-        /* Strip CRLF */
-        buffer[length-1] = 0x00;
+        } else {
+            DWORD count;
+            char  answer[10] = "";
+            WCHAR fileChar[2];
+            WCHAR dirChar[2];
 
-        /* If more than CRLF */
-        if (length > 1) {
-          thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
-          thisEntry->next = excludeList;
-          excludeList = thisEntry;
-          thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
-                                      (length * sizeof(WCHAR))+1);
-          lstrcpyW(thisEntry->name, buffer);
-          CharUpperBuffW(thisEntry->name, length);
-          WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
-        }
-    }
+            /* Read the F and D characters from the resource file */
+            wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
+            wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
 
-    /* See if EOF or error occurred */
-    if (!feof(inFile)) {
-        XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
-        *endOfName = endChar;
-        return TRUE;
-    }
+            while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
+                XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
 
-    /* Revert the input string to original form, and cleanup + return */
-    *endOfName = endChar;
-    fclose(inFile);
-    return FALSE;
-}
+                ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
+                WINE_TRACE("User answer %c\n", answer[0]);
 
-/* =========================================================================
- * Load a string from the resource file, handling any error
- * Returns string retrieved from resource file
- * ========================================================================= */
-static WCHAR *XCOPY_LoadMessage(UINT id) {
-    static WCHAR msg[MAXSTRING];
-    const WCHAR failedMsg[]  = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
+                answer[0] = toupper(answer[0]);
+            }
 
-    if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
-       WINE_FIXME("LoadString failed with %d\n", GetLastError());
-       lstrcpyW(msg, failedMsg);
+            if (answer[0] == dirChar[0]) {
+                isDir = TRUE;
+            } else {
+                isDir = FALSE;
+            }
+        }
+    } else {
+        isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
     }
-    return msg;
-}
 
-/* =========================================================================
- * Load a string for a system error and writes it to the screen
- * Returns string retrieved from resource file
- * ========================================================================= */
-static void XCOPY_FailMessage(DWORD err) {
-    LPWSTR lpMsgBuf;
-    int status;
+    if (isDir) {
+        lstrcpyW(stem, actualdestination);
+        *spec = 0x00;
+
+        /* Ensure ends with a '\' */
+        if (stem[lstrlenW(stem)-1] != '\\') {
+            lstrcatW(stem, wchr_slash);
+        }
 
-    status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
-                            FORMAT_MESSAGE_FROM_SYSTEM,
-                            NULL, err, 0,
-                            (LPWSTR) &lpMsgBuf, 0, NULL);
-    if (!status) {
-      WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
-                 err, GetLastError());
     } else {
-      const WCHAR infostr[] = {'%', 's', '\n', 0};
-      XCOPY_wprintf(infostr, lpMsgBuf);
-      LocalFree ((HLOCAL)lpMsgBuf);
+        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;
 }
 
+
 /* =========================================================================
- * Output a formatted unicode string. Ideally this will go to the console
- *  and hence required WriteConsoleW to output it, however if file i/o is
- *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
- * ========================================================================= */
-int XCOPY_wprintf(const WCHAR *format, ...) {
+   main - Main entrypoint for the xcopy command
 
-    static WCHAR *output_bufW = NULL;
-    static char  *output_bufA = NULL;
-    static BOOL  toConsole    = TRUE;
-    static BOOL  traceOutput  = FALSE;
-#define MAX_WRITECONSOLE_SIZE 65535
+     Processes the args, and drives the actual copying
+   ========================================================================= */
+int wmain (int argc, WCHAR *argvW[])
+{
+    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 */
+    WCHAR   copyCmd[MAXSTRING];               /* COPYCMD env var         */
+    DWORD   flags = 0;                        /* Option flags            */
+    const WCHAR PROMPTSTR1[]  = {'/', 'Y', 0};
+    const WCHAR PROMPTSTR2[]  = {'/', 'y', 0};
+    const WCHAR COPYCMD[]  = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
 
-    va_list parms;
-    DWORD   nOut;
-    int len;
-    DWORD   res = 0;
+    /* Preinitialize flags based on COPYCMD */
+    if (GetEnvironmentVariableW(COPYCMD, copyCmd, MAXSTRING)) {
+        if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
+            wcsstr(copyCmd, PROMPTSTR2) != NULL) {
+            flags |= OPT_NOPROMPT;
+        }
+    }
+
+    /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
+       wine, but on windows these can be normal files. At least one installer
+       uses files such as .packlist and (validly) expects them to be copied.
+       Under wine, if we do not copy hidden files by default then they get
+       lose                                                                   */
+    flags |= OPT_COPYHIDSYS;
 
     /*
-     * Allocate buffer to use when writing to console
-     * Note: Not freed - memory will be allocated once and released when
-     *         xcopy ends
+     * Parse the command line
      */
-
-    if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
-                                              MAX_WRITECONSOLE_SIZE);
-    if (!output_bufW) {
-      WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
-      return 0;
+    if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
+                                     &flags)) != RC_OK) {
+        if (rc == RC_HELP)
+            return RC_OK;
+        else
+            return rc;
     }
 
-    va_start(parms, format);
-    len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
-    va_end(parms);
-    if (len < 0) {
-      WINE_FIXME("String too long.\n");
-      return 0;
-    }
+    /* 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));
 
-    /* Try to write as unicode all the time we think its a console */
-    if (toConsole) {
-      res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
-                          output_bufW, len, &nOut, NULL);
-    }
+    /* Extract required information from source specification */
+    rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
+    if (rc != RC_OK) return rc;
 
-    /* If writing to console has failed (ever) we assume its file
-       i/o so convert to OEM codepage and output                  */
-    if (!res) {
-      BOOL usedDefaultChar = FALSE;
-      DWORD convertedChars;
+    /* Extract required information from destination specification */
+    rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
+                               destinationspec, sourcespec, flags);
+    if (rc != RC_OK) return rc;
 
-      toConsole = FALSE;
+    /* 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));
 
-      /*
-       * Allocate buffer to use when writing to file. Not freed, as above
-       */
-      if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
-                                                MAX_WRITECONSOLE_SIZE);
-      if (!output_bufA) {
-        WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
-        return 0;
-      }
+    /* Pause if necessary */
+    if (flags & OPT_PAUSE) {
+        DWORD count;
+        char pausestr[10];
 
-      /* Convert to OEM, then output */
-      convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
-                          len, output_bufA, MAX_WRITECONSOLE_SIZE,
-                          "?", &usedDefaultChar);
-      WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
-                &nOut, FALSE);
+        XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
+        ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
+                  &count, NULL);
     }
 
-    /* Trace whether screen or console */
-    if (!traceOutput) {
-      WINE_TRACE("Writing to console? (%d)\n", toConsole);
-      traceOutput = TRUE;
+    /* Now do the hard work... */
+    rc = XCOPY_DoCopy(sourcestem, sourcespec,
+                destinationstem, destinationspec,
+                flags);
+
+    /* Clear up exclude list allocated memory */
+    while (excludeList) {
+        EXCLUDELIST *pos = excludeList;
+        excludeList = excludeList -> next;
+        HeapFree(GetProcessHeap(), 0, pos->name);
+        HeapFree(GetProcessHeap(), 0, pos);
     }
-    return nOut;
+
+    /* Finished - print trailer and exit */
+    if (flags & OPT_SIMULATE) {
+        XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
+    } else if (!(flags & OPT_NOCOPY)) {
+        XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
+    }
+    if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
+    return rc;
+
 }
index 91be0fe..313d72a 100644 (file)
@@ -194,7 +194,7 @@ reactos/dll/cpl/inetcpl           # Synced to Wine-1.3.21
 
 ReactOS shares the following programs with Winehq.
 
-reactos/base/applications/cmdutils/xcopy  # Autosync
+reactos/base/applications/cmdutils/xcopy  # Synced to Wine-1.3.37
 reactos/base/applications/games/winmine   # Forked at Wine-1_3_5
 reactos/base/applications/extrac32        # Autosync
 reactos/base/applications/iexplore        # Autosync