| /* |
| * 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 |
| */ |
| |
| /* |
| * Notes: |
| * Apparently, valid return codes are: |
| * 0 - OK |
| * 1 - No files found to copy |
| * 2 - CTRL+C during copy |
| * 4 - Initialization error, or invalid source specification |
| * 5 - Disk write error |
| */ |
| |
| |
| #include <stdio.h> |
| #include <windows.h> |
| #include <wine/debug.h> |
| |
| /* Local #defines */ |
| #define RC_OK 0 |
| #define RC_NOFILES 1 |
| #define RC_CTRLC 2 |
| #define RC_INITERROR 4 |
| #define RC_WRITEERROR 5 |
| |
| #define OPT_ASSUMEDIR 0x00000001 |
| #define OPT_RECURSIVE 0x00000002 |
| #define OPT_EMPTYDIR 0x00000004 |
| #define OPT_QUIET 0x00000008 |
| #define OPT_FULL 0x00000010 |
| #define OPT_SIMULATE 0x00000020 |
| #define OPT_PAUSE 0x00000040 |
| #define OPT_NOCOPY 0x00000080 |
| #define OPT_NOPROMPT 0x00000100 |
| #define OPT_SHORTNAME 0x00000200 |
| #define OPT_MUSTEXIST 0x00000400 |
| #define OPT_REPLACEREAD 0x00000800 |
| #define OPT_COPYHIDSYS 0x00001000 |
| #define OPT_IGNOREERRORS 0x00002000 |
| #define OPT_SRCPROMPT 0x00004000 |
| #define OPT_ARCHIVEONLY 0x00008000 |
| #define OPT_REMOVEARCH 0x00010000 |
| |
| #define MAXSTRING 8192 |
| |
| 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); |
| |
| /* Global variables */ |
| static ULONG filesCopied = 0; /* Number of files copied */ |
| 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}; |
| |
| /* |
| * Parse the command line |
| */ |
| |
| /* overwrite the command line */ |
| argvW = CommandLineToArgvW( GetCommandLineW(), &argc ); |
| |
| /* Confirm at least one parameter */ |
| if (argc < 2) { |
| printf("Invalid number of parameters - Use xcopy /? for help\n"); |
| 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 { |
| printf("Invalid number of parameters - Use xcopy /? for help\n"); |
| 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 'E': flags |= OPT_EMPTYDIR; 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; |
| case '-': if (toupper(argvW[0][2])=='Y') |
| flags &= ~OPT_NOPROMPT; break; |
| default: |
| WINE_FIXME("Unhandled parameter '%s'\n", wine_dbgstr_w(*argvW)); |
| } |
| } |
| 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]; |
| |
| printf("Press <enter> to begin copying\n"); |
| ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr), |
| &count, NULL); |
| } |
| |
| /* Now do the hard work... */ |
| rc = XCOPY_DoCopy(sourcestem, sourcespec, |
| destinationstem, destinationspec, |
| flags); |
| |
| /* Finished - print trailer and exit */ |
| if (flags & OPT_SIMULATE) { |
| printf("%d file(s) would be copied\n", filesCopied); |
| } else if (!(flags & OPT_NOCOPY)) { |
| printf("%d file(s) copied\n", 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) { |
| LPWSTR lpMsgBuf; |
| DWORD lastError = GetLastError(); |
| int status; |
| status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, lastError, 0, (LPWSTR) &lpMsgBuf, 0, NULL); |
| printf("%S\n", lpMsgBuf); |
| 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] = ""; |
| |
| while (answer[0] != 'F' && answer[0] != 'D') { |
| printf("Is %S a filename or directory\n" |
| "on the target?\n" |
| "(F - File, D - Directory)\n", 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] == 'D') { |
| 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; |
| LPVOID lpMsgBuf; |
| DWORD error_code; |
| int status; |
| |
| /* 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; |
| } |
| |
| /* Prompt each file if necessary */ |
| if (!skipFile && (flags & OPT_SRCPROMPT)) { |
| DWORD count; |
| char answer[10]; |
| BOOL answered = FALSE; |
| |
| while (!answered) { |
| printf("%S? (Yes|No)\n", copyFrom); |
| ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), |
| &count, NULL); |
| |
| answered = TRUE; |
| if (toupper(answer[0]) == 'N') |
| skipFile = TRUE; |
| else if (toupper(answer[0]) != 'Y') |
| answered = FALSE; |
| } |
| } |
| |
| /* See if file exists */ |
| destAttribs = GetFileAttributesW(copyTo); |
| WINE_TRACE("Dest attribs: %d\n", srcAttribs); |
| |
| if (!skipFile && |
| destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) { |
| DWORD count; |
| char answer[10]; |
| BOOL answered = FALSE; |
| |
| while (!answered) { |
| printf("Overwrite %S? (Yes|No|All)\n", copyTo); |
| ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), |
| &count, NULL); |
| |
| answered = TRUE; |
| if (toupper(answer[0]) == 'A') |
| flags |= OPT_NOPROMPT; |
| else if (toupper(answer[0]) == 'N') |
| skipFile = TRUE; |
| else if (toupper(answer[0]) != 'Y') |
| 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) { |
| printf("Copying of '%S' to '%S' failed with r/c %d\n", |
| copyFrom, copyTo, GetLastError()); |
| |
| error_code = GetLastError (); |
| status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, error_code, 0, |
| (LPTSTR) &lpMsgBuf, 0, NULL); |
| if (!status) { |
| WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n", |
| error_code, GetLastError()); |
| } else { |
| printf("%S\n", lpMsgBuf); |
| LocalFree ((HLOCAL)lpMsgBuf); |
| } |
| 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; |
| } |