Home | Info | Community | Development | myReactOS | Contact Us
ReactOS Development > Doxygenxcopy.cGo 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
1.6.3
|