12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160 |
- /*
- * 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:
- * Documented valid return codes are:
- * 0 - OK
- * 1 - No files found to copy (*1)
- * 2 - CTRL+C during copy
- * 4 - Initialization error, or invalid source specification
- * 5 - Disk write error
- *
- * (*1) Testing shows return code 1 is never returned
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <windows.h>
- #include <wine/debug.h>
- #include "xcopy.h"
- WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
- /* 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*/
- /* To minimize stack usage during recursion, some temporary variables
- made global */
- static WCHAR copyFrom[MAX_PATH];
- static WCHAR copyTo[MAX_PATH];
- /* =========================================================================
- * 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];
- if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
- WINE_FIXME("LoadString failed with %d\n", GetLastError());
- lstrcpyW(msg, L"Failed!");
- }
- return msg;
- }
- /* =========================================================================
- * 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 WINAPIV 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;
- /*
- * 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*sizeof(WCHAR));
- if (!output_bufW) {
- WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
- return 0;
- }
- va_start(parms, format);
- len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_bufW,
- MAX_WRITECONSOLE_SIZE/sizeof(*output_bufW), &parms);
- va_end(parms);
- if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
- WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
- return 0;
- }
- /* 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);
- }
- /* 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;
- 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;
- }
- /* =========================================================================
- * 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 = 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 {
- XCOPY_wprintf(L"%1\n", lpMsgBuf);
- LocalFree ((HLOCAL)lpMsgBuf);
- }
- }
- /* =========================================================================
- * 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)
- {
- 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 (!CreateDirectoryW(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 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;
- /* Null terminate the filename (temporarily updates the filename hence
- parms not const) */
- *endOfName = 0x00;
- /* Open the file */
- inFile = _wfopen(filename, L"rt");
- if (inFile == NULL) {
- XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
- *endOfName = endChar;
- return TRUE;
- }
- /* Process line by line */
- while (fgetws(buffer, ARRAY_SIZE(buffer), inFile) != NULL) {
- EXCLUDELIST *thisEntry;
- int length = lstrlenW(buffer);
- /* If more than CRLF */
- if (length > 1) {
- buffer[length-1] = 0; /* strip CRLF */
- 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));
- }
- }
- /* See if EOF or error occurred */
- if (!feof(inFile)) {
- XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
- *endOfName = endChar;
- fclose(inFile);
- return TRUE;
- }
- /* Revert the input string to original form, and cleanup + return */
- *endOfName = endChar;
- fclose(inFile);
- return FALSE;
- }
- /* =========================================================================
- * 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;
- }
- /* =========================================================================
- 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_DATAW *finddata;
- HANDLE h;
- BOOL findres = TRUE;
- WCHAR *inputpath, *outputpath;
- BOOL copiedFile = FALSE;
- DWORD destAttribs, srcAttribs;
- BOOL skipFile;
- int ret = 0;
- /* Allocate some working memory on heap to minimize footprint */
- finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
- 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 = FindFirstFileW(inputpath, finddata);
- while (h != INVALID_HANDLE_VALUE && findres) {
- skipFile = FALSE;
- /* Ignore . and .. */
- if (lstrcmpW(finddata->cFileName, L".")==0 ||
- lstrcmpW(finddata->cFileName, L"..")==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 = CreateFileW(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);
- CharUpperBuffW(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)) {
- if (flags & OPT_FULL)
- XCOPY_wprintf(L"%1 -> %2\n", copyFrom, copyTo);
- else
- XCOPY_wprintf(L"%1\n", copyFrom);
- }
- /* If allowing overwriting of read only files, remove any
- write protection */
- if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
- (flags & OPT_REPLACEREAD)) {
- SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
- }
- copiedFile = TRUE;
- if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
- /* Skip copy */
- } else if (CopyFileW(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 {
- ret = RC_WRITEERROR;
- goto cleanup;
- }
- } else {
- if (!skipFile) {
- /* If keeping attributes, update the destination attributes
- otherwise remove the read only attribute */
- if (flags & OPT_KEEPATTRS) {
- SetFileAttributesW(copyTo, srcAttribs | FILE_ATTRIBUTE_ARCHIVE);
- } else {
- SetFileAttributesW(copyTo,
- (GetFileAttributesW(copyTo) & ~FILE_ATTRIBUTE_READONLY));
- }
- /* If /M supplied, remove the archive bit after successful copy */
- if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
- (flags & OPT_REMOVEARCH)) {
- SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
- }
- filesCopied++;
- }
- }
- }
- }
- /* Find next file */
- findres = FindNextFileW(h, finddata);
- }
- FindClose(h);
- /* Search 2 - do subdirs */
- if (flags & OPT_RECURSIVE) {
- /* If /E is supplied, create the directory now */
- if ((flags & OPT_EMPTYDIR) &&
- !(flags & OPT_SIMULATE)) {
- XCOPY_CreateDirectory(deststem);
- }
- lstrcpyW(inputpath, srcstem);
- lstrcatW(inputpath, L"*");
- findres = TRUE;
- WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
- h = FindFirstFileW(inputpath, finddata);
- while (h != INVALID_HANDLE_VALUE && findres) {
- /* Only looking for dirs */
- if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
- (lstrcmpW(finddata->cFileName, L".") != 0) &&
- (lstrcmpW(finddata->cFileName, L"..") != 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, L"\\");
- lstrcpyW(outputpath, deststem);
- if (*destspec == 0x00) {
- lstrcatW(outputpath, finddata->cFileName);
- lstrcatW(outputpath, L"\\");
- }
- XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
- }
- /* Find next one */
- findres = FindNextFileW(h, finddata);
- }
- FindClose(h);
- }
- 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 inline BOOL is_whitespace(WCHAR c)
- {
- return c == ' ' || c == '\t';
- }
- static WCHAR *skip_whitespace(WCHAR *p)
- {
- for (; *p && is_whitespace(*p); p++);
- return p;
- }
- static inline BOOL is_digit(WCHAR c)
- {
- return c >= '0' && c <= '9';
- }
- /* 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 = FALSE;
- 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)
- {
- 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 */
- int skip=0;
- WCHAR *rest;
- while (word[0]) {
- rest = NULL;
- 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 'K': flags |= OPT_KEEPATTRS; 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, L"EXCLUDE:", -1) == CSTR_EQUAL) {
- if (XCOPY_ProcessExcludeList(&word[9])) {
- XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
- goto out;
- } else {
- flags |= OPT_EXCLUDELIST;
- /* Do not support concatenated switches onto exclude lists yet */
- rest = end;
- }
- } else {
- flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
- }
- break;
- /* D can be /D or /D: */
- case 'D': if (word[2]==':' && is_digit(word[3])) {
- SYSTEMTIME st;
- WCHAR *pos = &word[3];
- BOOL isError = FALSE;
- memset(&st, 0x00, sizeof(st));
- /* Microsoft xcopy's usage message implies that the date
- * format depends on the locale, but that is false.
- * It is hardcoded to month-day-year.
- */
- st.wMonth = _wtol(pos);
- while (*pos && is_digit(*pos)) pos++;
- if (*pos++ != '-') isError = TRUE;
- if (!isError) {
- st.wDay = _wtol(pos);
- while (*pos && is_digit(*pos)) pos++;
- if (*pos++ != '-') isError = TRUE;
- }
- if (!isError) {
- st.wYear = _wtol(pos);
- while (*pos && is_digit(*pos)) pos++;
- if (st.wYear < 100) st.wYear+=2000;
- }
- /* Handle switches straight after the supplied date */
- rest = pos;
- 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,
- ARRAY_SIZE(datestring));
- GetTimeFormatW(0, TIME_NOSECONDS, &st,
- NULL, timestring, ARRAY_SIZE(timestring));
- 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;
- case '-': if (toupper(word[2])=='Y') {
- flags &= ~OPT_NOPROMPT;
- rest = &word[3]; /* Skip over 3 characters */
- }
- break;
- case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
- rc = RC_HELP;
- goto out;
- case 'V':
- WINE_FIXME("ignoring /V\n");
- break;
- default:
- WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
- XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
- goto out;
- }
- /* Unless overridden above, skip over the '/' and the first character */
- if (rest == NULL) rest = &word[2];
- /* By now, rest should point either to the null after the
- switch, or the beginning of the next switch if there
- was no whitespace between them */
- if (!skip && *rest && *rest != '/') {
- WINE_FIXME("Unexpected characters found and ignored '%s'\n", wine_dbgstr_w(rest));
- skip=1;
- } else {
- word = rest;
- }
- }
- }
- word = next;
- }
- /* Default the destination if not supplied */
- if (supplieddestination[0] == 0x00)
- lstrcpyW(supplieddestination, L".");
- *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, L"\\");
- lstrcpyW(spec, L"*");
- /* 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(ARRAY_SIZE(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 (GetFullPathNameW(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 = 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;
- } 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, L"\\");
- } 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;
- }
- /* =========================================================================
- main - Main entrypoint for the xcopy command
- Processes the args, and drives the actual copying
- ========================================================================= */
- int __cdecl 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 */
- /* Preinitialize flags based on COPYCMD */
- if (GetEnvironmentVariableW(L"COPYCMD", copyCmd, MAXSTRING)) {
- if (wcsstr(copyCmd, L"/Y") != NULL || wcsstr(copyCmd, L"/y") != 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;
- /*
- * Parse the command line
- */
- 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;
- /* Extract required information from destination specification */
- rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
- destinationspec, sourcespec, flags);
- if (rc != RC_OK) return rc;
- /* 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);
- }
- return rc;
- }
|