diff --git a/.gitignore b/.gitignore index 2e6f73f9e30..4003e3e3f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -874,6 +874,7 @@ programs/winver/winver programs/wordpad/rsrc.res programs/wordpad/toolbar.bmp programs/wordpad/wordpad +programs/xcopy/xcopy server/wineserver server/wineserver.man tools/bin2res diff --git a/Makefile.in b/Makefile.in index f488e7d6556..265acf3151e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -467,6 +467,7 @@ ALL_MAKEFILES = \ programs/winhelp/Makefile \ programs/winver/Makefile \ programs/wordpad/Makefile \ + programs/xcopy/Makefile \ server/Makefile \ tools/Makefile \ tools/widl/Makefile \ @@ -803,6 +804,7 @@ programs/winevdm/Makefile: programs/winevdm/Makefile.in programs/Makeprog.rules programs/winhelp/Makefile: programs/winhelp/Makefile.in programs/Makeprog.rules programs/winver/Makefile: programs/winver/Makefile.in programs/Makeprog.rules programs/wordpad/Makefile: programs/wordpad/Makefile.in programs/Makeprog.rules +programs/xcopy/Makefile: programs/xcopy/Makefile.in programs/Makeprog.rules server/Makefile: server/Makefile.in Make.rules tools/Makefile: tools/Makefile.in Make.rules tools/widl/Makefile: tools/widl/Makefile.in Make.rules diff --git a/configure b/configure index d99a70ab61b..5b56377fd17 100755 --- a/configure +++ b/configure @@ -20859,6 +20859,8 @@ ac_config_files="$ac_config_files programs/winver/Makefile" ac_config_files="$ac_config_files programs/wordpad/Makefile" +ac_config_files="$ac_config_files programs/xcopy/Makefile" + ac_config_files="$ac_config_files server/Makefile" ac_config_files="$ac_config_files tools/Makefile" @@ -21760,6 +21762,7 @@ do "programs/winhelp/Makefile") CONFIG_FILES="$CONFIG_FILES programs/winhelp/Makefile" ;; "programs/winver/Makefile") CONFIG_FILES="$CONFIG_FILES programs/winver/Makefile" ;; "programs/wordpad/Makefile") CONFIG_FILES="$CONFIG_FILES programs/wordpad/Makefile" ;; + "programs/xcopy/Makefile") CONFIG_FILES="$CONFIG_FILES programs/xcopy/Makefile" ;; "server/Makefile") CONFIG_FILES="$CONFIG_FILES server/Makefile" ;; "tools/Makefile") CONFIG_FILES="$CONFIG_FILES tools/Makefile" ;; "tools/widl/Makefile") CONFIG_FILES="$CONFIG_FILES tools/widl/Makefile" ;; diff --git a/configure.ac b/configure.ac index 95b3b8dc1ab..abd544c6df9 100644 --- a/configure.ac +++ b/configure.ac @@ -1827,6 +1827,7 @@ AC_CONFIG_FILES([programs/winevdm/Makefile]) AC_CONFIG_FILES([programs/winhelp/Makefile]) AC_CONFIG_FILES([programs/winver/Makefile]) AC_CONFIG_FILES([programs/wordpad/Makefile]) +AC_CONFIG_FILES([programs/xcopy/Makefile]) AC_CONFIG_FILES([server/Makefile]) AC_CONFIG_FILES([tools/Makefile]) AC_CONFIG_FILES([tools/widl/Makefile]) diff --git a/programs/Makefile.in b/programs/Makefile.in index 8b61cdeceb4..1dbf5d4c813 100644 --- a/programs/Makefile.in +++ b/programs/Makefile.in @@ -41,7 +41,8 @@ SUBDIRS = \ winevdm \ winhelp \ winver \ - wordpad + wordpad \ + xcopy # Sub-directories to run make install into INSTALLSUBDIRS = \ @@ -77,7 +78,8 @@ INSTALLSUBDIRS = \ winevdm \ winhelp \ winver \ - wordpad + wordpad \ + xcopy # Programs to install in bin directory INSTALLPROGS = \ diff --git a/programs/xcopy/Makefile.in b/programs/xcopy/Makefile.in new file mode 100644 index 00000000000..d54597ccb72 --- /dev/null +++ b/programs/xcopy/Makefile.in @@ -0,0 +1,17 @@ +TOPSRCDIR = @top_srcdir@ +TOPOBJDIR = ../.. +SRCDIR = @srcdir@ +VPATH = @srcdir@ +MODULE = xcopy.exe +APPMODE = -mconsole +IMPORTS = shell32 user32 msvcrt kernel32 +EXTRADEFS = -DUNICODE +EXTRAINCL = -I$(TOPSRCDIR)/include/msvcrt +MODCFLAGS = @BUILTINFLAG@ + +C_SRCS = \ + xcopy.c + +@MAKE_PROG_RULES@ + +@DEPENDENCIES@ # everything below this line is overwritten by make depend diff --git a/programs/xcopy/xcopy.c b/programs/xcopy/xcopy.c new file mode 100644 index 00000000000..718923e64ed --- /dev/null +++ b/programs/xcopy/xcopy.c @@ -0,0 +1,514 @@ +/* + * 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 +#include +#include + +/* 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 + +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 */ + DWORD flags = 0; /* Option flags */ + LPWSTR *argvW = NULL; + /* + * 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; + } + + /* 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 */ + 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; + 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)); + + /* 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 { + 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; + + /* 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) { + + /* 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); + lstrcatW(copyFrom, finddata->cFileName); + + lstrcpyW(copyTo, deststem); + if (*destspec == 0x00) { + 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); + + /* Output a status message */ + if (flags & OPT_QUIET) { + /* Skip message */ + } else if (flags & OPT_FULL) { + printf("%S -> %S\n", copyFrom, copyTo); + } else { + printf("%S\n", copyFrom); + } + + copiedFile = TRUE; + if (flags & OPT_SIMULATE) { + /* Skip copy as just simulating */ + } else if (CopyFile(copyFrom, copyTo, TRUE) == 0) { + printf("Copying of '%S' to '%S' failed with r/c %d\n", + copyFrom, copyTo, GetLastError()); + } + 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; +}