Home | Info | Community | Development | myReactOS | Contact Us

  1. Home
  2. Community
  3. Development
  4. myReactOS

  1. Main Page
  2. Alphabetical List
  3. Data Structures
  4. Directories
  5. File List
  6. Data Fields
  7. Globals
  8. Related Pages

ReactOS Development > Doxygen

xcopy.c

Go to the documentation of this file.
00001 /*
00002  * XCOPY - Wine-compatible xcopy program
00003  *
00004  * Copyright (C) 2007 J. Edmeades
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Lesser General Public
00008  * License as published by the Free Software Foundation; either
00009  * version 2.1 of the License, or (at your option) any later version.
00010  *
00011  * This library is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  * Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public
00017  * License along with this library; if not, write to the Free Software
00018  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
00019  */
00020 
00021 /*
00022  * FIXME:
00023  * This should now support all options listed in the xcopy help from
00024  * windows XP except:
00025  *  /Z - Copy from network drives in restartable mode
00026  *  /X - Copy file audit settings (sets /O)
00027  *  /O - Copy file ownership + ACL info
00028  *  /G - Copy encrypted files to unencrypted destination
00029  *  /V - Verifies files
00030  */
00031 
00032 /*
00033  * Notes:
00034  * Apparently, valid return codes are:
00035  *   0 - OK
00036  *   1 - No files found to copy
00037  *   2 - CTRL+C during copy
00038  *   4 - Initialization error, or invalid source specification
00039  *   5 - Disk write error
00040  */
00041 
00042 
00043 #include <stdio.h>
00044 #include <windows.h>
00045 #include <wine/debug.h>
00046 #include <wine/unicode.h>
00047 #include "xcopy.h"
00048 
00049 WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
00050 
00051 /* Prototypes */
00052 static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
00053                                   WCHAR *supplieddestination, DWORD *flags);
00054 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
00055                                    WCHAR *spec, DWORD flags);
00056 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem,
00057                                  WCHAR *spec, WCHAR *srcspec, DWORD flags);
00058 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
00059                         WCHAR *deststem, WCHAR *destspec,
00060                         DWORD flags);
00061 static BOOL XCOPY_CreateDirectory(const WCHAR* path);
00062 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms);
00063 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName);
00064 static WCHAR *XCOPY_LoadMessage(UINT id);
00065 static void XCOPY_FailMessage(DWORD err);
00066 static int XCOPY_wprintf(const WCHAR *format, ...);
00067 
00068 /* Typedefs */
00069 typedef struct _EXCLUDELIST
00070 {
00071   struct _EXCLUDELIST *next;
00072   WCHAR               *name;
00073 } EXCLUDELIST;
00074 
00075 
00076 /* Global variables */
00077 static ULONG filesCopied           = 0;              /* Number of files copied  */
00078 static EXCLUDELIST *excludeList    = NULL;           /* Excluded strings list   */
00079 static FILETIME dateRange;                           /* Date range to copy after*/
00080 static const WCHAR wchr_slash[]   = {'\\', 0};
00081 static const WCHAR wchr_star[]    = {'*', 0};
00082 static const WCHAR wchr_dot[]     = {'.', 0};
00083 static const WCHAR wchr_dotdot[]  = {'.', '.', 0};
00084 
00085 /* Constants (Mostly for widechars) */
00086 
00087 
00088 /* To minimize stack usage during recursion, some temporary variables
00089    made global                                                        */
00090 static WCHAR copyFrom[MAX_PATH];
00091 static WCHAR copyTo[MAX_PATH];
00092 
00093 
00094 /* =========================================================================
00095    main - Main entrypoint for the xcopy command
00096 
00097      Processes the args, and drives the actual copying
00098    ========================================================================= */
00099 int wmain (int argc, WCHAR *argvW[])
00100 {
00101     int     rc = 0;
00102     WCHAR   suppliedsource[MAX_PATH] = {0};   /* As supplied on the cmd line */
00103     WCHAR   supplieddestination[MAX_PATH] = {0};
00104     WCHAR   sourcestem[MAX_PATH] = {0};       /* Stem of source          */
00105     WCHAR   sourcespec[MAX_PATH] = {0};       /* Filespec of source      */
00106     WCHAR   destinationstem[MAX_PATH] = {0};  /* Stem of destination     */
00107     WCHAR   destinationspec[MAX_PATH] = {0};  /* Filespec of destination */
00108     WCHAR   copyCmd[MAXSTRING];               /* COPYCMD env var         */
00109     DWORD   flags = 0;                        /* Option flags            */
00110     const WCHAR PROMPTSTR1[]  = {'/', 'Y', 0};
00111     const WCHAR PROMPTSTR2[]  = {'/', 'y', 0};
00112     const WCHAR COPYCMD[]  = {'C', 'O', 'P', 'Y', 'C', 'M', 'D', 0};
00113 
00114     /* Preinitialize flags based on COPYCMD */
00115     if (GetEnvironmentVariableW(COPYCMD, copyCmd, MAXSTRING)) {
00116         if (wcsstr(copyCmd, PROMPTSTR1) != NULL ||
00117             wcsstr(copyCmd, PROMPTSTR2) != NULL) {
00118             flags |= OPT_NOPROMPT;
00119         }
00120     }
00121 
00122     /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
00123        wine, but on windows these can be normal files. At least one installer
00124        uses files such as .packlist and (validly) expects them to be copied.
00125        Under wine, if we do not copy hidden files by default then they get
00126        lose                                                                   */
00127     flags |= OPT_COPYHIDSYS;
00128 
00129     /*
00130      * Parse the command line
00131      */
00132     if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
00133                                      &flags)) != RC_OK) {
00134         if (rc == RC_HELP)
00135             return RC_OK;
00136         else
00137             return rc;
00138     }
00139 
00140     /* Trace out the supplied information */
00141     WINE_TRACE("Supplied parameters:\n");
00142     WINE_TRACE("Source      : '%s'\n", wine_dbgstr_w(suppliedsource));
00143     WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
00144 
00145     /* Extract required information from source specification */
00146     rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
00147     if (rc != RC_OK) return rc;
00148 
00149     /* Extract required information from destination specification */
00150     rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
00151                                destinationspec, sourcespec, flags);
00152     if (rc != RC_OK) return rc;
00153 
00154     /* Trace out the resulting information */
00155     WINE_TRACE("Resolved parameters:\n");
00156     WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
00157     WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
00158     WINE_TRACE("Dest   Stem : '%s'\n", wine_dbgstr_w(destinationstem));
00159     WINE_TRACE("Dest   Spec : '%s'\n", wine_dbgstr_w(destinationspec));
00160 
00161     /* Pause if necessary */
00162     if (flags & OPT_PAUSE) {
00163         DWORD count;
00164         char pausestr[10];
00165 
00166         XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
00167         ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
00168                   &count, NULL);
00169     }
00170 
00171     /* Now do the hard work... */
00172     rc = XCOPY_DoCopy(sourcestem, sourcespec,
00173                 destinationstem, destinationspec,
00174                 flags);
00175 
00176     /* Clear up exclude list allocated memory */
00177     while (excludeList) {
00178         EXCLUDELIST *pos = excludeList;
00179         excludeList = excludeList -> next;
00180         HeapFree(GetProcessHeap(), 0, pos->name);
00181         HeapFree(GetProcessHeap(), 0, pos);
00182     }
00183 
00184     /* Finished - print trailer and exit */
00185     if (flags & OPT_SIMULATE) {
00186         XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
00187     } else if (!(flags & OPT_NOCOPY)) {
00188         XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
00189     }
00190     if (rc == RC_OK && filesCopied == 0) rc = RC_NOFILES;
00191     return rc;
00192 
00193 }
00194 
00195 /* =========================================================================
00196    XCOPY_ParseCommandLine - Parses the command line
00197    ========================================================================= */
00198 static BOOL is_whitespace(WCHAR c)
00199 {
00200     return c == ' ' || c == '\t';
00201 }
00202 
00203 static WCHAR *skip_whitespace(WCHAR *p)
00204 {
00205     for (; *p && is_whitespace(*p); p++);
00206     return p;
00207 }
00208 
00209 /* Windows XCOPY uses a simplified command line parsing algorithm
00210    that lacks the escaped-quote logic of build_argv(), because
00211    literal double quotes are illegal in any of its arguments.
00212    Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
00213 static int find_end_of_word(const WCHAR *word, WCHAR **end)
00214 {
00215     BOOL in_quotes = 0;
00216     const WCHAR *ptr = word;
00217     for (;;) {
00218         for (; *ptr != '\0' && *ptr != '"' &&
00219                  (in_quotes || !is_whitespace(*ptr)); ptr++);
00220         if (*ptr == '"') {
00221             in_quotes = !in_quotes;
00222             ptr++;
00223         }
00224         /* Odd number of double quotes is illegal for XCOPY */
00225         if (in_quotes && *ptr == '\0')
00226             return RC_INITERROR;
00227         if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
00228             break;
00229     }
00230     *end = (WCHAR*)ptr;
00231     return RC_OK;
00232 }
00233 
00234 /* Remove all double quotes from a word */
00235 static void strip_quotes(WCHAR *word, WCHAR **end)
00236 {
00237     WCHAR *rp, *wp;
00238     for (rp = word, wp = word; *rp != '\0'; rp++) {
00239         if (*rp == '"')
00240             continue;
00241         if (wp < rp)
00242             *wp = *rp;
00243         wp++;
00244     }
00245     *wp = '\0';
00246     *end = wp;
00247 }
00248 
00249 static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
00250                                   WCHAR *supplieddestination, DWORD *pflags)
00251 {
00252     const WCHAR EXCLUDE[]  = {'E', 'X', 'C', 'L', 'U', 'D', 'E', ':', 0};
00253     DWORD flags = *pflags;
00254     WCHAR *cmdline, *word, *end, *next;
00255     int rc = RC_INITERROR;
00256 
00257     cmdline = _wcsdup(GetCommandLineW());
00258     if (cmdline == NULL)
00259         return rc;
00260 
00261     /* Skip first arg, which is the program name */
00262     if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
00263         goto out;
00264     word = skip_whitespace(word);
00265 
00266     while (*word)
00267     {
00268         WCHAR first;
00269         if ((rc = find_end_of_word(word, &end)) != RC_OK)
00270             goto out;
00271 
00272         next = skip_whitespace(end);
00273         first = word[0];
00274         *end = '\0';
00275         strip_quotes(word, &end);
00276         WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
00277 
00278         /* First non-switch parameter is source, second is destination */
00279         if (first != '/') {
00280             if (suppliedsource[0] == 0x00) {
00281                 lstrcpyW(suppliedsource, word);
00282             } else if (supplieddestination[0] == 0x00) {
00283                 lstrcpyW(supplieddestination, word);
00284             } else {
00285                 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
00286                 goto out;
00287             }
00288         } else {
00289             /* Process all the switch options
00290                  Note: Windows docs say /P prompts when dest is created
00291                        but tests show it is done for each src file
00292                        regardless of the destination                   */
00293             switch (toupper(word[1])) {
00294             case 'I': flags |= OPT_ASSUMEDIR;     break;
00295             case 'S': flags |= OPT_RECURSIVE;     break;
00296             case 'Q': flags |= OPT_QUIET;         break;
00297             case 'F': flags |= OPT_FULL;          break;
00298             case 'L': flags |= OPT_SIMULATE;      break;
00299             case 'W': flags |= OPT_PAUSE;         break;
00300             case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
00301             case 'Y': flags |= OPT_NOPROMPT;      break;
00302             case 'N': flags |= OPT_SHORTNAME;     break;
00303             case 'U': flags |= OPT_MUSTEXIST;     break;
00304             case 'R': flags |= OPT_REPLACEREAD;   break;
00305             case 'H': flags |= OPT_COPYHIDSYS;    break;
00306             case 'C': flags |= OPT_IGNOREERRORS;  break;
00307             case 'P': flags |= OPT_SRCPROMPT;     break;
00308             case 'A': flags |= OPT_ARCHIVEONLY;   break;
00309             case 'M': flags |= OPT_ARCHIVEONLY |
00310                                OPT_REMOVEARCH;    break;
00311 
00312             /* E can be /E or /EXCLUDE */
00313             case 'E': if (CompareStringW(LOCALE_USER_DEFAULT,
00314                                          NORM_IGNORECASE | SORT_STRINGSORT,
00315                                          &word[1], 8,
00316                                          EXCLUDE, -1) == 2) {
00317                         if (XCOPY_ProcessExcludeList(&word[9])) {
00318                           XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
00319                           goto out;
00320                         } else flags |= OPT_EXCLUDELIST;
00321                       } else flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
00322                       break;
00323 
00324             /* D can be /D or /D: */
00325             case 'D': if (word[2]==':' && isdigit(word[3])) {
00326                           SYSTEMTIME st;
00327                           WCHAR     *pos = &word[3];
00328                           BOOL       isError = FALSE;
00329                           memset(&st, 0x00, sizeof(st));
00330 
00331                           /* Parse the arg : Month */
00332                           st.wMonth = _wtol(pos);
00333                           while (*pos && isdigit(*pos)) pos++;
00334                           if (*pos++ != '-') isError = TRUE;
00335 
00336                           /* Parse the arg : Day */
00337                           if (!isError) {
00338                               st.wDay = _wtol(pos);
00339                               while (*pos && isdigit(*pos)) pos++;
00340                               if (*pos++ != '-') isError = TRUE;
00341                           }
00342 
00343                           /* Parse the arg : Day */
00344                           if (!isError) {
00345                               st.wYear = _wtol(pos);
00346                               while (*pos && isdigit(*pos)) pos++;
00347                               if (st.wYear < 100) st.wYear+=2000;
00348                           }
00349 
00350                           if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
00351                               SYSTEMTIME st;
00352                               WCHAR datestring[32], timestring[32];
00353 
00354                               flags |= OPT_DATERANGE;
00355 
00356                               /* Debug info: */
00357                               FileTimeToSystemTime (&dateRange, &st);
00358                               GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
00359                                              sizeof(datestring)/sizeof(WCHAR));
00360                               GetTimeFormatW(0, TIME_NOSECONDS, &st,
00361                                              NULL, timestring, sizeof(timestring)/sizeof(WCHAR));
00362 
00363                               WINE_TRACE("Date being used is: %s %s\n",
00364                                          wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
00365                           } else {
00366                               XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
00367                               goto out;
00368                           }
00369                       } else {
00370                           flags |= OPT_DATENEWER;
00371                       }
00372                       break;
00373 
00374             case '-': if (toupper(word[2])=='Y')
00375                           flags &= ~OPT_NOPROMPT; break;
00376             case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
00377                       rc = RC_HELP;
00378                       goto out;
00379             default:
00380                 WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
00381                 XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
00382                 goto out;
00383             }
00384         }
00385         word = next;
00386     }
00387 
00388     /* Default the destination if not supplied */
00389     if (supplieddestination[0] == 0x00)
00390         lstrcpyW(supplieddestination, wchr_dot);
00391 
00392     *pflags = flags;
00393     rc = RC_OK;
00394 
00395  out:
00396     free(cmdline);
00397     return rc;
00398 }
00399 
00400 
00401 /* =========================================================================
00402    XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
00403      converts it into a stem and a filespec
00404    ========================================================================= */
00405 static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
00406                                    WCHAR *spec, DWORD flags)
00407 {
00408     WCHAR             actualsource[MAX_PATH];
00409     WCHAR            *starPos;
00410     WCHAR            *questPos;
00411     DWORD             attribs;
00412 
00413     /*
00414      * Validate the source, expanding to full path ensuring it exists
00415      */
00416     if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
00417         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
00418         return RC_INITERROR;
00419     }
00420 
00421     /* If full names required, convert to using the full path */
00422     if (flags & OPT_FULL) {
00423         lstrcpyW(suppliedsource, actualsource);
00424     }
00425 
00426     /*
00427      * Work out the stem of the source
00428      */
00429 
00430     /* If a directory is supplied, use that as-is (either fully or
00431           partially qualified)
00432        If a filename is supplied + a directory or drive path, use that
00433           as-is
00434        Otherwise
00435           If no directory or path specified, add eg. C:
00436           stem is Drive/Directory is bit up to last \ (or first :)
00437           spec is bit after that                                         */
00438 
00439     starPos = wcschr(suppliedsource, '*');
00440     questPos = wcschr(suppliedsource, '?');
00441     if (starPos || questPos) {
00442         attribs = 0x00;  /* Ensures skips invalid or directory check below */
00443     } else {
00444         attribs = GetFileAttributesW(actualsource);
00445     }
00446 
00447     if (attribs == INVALID_FILE_ATTRIBUTES) {
00448         XCOPY_FailMessage(GetLastError());
00449         return RC_INITERROR;
00450 
00451     /* Directory:
00452          stem should be exactly as supplied plus a '\', unless it was
00453           eg. C: in which case no slash required */
00454     } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
00455         WCHAR lastChar;
00456 
00457         WINE_TRACE("Directory supplied\n");
00458         lstrcpyW(stem, suppliedsource);
00459         lastChar = stem[lstrlenW(stem)-1];
00460         if (lastChar != '\\' && lastChar != ':') {
00461             lstrcatW(stem, wchr_slash);
00462         }
00463         lstrcpyW(spec, wchr_star);
00464 
00465     /* File or wildcard search:
00466          stem should be:
00467            Up to and including last slash if directory path supplied
00468            If c:filename supplied, just the c:
00469            Otherwise stem should be the current drive letter + ':' */
00470     } else {
00471         WCHAR *lastDir;
00472 
00473         WINE_TRACE("Filename supplied\n");
00474         lastDir   = wcsrchr(suppliedsource, '\\');
00475 
00476         if (lastDir) {
00477             lstrcpyW(stem, suppliedsource);
00478             stem[(lastDir-suppliedsource) + 1] = 0x00;
00479             lstrcpyW(spec, (lastDir+1));
00480         } else if (suppliedsource[1] == ':') {
00481             lstrcpyW(stem, suppliedsource);
00482             stem[2] = 0x00;
00483             lstrcpyW(spec, suppliedsource+2);
00484         } else {
00485             WCHAR curdir[MAXSTRING];
00486             GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
00487             stem[0] = curdir[0];
00488             stem[1] = curdir[1];
00489             stem[2] = 0x00;
00490             lstrcpyW(spec, suppliedsource);
00491         }
00492     }
00493 
00494     return RC_OK;
00495 }
00496 
00497 /* =========================================================================
00498    XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
00499      converts it into a stem
00500    ========================================================================= */
00501 static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
00502                                  WCHAR *srcspec, DWORD flags)
00503 {
00504     WCHAR  actualdestination[MAX_PATH];
00505     DWORD attribs;
00506     BOOL isDir = FALSE;
00507 
00508     /*
00509      * Validate the source, expanding to full path ensuring it exists
00510      */
00511     if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
00512         WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
00513         return RC_INITERROR;
00514     }
00515 
00516     /* Destination is either a directory or a file */
00517     attribs = GetFileAttributesW(actualdestination);
00518 
00519     if (attribs == INVALID_FILE_ATTRIBUTES) {
00520 
00521         /* If /I supplied and wildcard copy, assume directory */
00522         /* Also if destination ends with backslash */
00523         if ((flags & OPT_ASSUMEDIR &&
00524             (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) ||
00525             (supplieddestination[lstrlenW(supplieddestination)-1] == '\\')) {
00526 
00527             isDir = TRUE;
00528 
00529         } else {
00530             DWORD count;
00531             char  answer[10] = "";
00532             WCHAR fileChar[2];
00533             WCHAR dirChar[2];
00534 
00535             /* Read the F and D characters from the resource file */
00536             wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
00537             wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
00538 
00539             while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
00540                 XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
00541 
00542                 ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
00543                 WINE_TRACE("User answer %c\n", answer[0]);
00544 
00545                 answer[0] = toupper(answer[0]);
00546             }
00547 
00548             if (answer[0] == dirChar[0]) {
00549                 isDir = TRUE;
00550             } else {
00551                 isDir = FALSE;
00552             }
00553         }
00554     } else {
00555         isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
00556     }
00557 
00558     if (isDir) {
00559         lstrcpyW(stem, actualdestination);
00560         *spec = 0x00;
00561 
00562         /* Ensure ends with a '\' */
00563         if (stem[lstrlenW(stem)-1] != '\\') {
00564             lstrcatW(stem, wchr_slash);
00565         }
00566 
00567     } else {
00568         WCHAR drive[MAX_PATH];
00569         WCHAR dir[MAX_PATH];
00570         WCHAR fname[MAX_PATH];
00571         WCHAR ext[MAX_PATH];
00572         _wsplitpath(actualdestination, drive, dir, fname, ext);
00573         lstrcpyW(stem, drive);
00574         lstrcatW(stem, dir);
00575         lstrcpyW(spec, fname);
00576         lstrcatW(spec, ext);
00577     }
00578     return RC_OK;
00579 }
00580 
00581 /* =========================================================================
00582    XCOPY_DoCopy - Recursive function to copy files based on input parms
00583      of a stem and a spec
00584 
00585       This works by using FindFirstFile supplying the source stem and spec.
00586       If results are found, any non-directory ones are processed
00587       Then, if /S or /E is supplied, another search is made just for
00588       directories, and this function is called again for that directory
00589 
00590    ========================================================================= */
00591 static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
00592                         WCHAR *deststem, WCHAR *destspec,
00593                         DWORD flags)
00594 {
00595     WIN32_FIND_DATAW *finddata;
00596     HANDLE          h;
00597     BOOL            findres = TRUE;
00598     WCHAR           *inputpath, *outputpath;
00599     BOOL            copiedFile = FALSE;
00600     DWORD           destAttribs, srcAttribs;
00601     BOOL            skipFile;
00602     int             ret = 0;
00603 
00604     /* Allocate some working memory on heap to minimize footprint */
00605     finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
00606     inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
00607     outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
00608 
00609     /* Build the search info into a single parm */
00610     lstrcpyW(inputpath, srcstem);
00611     lstrcatW(inputpath, srcspec);
00612 
00613     /* Search 1 - Look for matching files */
00614     h = FindFirstFileW(inputpath, finddata);
00615     while (h != INVALID_HANDLE_VALUE && findres) {
00616 
00617         skipFile = FALSE;
00618 
00619         /* Ignore . and .. */
00620         if (lstrcmpW(finddata->cFileName, wchr_dot)==0 ||
00621             lstrcmpW(finddata->cFileName, wchr_dotdot)==0 ||
00622             finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
00623 
00624             WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
00625         } else {
00626 
00627             /* Get the filename information */
00628             lstrcpyW(copyFrom, srcstem);
00629             if (flags & OPT_SHORTNAME) {
00630               lstrcatW(copyFrom, finddata->cAlternateFileName);
00631             } else {
00632               lstrcatW(copyFrom, finddata->cFileName);
00633             }
00634 
00635             lstrcpyW(copyTo, deststem);
00636             if (*destspec == 0x00) {
00637                 if (flags & OPT_SHORTNAME) {
00638                     lstrcatW(copyTo, finddata->cAlternateFileName);
00639                 } else {
00640                     lstrcatW(copyTo, finddata->cFileName);
00641                 }
00642             } else {
00643                 lstrcatW(copyTo, destspec);
00644             }
00645 
00646             /* Do the copy */
00647             WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
00648                                                       wine_dbgstr_w(copyTo));
00649             if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
00650 
00651             /* See if allowed to copy it */
00652             srcAttribs = GetFileAttributesW(copyFrom);
00653             WINE_TRACE("Source attribs: %d\n", srcAttribs);
00654 
00655             if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
00656                 (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
00657 
00658                 if (!(flags & OPT_COPYHIDSYS)) {
00659                     skipFile = TRUE;
00660                 }
00661             }
00662 
00663             if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
00664                 (flags & OPT_ARCHIVEONLY)) {
00665                 skipFile = TRUE;
00666             }
00667 
00668             /* See if file exists */
00669             destAttribs = GetFileAttributesW(copyTo);
00670             WINE_TRACE("Dest attribs: %d\n", srcAttribs);
00671 
00672             /* Check date ranges if a destination file already exists */
00673             if (!skipFile && (flags & OPT_DATERANGE) &&
00674                 (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
00675                 WINE_TRACE("Skipping file as modified date too old\n");
00676                 skipFile = TRUE;
00677             }
00678 
00679             /* If just /D supplied, only overwrite if src newer than dest */
00680             if (!skipFile && (flags & OPT_DATENEWER) &&
00681                (destAttribs != INVALID_FILE_ATTRIBUTES)) {
00682                 HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
00683                                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
00684                                       NULL);
00685                 if (h != INVALID_HANDLE_VALUE) {
00686                     FILETIME writeTime;
00687                     GetFileTime(h, NULL, NULL, &writeTime);
00688 
00689                     if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
00690                         WINE_TRACE("Skipping file as dest newer or same date\n");
00691                         skipFile = TRUE;
00692                     }
00693                     CloseHandle(h);
00694                 }
00695             }
00696 
00697             /* See if exclude list provided. Note since filenames are case
00698                insensitive, need to uppercase the filename before doing
00699                strstr                                                     */
00700             if (!skipFile && (flags & OPT_EXCLUDELIST)) {
00701                 EXCLUDELIST *pos = excludeList;
00702                 WCHAR copyFromUpper[MAX_PATH];
00703 
00704                 /* Uppercase source filename */
00705                 lstrcpyW(copyFromUpper, copyFrom);
00706                 CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
00707 
00708                 /* Loop through testing each exclude line */
00709                 while (pos) {
00710                     if (wcsstr(copyFromUpper, pos->name) != NULL) {
00711                         WINE_TRACE("Skipping file as matches exclude '%s'\n",
00712                                    wine_dbgstr_w(pos->name));
00713                         skipFile = TRUE;
00714                         pos = NULL;
00715                     } else {
00716                         pos = pos->next;
00717                     }
00718                 }
00719             }
00720 
00721             /* Prompt each file if necessary */
00722             if (!skipFile && (flags & OPT_SRCPROMPT)) {
00723                 DWORD count;
00724                 char  answer[10];
00725                 BOOL  answered = FALSE;
00726                 WCHAR yesChar[2];
00727                 WCHAR noChar[2];
00728 
00729                 /* Read the Y and N characters from the resource file */
00730                 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
00731                 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
00732 
00733                 while (!answered) {
00734                     XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
00735                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
00736                               &count, NULL);
00737 
00738                     answered = TRUE;
00739                     if (toupper(answer[0]) == noChar[0])
00740                         skipFile = TRUE;
00741                     else if (toupper(answer[0]) != yesChar[0])
00742                         answered = FALSE;
00743                 }
00744             }
00745 
00746             if (!skipFile &&
00747                 destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
00748                 DWORD count;
00749                 char  answer[10];
00750                 BOOL  answered = FALSE;
00751                 WCHAR yesChar[2];
00752                 WCHAR allChar[2];
00753                 WCHAR noChar[2];
00754 
00755                 /* Read the A,Y and N characters from the resource file */
00756                 wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
00757                 wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
00758                 wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
00759 
00760                 while (!answered) {
00761                     XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
00762                     ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
00763                               &count, NULL);
00764 
00765                     answered = TRUE;
00766                     if (toupper(answer[0]) == allChar[0])
00767                         flags |= OPT_NOPROMPT;
00768                     else if (toupper(answer[0]) == noChar[0])
00769                         skipFile = TRUE;
00770                     else if (toupper(answer[0]) != yesChar[0])
00771                         answered = FALSE;
00772                 }
00773             }
00774 
00775             /* See if it has to exist! */
00776             if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
00777                 skipFile = TRUE;
00778             }
00779 
00780             /* Output a status message */
00781             if (!skipFile) {
00782                 if (flags & OPT_QUIET) {
00783                     /* Skip message */
00784                 } else if (flags & OPT_FULL) {
00785                     const WCHAR infostr[]   = {'%', 's', ' ', '-', '>', ' ',
00786                                                '%', 's', '\n', 0};
00787 
00788                     XCOPY_wprintf(infostr, copyFrom, copyTo);
00789                 } else {
00790                     const WCHAR infostr[] = {'%', 's', '\n', 0};
00791                     XCOPY_wprintf(infostr, copyFrom);
00792                 }
00793 
00794                 /* If allowing overwriting of read only files, remove any
00795                    write protection                                       */
00796                 if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
00797                     (flags & OPT_REPLACEREAD)) {
00798                     SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
00799                 }
00800 
00801                 copiedFile = TRUE;
00802                 if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
00803                     /* Skip copy */
00804                 } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
00805 
00806                     DWORD error = GetLastError();
00807                     XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
00808                            copyFrom, copyTo, error);
00809                     XCOPY_FailMessage(error);
00810 
00811                     if (flags & OPT_IGNOREERRORS) {
00812                         skipFile = TRUE;
00813                     } else {
00814                         ret = RC_WRITEERROR;
00815                         goto cleanup;
00816                     }
00817                 }
00818 
00819                 /* If /M supplied, remove the archive bit after successful copy */
00820                 if (!skipFile) {
00821                     if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
00822                         (flags & OPT_REMOVEARCH)) {
00823                         SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
00824                     }
00825                     filesCopied++;
00826                 }
00827             }
00828         }
00829 
00830         /* Find next file */
00831         findres = FindNextFileW(h, finddata);
00832     }
00833     FindClose(h);
00834 
00835     /* Search 2 - do subdirs */
00836     if (flags & OPT_RECURSIVE) {
00837         lstrcpyW(inputpath, srcstem);
00838         lstrcatW(inputpath, wchr_star);
00839         findres = TRUE;
00840         WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
00841 
00842         h = FindFirstFileW(inputpath, finddata);
00843         while (h != INVALID_HANDLE_VALUE && findres) {
00844 
00845             /* Only looking for dirs */
00846             if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
00847                 (lstrcmpW(finddata->cFileName, wchr_dot) != 0) &&
00848                 (lstrcmpW(finddata->cFileName, wchr_dotdot) != 0)) {
00849 
00850                 WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
00851 
00852                 /* Make up recursive information */
00853                 lstrcpyW(inputpath, srcstem);
00854                 lstrcatW(inputpath, finddata->cFileName);
00855                 lstrcatW(inputpath, wchr_slash);
00856 
00857                 lstrcpyW(outputpath, deststem);
00858                 if (*destspec == 0x00) {
00859                     lstrcatW(outputpath, finddata->cFileName);
00860 
00861                     /* If /E is supplied, create the directory now */
00862                     if ((flags & OPT_EMPTYDIR) &&
00863                         !(flags & OPT_SIMULATE))
00864                         XCOPY_CreateDirectory(outputpath);
00865 
00866                     lstrcatW(outputpath, wchr_slash);
00867                 }
00868 
00869                 XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
00870             }
00871 
00872             /* Find next one */
00873             findres = FindNextFileW(h, finddata);
00874         }
00875     }
00876 
00877 cleanup:
00878 
00879     /* free up memory */
00880     HeapFree(GetProcessHeap(), 0, finddata);
00881     HeapFree(GetProcessHeap(), 0, inputpath);
00882     HeapFree(GetProcessHeap(), 0, outputpath);
00883 
00884     return ret;
00885 }
00886 
00887 /* =========================================================================
00888  * Routine copied from cmd.exe md command -
00889  * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
00890  * dir2 if they do not already exist.
00891  * ========================================================================= */
00892 static BOOL XCOPY_CreateDirectory(const WCHAR* path)
00893 {
00894     int len;
00895     WCHAR *new_path;
00896     BOOL ret = TRUE;
00897 
00898     new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
00899     lstrcpyW(new_path,path);
00900 
00901     while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
00902         new_path[len - 1] = 0;
00903 
00904     while (!CreateDirectoryW(new_path,NULL))
00905     {
00906         WCHAR *slash;
00907         DWORD last_error = GetLastError();
00908         if (last_error == ERROR_ALREADY_EXISTS)
00909             break;
00910 
00911         if (last_error != ERROR_PATH_NOT_FOUND)
00912         {
00913             ret = FALSE;
00914             break;
00915         }
00916 
00917         if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
00918         {
00919             ret = FALSE;
00920             break;
00921         }
00922 
00923         len = slash - new_path;
00924         new_path[len] = 0;
00925         if (!XCOPY_CreateDirectory(new_path))
00926         {
00927             ret = FALSE;
00928             break;
00929         }
00930         new_path[len] = '\\';
00931     }
00932     HeapFree(GetProcessHeap(),0,new_path);
00933     return ret;
00934 }
00935 
00936 /* =========================================================================
00937  * Process the /EXCLUDE: file list, building up a list of substrings to
00938  * avoid copying
00939  * Returns TRUE on any failure
00940  * ========================================================================= */
00941 static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
00942 
00943     WCHAR *filenameStart = parms;
00944 
00945     WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
00946     excludeList = NULL;
00947 
00948     while (*parms && *parms != ' ' && *parms != '/') {
00949 
00950         /* If found '+' then process the file found so far */
00951         if (*parms == '+') {
00952             if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
00953                 return TRUE;
00954             }
00955             filenameStart = parms+1;
00956         }
00957         parms++;
00958     }
00959 
00960     if (filenameStart != parms) {
00961         if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
00962             return TRUE;
00963         }
00964     }
00965 
00966     return FALSE;
00967 }
00968 
00969 /* =========================================================================
00970  * Process a single file from the /EXCLUDE: file list, building up a list
00971  * of substrings to avoid copying
00972  * Returns TRUE on any failure
00973  * ========================================================================= */
00974 static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
00975 
00976     WCHAR   endChar = *endOfName;
00977     WCHAR   buffer[MAXSTRING];
00978     FILE   *inFile  = NULL;
00979     const WCHAR readTextMode[]  = {'r', 't', 0};
00980 
00981     /* Null terminate the filename (temporarily updates the filename hence
00982          parms not const)                                                 */
00983     *endOfName = 0x00;
00984 
00985     /* Open the file */
00986     inFile = _wfopen(filename, readTextMode);
00987     if (inFile == NULL) {
00988         XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
00989         *endOfName = endChar;
00990         return TRUE;
00991     }
00992 
00993     /* Process line by line */
00994     while (fgetws(buffer, sizeof(buffer)/sizeof(WCHAR), inFile) != NULL) {
00995         EXCLUDELIST *thisEntry;
00996         int length = lstrlenW(buffer);
00997 
00998         /* Strip CRLF */
00999         buffer[length-1] = 0x00;
01000 
01001         /* If more than CRLF */
01002         if (length > 1) {
01003           thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
01004           thisEntry->next = excludeList;
01005           excludeList = thisEntry;
01006           thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
01007                                       (length * sizeof(WCHAR))+1);
01008           lstrcpyW(thisEntry->name, buffer);
01009           CharUpperBuffW(thisEntry->name, length);
01010           WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
01011         }
01012     }
01013 
01014     /* See if EOF or error occurred */
01015     if (!feof(inFile)) {
01016         XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
01017         *endOfName = endChar;
01018         return TRUE;
01019     }
01020 
01021     /* Revert the input string to original form, and cleanup + return */
01022     *endOfName = endChar;
01023     fclose(inFile);
01024     return FALSE;
01025 }
01026 
01027 /* =========================================================================
01028  * Load a string from the resource file, handling any error
01029  * Returns string retrieved from resource file
01030  * ========================================================================= */
01031 static WCHAR *XCOPY_LoadMessage(UINT id) {
01032     static WCHAR msg[MAXSTRING];
01033     const WCHAR failedMsg[]  = {'F', 'a', 'i', 'l', 'e', 'd', '!', 0};
01034 
01035     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
01036        WINE_FIXME("LoadString failed with %d\n", GetLastError());
01037        lstrcpyW(msg, failedMsg);
01038     }
01039     return msg;
01040 }
01041 
01042 /* =========================================================================
01043  * Load a string for a system error and writes it to the screen
01044  * Returns string retrieved from resource file
01045  * ========================================================================= */
01046 static void XCOPY_FailMessage(DWORD err) {
01047     LPWSTR lpMsgBuf;
01048     int status;
01049 
01050     status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
01051                             FORMAT_MESSAGE_FROM_SYSTEM,
01052                             NULL, err, 0,
01053                             (LPWSTR) &lpMsgBuf, 0, NULL);
01054     if (!status) {
01055       WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
01056                  err, GetLastError());
01057     } else {
01058       const WCHAR infostr[] = {'%', 's', '\n', 0};
01059       XCOPY_wprintf(infostr, lpMsgBuf);
01060       LocalFree ((HLOCAL)lpMsgBuf);
01061     }
01062 }
01063 
01064 /* =========================================================================
01065  * Output a formatted unicode string. Ideally this will go to the console
01066  *  and hence required WriteConsoleW to output it, however if file i/o is
01067  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
01068  * ========================================================================= */
01069 int XCOPY_wprintf(const WCHAR *format, ...) {
01070 
01071     static WCHAR *output_bufW = NULL;
01072     static char  *output_bufA = NULL;
01073     static BOOL  toConsole    = TRUE;
01074     static BOOL  traceOutput  = FALSE;
01075 #define MAX_WRITECONSOLE_SIZE 65535
01076 
01077     va_list parms;
01078     DWORD   nOut;
01079     int len;
01080     DWORD   res = 0;
01081 
01082     /*
01083      * Allocate buffer to use when writing to console
01084      * Note: Not freed - memory will be allocated once and released when
01085      *         xcopy ends
01086      */
01087 
01088     if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
01089                                               MAX_WRITECONSOLE_SIZE);
01090     if (!output_bufW) {
01091       WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
01092       return 0;
01093     }
01094 
01095     va_start(parms, format);
01096     len = vsnprintfW(output_bufW, MAX_WRITECONSOLE_SIZE/sizeof(WCHAR), format, parms);
01097     va_end(parms);
01098     if (len < 0) {
01099       WINE_FIXME("String too long.\n");
01100       return 0;
01101     }
01102 
01103     /* Try to write as unicode all the time we think its a console */
01104     if (toConsole) {
01105       res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
01106                           output_bufW, len, &nOut, NULL);
01107     }
01108 
01109     /* If writing to console has failed (ever) we assume its file
01110        i/o so convert to OEM codepage and output                  */
01111     if (!res) {
01112       BOOL usedDefaultChar = FALSE;
01113       DWORD convertedChars;
01114 
01115       toConsole = FALSE;
01116 
01117       /*
01118        * Allocate buffer to use when writing to file. Not freed, as above
01119        */
01120       if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
01121                                                 MAX_WRITECONSOLE_SIZE);
01122       if (!output_bufA) {
01123         WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
01124         return 0;
01125       }
01126 
01127       /* Convert to OEM, then output */
01128       convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
01129                           len, output_bufA, MAX_WRITECONSOLE_SIZE,
01130                           "?", &usedDefaultChar);
01131       WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
01132                 &nOut, FALSE);
01133     }
01134 
01135     /* Trace whether screen or console */
01136     if (!traceOutput) {
01137       WINE_TRACE("Writing to console? (%d)\n", toConsole);
01138       traceOutput = TRUE;
01139     }
01140     return nOut;
01141 }

Generated on Thu Feb 9 04:39:04 2012 for ReactOS by doxygen 1.6.3

ReactOS is a registered trademark or a trademark of ReactOS Foundation in the United States and other countries.