/* * 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 #include #include #include "xcopy.h" WINE_DEFAULT_DEBUG_CHANNEL(xcopy); /* Prototypes */ static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem, WCHAR *spec); static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec, WCHAR *srcspec, DWORD flags); static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec, WCHAR *deststem, WCHAR *destspec, DWORD flags); static BOOL XCOPY_CreateDirectory(const WCHAR* path); 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); /* 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 main (int argc, char *argv[]) { int rc = 0; WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */ WCHAR supplieddestination[MAX_PATH] = {0}; WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */ WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */ WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */ WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */ WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */ DWORD flags = 0; /* Option flags */ LPWSTR *argvW = NULL; 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 */ /* overwrite the command line */ argvW = CommandLineToArgvW( GetCommandLineW(), &argc ); /* Confirm at least one parameter */ if (argc < 2) { 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; } } /* 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 { 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; 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 '?': wprintf(XCOPY_LoadMessage(STRING_HELP)); return RC_OK; default: WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW)); 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); /* 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]; 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) { wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied); } else if (!(flags & OPT_NOCOPY)) { 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) { WCHAR actualsource[MAX_PATH]; WCHAR *starPos; WCHAR *questPos; /* * Validate the source, expanding to full path ensuring it exists */ if (GetFullPathName(suppliedsource, MAX_PATH, actualsource, NULL) == 0) { WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError()); return RC_INITERROR; } /* * Work out the stem of the source */ /* If no wildcard were supplied then the source is either a single file or a directory - in which case thats the stem of the search, otherwise split off the wildcards and use the higher level as the stem */ lstrcpyW(stem, actualsource); starPos = wcschr(stem, '*'); questPos = wcschr(stem, '?'); if (starPos || questPos) { WCHAR *lastDir; if (starPos) *starPos = 0x00; if (questPos) *questPos = 0x00; lastDir = wcsrchr(stem, '\\'); if (lastDir) *(lastDir+1) = 0x00; else { WINE_FIXME("Unexpected syntax error in source parameter\n"); return RC_INITERROR; } lstrcpyW(spec, actualsource + (lastDir - stem)+1); } else { DWORD attribs = GetFileAttributes(actualsource); if (attribs == INVALID_FILE_ATTRIBUTES) { XCOPY_FailMessage(GetLastError()); return RC_INITERROR; /* Directory: */ } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) { lstrcatW(stem, wchr_slash); lstrcpyW(spec, wchr_star); /* File: */ } else { WCHAR drive[MAX_PATH]; WCHAR dir[MAX_PATH]; WCHAR fname[MAX_PATH]; WCHAR ext[MAX_PATH]; _wsplitpath(actualsource, drive, dir, fname, ext); lstrcpyW(stem, drive); lstrcatW(stem, dir); lstrcpyW(spec, fname); lstrcatW(spec, ext); } } return RC_OK; } /* ========================================================================= XCOPY_ProcessDestParm - Takes the supplied destination parameter, and converts it into a stem ========================================================================= */ static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec, WCHAR *srcspec, DWORD flags) { WCHAR actualdestination[MAX_PATH]; DWORD attribs; BOOL isDir = FALSE; /* * Validate the source, expanding to full path ensuring it exists */ if (GetFullPathName(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) { WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError()); return RC_INITERROR; } /* Destination is either a directory or a file */ attribs = GetFileAttributes(actualdestination); if (attribs == INVALID_FILE_ATTRIBUTES) { /* If /I supplied and wildcard copy, assume directory */ if (flags & OPT_ASSUMEDIR && (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) { isDir = TRUE; } else { DWORD count; char answer[10] = ""; 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]) { 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) { 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) { 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) { printf("%S -> %S\n", copyFrom, copyTo); } else { printf("%S\n", 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(); 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) { 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; 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)) { 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[2048]; 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 { printf("%S\n", lpMsgBuf); LocalFree ((HLOCAL)lpMsgBuf); } }