[1/2] xcopy: Reorder the functions to avoid forward declarations.
Francois Gouget
fgouget at free.fr
Tue Oct 18 09:43:04 CDT 2011
---
programs/xcopy/xcopy.c | 1335 ++++++++++++++++++++++++------------------------
1 files changed, 660 insertions(+), 675 deletions(-)
diff --git a/programs/xcopy/xcopy.c b/programs/xcopy/xcopy.c
index 4c1154f..f684a55 100644
--- a/programs/xcopy/xcopy.c
+++ b/programs/xcopy/xcopy.c
@@ -48,22 +48,6 @@
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,260 @@ 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 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 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;
+ 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 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[] = {'%', 's', '\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;
-
- 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));
-
- 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;
+ WCHAR *slash;
+ DWORD last_error = GetLastError();
+ if (last_error == ERROR_ALREADY_EXISTS)
+ 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;
- }
+ if (last_error != ERROR_PATH_NOT_FOUND)
+ {
+ ret = FALSE;
+ break;
}
- word = next;
- }
-
- /* Default the destination if not supplied */
- if (supplieddestination[0] == 0x00)
- lstrcpyW(supplieddestination, wchr_dot);
-
- *pflags = flags;
- rc = RC_OK;
-
- out:
- free(cmdline);
- 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 (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);
+ if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
+ {
+ ret = FALSE;
+ break;
}
- 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];
- GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
- stem[0] = curdir[0];
- stem[1] = curdir[1];
- stem[2] = 0x00;
- lstrcpyW(spec, suppliedsource);
+ len = slash - new_path;
+ new_path[len] = 0;
+ if (!XCOPY_CreateDirectory(new_path))
+ {
+ ret = FALSE;
+ break;
}
+ new_path[len] = '\\';
}
-
- return RC_OK;
+ HeapFree(GetProcessHeap(),0,new_path);
+ return ret;
}
/* =========================================================================
- 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;
+ * 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) {
- /*
- * 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};
+
+ /* 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;
}
- /* Destination is either a directory or a file */
- attribs = GetFileAttributesW(actualdestination);
+ /* Process line by line */
+ while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
+ EXCLUDELIST *thisEntry;
+ int length = lstrlenW(buffer);
- if (attribs == INVALID_FILE_ATTRIBUTES) {
+ /* Strip CRLF */
+ buffer[length-1] = 0x00;
- /* 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] == '\\')) {
+ /* 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));
+ }
+ }
- 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;
}
/* =========================================================================
@@ -842,300 +594,533 @@ static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
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)) {
+ /* 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));
+
+ /* 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;
+ }
- WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
+ /* Parse the arg : Day */
+ if (!isError) {
+ st.wYear = _wtol(pos);
+ while (*pos && isdigit(*pos)) pos++;
+ if (st.wYear < 100) st.wYear+=2000;
+ }
- /* Make up recursive information */
- lstrcpyW(inputpath, srcstem);
- lstrcatW(inputpath, finddata->cFileName);
- lstrcatW(inputpath, wchr_slash);
+ if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
+ SYSTEMTIME st;
+ WCHAR datestring[32], timestring[32];
- lstrcpyW(outputpath, deststem);
- if (*destspec == 0x00) {
- lstrcatW(outputpath, finddata->cFileName);
+ flags |= OPT_DATERANGE;
- /* If /E is supplied, create the directory now */
- if ((flags & OPT_EMPTYDIR) &&
- !(flags & OPT_SIMULATE))
- XCOPY_CreateDirectory(outputpath);
+ /* 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));
- lstrcatW(outputpath, wchr_slash);
- }
+ 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;
- XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
+ 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;
}
-
- /* Find next one */
- findres = FindNextFileW(h, finddata);
}
+ 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) {
-
- 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;
+ 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;
- /* Open the file */
- inFile = _wfopen(filename, readTextMode);
- if (inFile == NULL) {
- XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
- *endOfName = endChar;
- return TRUE;
+ /*
+ * 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;
}
- /* Process line by line */
- while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
- EXCLUDELIST *thisEntry;
- int length = lstrlenW(buffer);
+ /* Destination is either a directory or a file */
+ attribs = GetFileAttributesW(actualdestination);
- /* Strip CRLF */
- buffer[length-1] = 0x00;
+ if (attribs == INVALID_FILE_ATTRIBUTES) {
- /* 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));
- }
- }
+ /* 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] == '\\')) {
- /* See if EOF or error occurred */
- if (!feof(inFile)) {
- XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
- *endOfName = endChar;
- return TRUE;
- }
+ isDir = TRUE;
- /* Revert the input string to original form, and cleanup + return */
- *endOfName = endChar;
- fclose(inFile);
- return FALSE;
-}
+ } else {
+ DWORD count;
+ char answer[10] = "";
+ WCHAR fileChar[2];
+ WCHAR dirChar[2];
-/* =========================================================================
- * 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};
+ /* Read the F and D characters from the resource file */
+ wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
+ wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
- if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
- WINE_FIXME("LoadString failed with %d\n", GetLastError());
- lstrcpyW(msg, failedMsg);
+ 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);
}
- 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 whenever we think it's 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 it's 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;
+
}
--
1.7.6.3
More information about the wine-patches
mailing list