+/*
+ * 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
+ */
+
+/*
+ * FIXME:
+ * This should now support all options listed in the xcopy help from
+ * windows XP except:
+ * /Z - Copy from network drives in restartable mode
+ * /X - Copy file audit settings (sets /O)
+ * /O - Copy file ownership + ACL info
+ * /G - Copy encrypted files to unencrypted destination
+ * /V - Verifies files
+ */
+
+/*
+ * 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>
+#include "xcopy.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
+
+/* Prototypes */
+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
+{
+ struct _EXCLUDELIST *next;
+ WCHAR *name;
+} EXCLUDELIST;
+
+
+/* Global variables */
+static ULONG filesCopied = 0; /* Number of files copied */
+static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
+static FILETIME dateRange; /* Date range to copy after*/
+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 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};
+ const WCHAR EXCLUDE[] = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
+
+ /*
+ * Parse the command line
+ */
+
+ /* Confirm at least one parameter */
+ if (argc < 2) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
+ return RC_INITERROR;
+ }
+
+ /* Preinitialize flags based on COPYCMD */
+ if (GetEnvironmentVariable(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;
+
+ /* 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 {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
+ return RC_INITERROR;
+ }
+ } 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(argvW[0][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 (CompareString (LOCALE_USER_DEFAULT,
+ NORM_IGNORECASE | SORT_STRINGSORT,
+ &argvW[0][1], 8,
+ EXCLUDE, -1) == 2) {
+ if (XCOPY_ProcessExcludeList(&argvW[0][9])) {
+ XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+ return RC_INITERROR;
+ } else flags |= OPT_EXCLUDELIST;
+ } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
+ break;
+
+ /* D can be /D or /D: */
+ case 'D': if ((argvW[0][2])==':' && isdigit(argvW[0][3])) {
+ SYSTEMTIME st;
+ WCHAR *pos = &argvW[0][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);
+ 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);
+ GetDateFormat (0, DATE_SHORTDATE, &st, NULL, datestring,
+ sizeof(datestring));
+ GetTimeFormat (0, TIME_NOSECONDS, &st,
+ NULL, timestring, sizeof(timestring));
+
+ WINE_TRACE("Date being used is: %s %s\n",
+ wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
+ } else {
+ XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
+ return RC_INITERROR;
+ }
+ } else {
+ flags |= OPT_DATENEWER;
+ }
+ break;
+
+ case '-': if (toupper(argvW[0][2])=='Y')
+ flags &= ~OPT_NOPROMPT; break;
+ case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
+ return RC_OK;
+ default:
+ WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW));
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), *argvW);
+ return RC_INITERROR;
+ }
+ }
+ 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, flags);
+
+ /* 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));
+
+ /* Pause if necessary */
+ if (flags & OPT_PAUSE) {
+ DWORD count;
+ char pausestr[10];
+
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
+ ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
+ &count, NULL);
+ }
+
+ /* 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);
+ }
+
+ /* 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;
+
+}
+
+
+/* =========================================================================
+ 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 (GetFullPathName(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 = GetFileAttributes(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);
+
+ /* 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;
+
+ WINE_TRACE("Filename supplied\n");
+ lastDir = wcsrchr(suppliedsource, '\\');
+
+ 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];
+ GetCurrentDirectory (sizeof(curdir), curdir);
+ stem[0] = curdir[0];
+ stem[1] = curdir[1];
+ stem[2] = 0x00;
+ lstrcpyW(spec, suppliedsource);
+ }
+ }
+
+ 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] = "";
+ WCHAR fileChar[2];
+ WCHAR dirChar[2];
+
+ /* Read the F and D characters from the resource file */
+ wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
+ wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
+
+ while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), 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] == dirChar[0]) {
+ 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;
+ DWORD destAttribs, srcAttribs;
+ BOOL skipFile;
+
+ /* 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) {
+
+ skipFile = FALSE;
+
+ /* 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);
+ if (flags & OPT_SHORTNAME) {
+ lstrcatW(copyFrom, finddata->cAlternateFileName);
+ } else {
+ lstrcatW(copyFrom, finddata->cFileName);
+ }
+
+ lstrcpyW(copyTo, deststem);
+ if (*destspec == 0x00) {
+ if (flags & OPT_SHORTNAME) {
+ lstrcatW(copyTo, finddata->cAlternateFileName);
+ } else {
+ 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);
+
+ /* See if allowed to copy it */
+ srcAttribs = GetFileAttributesW(copyFrom);
+ WINE_TRACE("Source attribs: %d\n", srcAttribs);
+
+ if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
+ (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
+
+ if (!(flags & OPT_COPYHIDSYS)) {
+ skipFile = TRUE;
+ }
+ }
+
+ if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
+ (flags & OPT_ARCHIVEONLY)) {
+ skipFile = TRUE;
+ }
+
+ /* See if file exists */
+ destAttribs = GetFileAttributesW(copyTo);
+ WINE_TRACE("Dest attribs: %d\n", srcAttribs);
+
+ /* Check date ranges if a destination file already exists */
+ if (!skipFile && (flags & OPT_DATERANGE) &&
+ (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
+ WINE_TRACE("Skipping file as modified date too old\n");
+ skipFile = TRUE;
+ }
+
+ /* If just /D supplied, only overwrite if src newer than dest */
+ if (!skipFile && (flags & OPT_DATENEWER) &&
+ (destAttribs != INVALID_FILE_ATTRIBUTES)) {
+ HANDLE h = CreateFile(copyTo, GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (h != INVALID_HANDLE_VALUE) {
+ FILETIME writeTime;
+ GetFileTime(h, NULL, NULL, &writeTime);
+
+ if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
+ WINE_TRACE("Skipping file as dest newer or same date\n");
+ skipFile = TRUE;
+ }
+ CloseHandle(h);
+ }
+ }
+
+ /* See if exclude list provided. Note since filenames are case
+ insensitive, need to uppercase the filename before doing
+ strstr */
+ if (!skipFile && (flags & OPT_EXCLUDELIST)) {
+ EXCLUDELIST *pos = excludeList;
+ WCHAR copyFromUpper[MAX_PATH];
+
+ /* Uppercase source filename */
+ lstrcpyW(copyFromUpper, copyFrom);
+ CharUpperBuff(copyFromUpper, lstrlenW(copyFromUpper));
+
+ /* Loop through testing each exclude line */
+ while (pos) {
+ if (wcsstr(copyFromUpper, pos->name) != NULL) {
+ WINE_TRACE("Skipping file as matches exclude '%s'\n",
+ wine_dbgstr_w(pos->name));
+ skipFile = TRUE;
+ pos = NULL;
+ } else {
+ pos = pos->next;
+ }
+ }
+ }
+
+ /* Prompt each file if necessary */
+ if (!skipFile && (flags & OPT_SRCPROMPT)) {
+ DWORD count;
+ char answer[10];
+ BOOL answered = FALSE;
+ WCHAR yesChar[2];
+ WCHAR noChar[2];
+
+ /* Read the Y and N characters from the resource file */
+ wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
+ wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
+
+ while (!answered) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
+ ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
+ &count, NULL);
+
+ answered = TRUE;
+ if (toupper(answer[0]) == noChar[0])
+ skipFile = TRUE;
+ else if (toupper(answer[0]) != yesChar[0])
+ answered = FALSE;
+ }
+ }
+
+ if (!skipFile &&
+ destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
+ DWORD count;
+ char answer[10];
+ BOOL answered = FALSE;
+ WCHAR yesChar[2];
+ WCHAR allChar[2];
+ WCHAR noChar[2];
+
+ /* Read the A,Y and N characters from the resource file */
+ wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
+ wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
+ wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
+
+ while (!answered) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
+ ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
+ &count, NULL);
+
+ answered = TRUE;
+ if (toupper(answer[0]) == allChar[0])
+ flags |= OPT_NOPROMPT;
+ else if (toupper(answer[0]) == noChar[0])
+ skipFile = TRUE;
+ else if (toupper(answer[0]) != yesChar[0])
+ answered = FALSE;
+ }
+ }
+
+ /* See if it has to exist! */
+ if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
+ skipFile = TRUE;
+ }
+
+ /* Output a status message */
+ if (!skipFile) {
+ if (flags & OPT_QUIET) {
+ /* Skip message */
+ } else if (flags & OPT_FULL) {
+ const WCHAR infostr[] = {'%', 's', ' ', '-', '>', ' ',
+ '%', 's', '\n', 0};
+
+ XCOPY_wprintf(infostr, copyFrom, copyTo);
+ } else {
+ const WCHAR infostr[] = {'%', 's', '\n', 0};
+ XCOPY_wprintf(infostr, copyFrom);
+ }
+
+ /* If allowing overwriting of read only files, remove any
+ write protection */
+ if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
+ (flags & OPT_REPLACEREAD)) {
+ SetFileAttributes(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
+ }
+
+ copiedFile = TRUE;
+ if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
+ /* Skip copy */
+ } else if (CopyFile(copyFrom, copyTo, FALSE) == 0) {
+
+ DWORD error = GetLastError();
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
+ copyFrom, copyTo, error);
+ XCOPY_FailMessage(error);
+
+ if (flags & OPT_IGNOREERRORS) {
+ skipFile = TRUE;
+ } else {
+ return RC_WRITEERROR;
+ }
+ }
+
+ /* If /M supplied, remove the archive bit after successful copy */
+ if (!skipFile) {
+ if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
+ (flags & OPT_REMOVEARCH)) {
+ SetFileAttributes(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
+ }
+ 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;
+}
+
+/* =========================================================================
+ * 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) {
+
+ WCHAR *filenameStart = parms;
+
+ WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
+ excludeList = NULL;
+
+ while (*parms && *parms != ' ' && *parms != '/') {
+
+ /* If found '+' then process the file found so far */
+ if (*parms == '+') {
+ if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
+ return TRUE;
+ }
+ filenameStart = parms+1;
+ }
+ parms++;
+ }
+
+ if (filenameStart != parms) {
+ if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* =========================================================================
+ * 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) {
+
+ WCHAR endChar = *endOfName;
+ WCHAR buffer[MAXSTRING];
+ FILE *inFile = NULL;
+ const WCHAR readTextMode[] = {'r', 't', 0};
+
+ /* Null terminate the filename (temporarily updates the filename hence
+ parms not const) */
+ *endOfName = 0x00;
+
+ /* Open the file */
+ inFile = _wfopen(filename, readTextMode);
+ if (inFile == NULL) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
+ *endOfName = endChar;
+ return TRUE;
+ }
+
+ /* Process line by line */
+ while (fgetws(buffer, sizeof(buffer), inFile) != NULL) {
+ EXCLUDELIST *thisEntry;
+ int length = lstrlenW(buffer);
+
+ /* Strip CRLF */
+ buffer[length-1] = 0x00;
+
+ /* 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);
+ CharUpperBuff(thisEntry->name, length);
+ WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
+ }
+ }
+
+ /* See if EOF or error occurred */
+ if (!feof(inFile)) {
+ XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
+ *endOfName = endChar;
+ return TRUE;
+ }
+
+ /* Revert the input string to original form, and cleanup + return */
+ *endOfName = endChar;
+ fclose(inFile);
+ return FALSE;
+}
+
+/* =========================================================================
+ * 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};
+
+ if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg))) {
+ WINE_FIXME("LoadString failed with %d\n", GetLastError());
+ lstrcpyW(msg, failedMsg);
+ }
+ 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;
+
+ status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, err, 0,
+ (LPTSTR) &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);
+ }
+}
+
+/* =========================================================================
+ * 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, ...) {
+
+ static WCHAR *output_bufW = NULL;
+ static char *output_bufA = NULL;
+ static BOOL toConsole = TRUE;
+ static BOOL traceOutput = FALSE;
+#define MAX_WRITECONSOLE_SIZE 65535
+
+ va_list parms;
+ DWORD len, nOut;
+ DWORD res = 0;
+
+ /*
+ * Allocate buffer to use when writing to console
+ * Note: Not freed - memory will be allocated once and released when
+ * xcopy ends
+ */
+
+ 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;
+ }
+
+ /* Use wvsprintf to store output into unicode buffer */
+ va_start(parms, format);
+ len = vswprintf(output_bufW, format, parms);
+ va_end(parms);
+
+ /* 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);
+ }
+
+ /* 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;
+
+ toConsole = FALSE;
+
+ /*
+ * 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;
+ }
+
+ /* 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);
+ }
+
+ /* Trace whether screen or console */
+ if (!traceOutput) {
+ WINE_TRACE("Writing to console? (%d)\n", toConsole);
+ traceOutput = TRUE;
+ }
+ return nOut;
+}