xcopy.c 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. /*
  2. * XCOPY - Wine-compatible xcopy program
  3. *
  4. * Copyright (C) 2007 J. Edmeades
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this library; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  19. */
  20. /*
  21. * FIXME:
  22. * This should now support all options listed in the xcopy help from
  23. * windows XP except:
  24. * /Z - Copy from network drives in restartable mode
  25. * /X - Copy file audit settings (sets /O)
  26. * /O - Copy file ownership + ACL info
  27. * /G - Copy encrypted files to unencrypted destination
  28. * /V - Verifies files
  29. */
  30. /*
  31. * Notes:
  32. * Documented valid return codes are:
  33. * 0 - OK
  34. * 1 - No files found to copy (*1)
  35. * 2 - CTRL+C during copy
  36. * 4 - Initialization error, or invalid source specification
  37. * 5 - Disk write error
  38. *
  39. * (*1) Testing shows return code 1 is never returned
  40. */
  41. #include <stdio.h>
  42. #include <stdlib.h>
  43. #include <windows.h>
  44. #include <wine/debug.h>
  45. #include "xcopy.h"
  46. WINE_DEFAULT_DEBUG_CHANNEL(xcopy);
  47. /* Typedefs */
  48. typedef struct _EXCLUDELIST
  49. {
  50. struct _EXCLUDELIST *next;
  51. WCHAR *name;
  52. } EXCLUDELIST;
  53. /* Global variables */
  54. static ULONG filesCopied = 0; /* Number of files copied */
  55. static EXCLUDELIST *excludeList = NULL; /* Excluded strings list */
  56. static FILETIME dateRange; /* Date range to copy after*/
  57. /* To minimize stack usage during recursion, some temporary variables
  58. made global */
  59. static WCHAR copyFrom[MAX_PATH];
  60. static WCHAR copyTo[MAX_PATH];
  61. /* =========================================================================
  62. * Load a string from the resource file, handling any error
  63. * Returns string retrieved from resource file
  64. * ========================================================================= */
  65. static WCHAR *XCOPY_LoadMessage(UINT id) {
  66. static WCHAR msg[MAXSTRING];
  67. if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
  68. WINE_FIXME("LoadString failed with %d\n", GetLastError());
  69. lstrcpyW(msg, L"Failed!");
  70. }
  71. return msg;
  72. }
  73. /* =========================================================================
  74. * Output a formatted unicode string. Ideally this will go to the console
  75. * and hence required WriteConsoleW to output it, however if file i/o is
  76. * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
  77. * ========================================================================= */
  78. static int WINAPIV XCOPY_wprintf(const WCHAR *format, ...) {
  79. static WCHAR *output_bufW = NULL;
  80. static char *output_bufA = NULL;
  81. static BOOL toConsole = TRUE;
  82. static BOOL traceOutput = FALSE;
  83. #define MAX_WRITECONSOLE_SIZE 65535
  84. va_list parms;
  85. DWORD nOut;
  86. int len;
  87. DWORD res = 0;
  88. /*
  89. * Allocate buffer to use when writing to console
  90. * Note: Not freed - memory will be allocated once and released when
  91. * xcopy ends
  92. */
  93. if (!output_bufW) output_bufW = HeapAlloc(GetProcessHeap(), 0,
  94. MAX_WRITECONSOLE_SIZE*sizeof(WCHAR));
  95. if (!output_bufW) {
  96. WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
  97. return 0;
  98. }
  99. va_start(parms, format);
  100. len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_bufW,
  101. MAX_WRITECONSOLE_SIZE/sizeof(*output_bufW), &parms);
  102. va_end(parms);
  103. if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
  104. WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  105. return 0;
  106. }
  107. /* Try to write as unicode whenever we think it's a console */
  108. if (toConsole) {
  109. res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
  110. output_bufW, len, &nOut, NULL);
  111. }
  112. /* If writing to console has failed (ever) we assume it's file
  113. i/o so convert to OEM codepage and output */
  114. if (!res) {
  115. BOOL usedDefaultChar = FALSE;
  116. DWORD convertedChars;
  117. toConsole = FALSE;
  118. /*
  119. * Allocate buffer to use when writing to file. Not freed, as above
  120. */
  121. if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
  122. MAX_WRITECONSOLE_SIZE);
  123. if (!output_bufA) {
  124. WINE_FIXME("Out of memory - could not allocate 2 x 64K buffers\n");
  125. return 0;
  126. }
  127. /* Convert to OEM, then output */
  128. convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, output_bufW,
  129. len, output_bufA, MAX_WRITECONSOLE_SIZE,
  130. "?", &usedDefaultChar);
  131. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
  132. &nOut, FALSE);
  133. }
  134. /* Trace whether screen or console */
  135. if (!traceOutput) {
  136. WINE_TRACE("Writing to console? (%d)\n", toConsole);
  137. traceOutput = TRUE;
  138. }
  139. return nOut;
  140. }
  141. /* =========================================================================
  142. * Load a string for a system error and writes it to the screen
  143. * Returns string retrieved from resource file
  144. * ========================================================================= */
  145. static void XCOPY_FailMessage(DWORD err) {
  146. LPWSTR lpMsgBuf;
  147. int status;
  148. status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  149. FORMAT_MESSAGE_FROM_SYSTEM,
  150. NULL, err, 0,
  151. (LPWSTR) &lpMsgBuf, 0, NULL);
  152. if (!status) {
  153. WINE_FIXME("FIXME: Cannot display message for error %d, status %d\n",
  154. err, GetLastError());
  155. } else {
  156. XCOPY_wprintf(L"%1\n", lpMsgBuf);
  157. LocalFree ((HLOCAL)lpMsgBuf);
  158. }
  159. }
  160. /* =========================================================================
  161. * Routine copied from cmd.exe md command -
  162. * This works recursively. so creating dir1\dir2\dir3 will create dir1 and
  163. * dir2 if they do not already exist.
  164. * ========================================================================= */
  165. static BOOL XCOPY_CreateDirectory(const WCHAR* path)
  166. {
  167. int len;
  168. WCHAR *new_path;
  169. BOOL ret = TRUE;
  170. new_path = HeapAlloc(GetProcessHeap(),0, sizeof(WCHAR) * (lstrlenW(path)+1));
  171. lstrcpyW(new_path,path);
  172. while ((len = lstrlenW(new_path)) && new_path[len - 1] == '\\')
  173. new_path[len - 1] = 0;
  174. while (!CreateDirectoryW(new_path,NULL))
  175. {
  176. WCHAR *slash;
  177. DWORD last_error = GetLastError();
  178. if (last_error == ERROR_ALREADY_EXISTS)
  179. break;
  180. if (last_error != ERROR_PATH_NOT_FOUND)
  181. {
  182. ret = FALSE;
  183. break;
  184. }
  185. if (!(slash = wcsrchr(new_path,'\\')) && ! (slash = wcsrchr(new_path,'/')))
  186. {
  187. ret = FALSE;
  188. break;
  189. }
  190. len = slash - new_path;
  191. new_path[len] = 0;
  192. if (!XCOPY_CreateDirectory(new_path))
  193. {
  194. ret = FALSE;
  195. break;
  196. }
  197. new_path[len] = '\\';
  198. }
  199. HeapFree(GetProcessHeap(),0,new_path);
  200. return ret;
  201. }
  202. /* =========================================================================
  203. * Process a single file from the /EXCLUDE: file list, building up a list
  204. * of substrings to avoid copying
  205. * Returns TRUE on any failure
  206. * ========================================================================= */
  207. static BOOL XCOPY_ProcessExcludeFile(WCHAR* filename, WCHAR* endOfName) {
  208. WCHAR endChar = *endOfName;
  209. WCHAR buffer[MAXSTRING];
  210. FILE *inFile = NULL;
  211. /* Null terminate the filename (temporarily updates the filename hence
  212. parms not const) */
  213. *endOfName = 0x00;
  214. /* Open the file */
  215. inFile = _wfopen(filename, L"rt");
  216. if (inFile == NULL) {
  217. XCOPY_wprintf(XCOPY_LoadMessage(STRING_OPENFAIL), filename);
  218. *endOfName = endChar;
  219. return TRUE;
  220. }
  221. /* Process line by line */
  222. while (fgetws(buffer, ARRAY_SIZE(buffer), inFile) != NULL) {
  223. EXCLUDELIST *thisEntry;
  224. int length = lstrlenW(buffer);
  225. /* If more than CRLF */
  226. if (length > 1) {
  227. buffer[length-1] = 0; /* strip CRLF */
  228. thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(EXCLUDELIST));
  229. thisEntry->next = excludeList;
  230. excludeList = thisEntry;
  231. thisEntry->name = HeapAlloc(GetProcessHeap(), 0,
  232. (length * sizeof(WCHAR))+1);
  233. lstrcpyW(thisEntry->name, buffer);
  234. CharUpperBuffW(thisEntry->name, length);
  235. WINE_TRACE("Read line : '%s'\n", wine_dbgstr_w(thisEntry->name));
  236. }
  237. }
  238. /* See if EOF or error occurred */
  239. if (!feof(inFile)) {
  240. XCOPY_wprintf(XCOPY_LoadMessage(STRING_READFAIL), filename);
  241. *endOfName = endChar;
  242. fclose(inFile);
  243. return TRUE;
  244. }
  245. /* Revert the input string to original form, and cleanup + return */
  246. *endOfName = endChar;
  247. fclose(inFile);
  248. return FALSE;
  249. }
  250. /* =========================================================================
  251. * Process the /EXCLUDE: file list, building up a list of substrings to
  252. * avoid copying
  253. * Returns TRUE on any failure
  254. * ========================================================================= */
  255. static BOOL XCOPY_ProcessExcludeList(WCHAR* parms) {
  256. WCHAR *filenameStart = parms;
  257. WINE_TRACE("/EXCLUDE parms: '%s'\n", wine_dbgstr_w(parms));
  258. excludeList = NULL;
  259. while (*parms && *parms != ' ' && *parms != '/') {
  260. /* If found '+' then process the file found so far */
  261. if (*parms == '+') {
  262. if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
  263. return TRUE;
  264. }
  265. filenameStart = parms+1;
  266. }
  267. parms++;
  268. }
  269. if (filenameStart != parms) {
  270. if (XCOPY_ProcessExcludeFile(filenameStart, parms)) {
  271. return TRUE;
  272. }
  273. }
  274. return FALSE;
  275. }
  276. /* =========================================================================
  277. XCOPY_DoCopy - Recursive function to copy files based on input parms
  278. of a stem and a spec
  279. This works by using FindFirstFile supplying the source stem and spec.
  280. If results are found, any non-directory ones are processed
  281. Then, if /S or /E is supplied, another search is made just for
  282. directories, and this function is called again for that directory
  283. ========================================================================= */
  284. static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
  285. WCHAR *deststem, WCHAR *destspec,
  286. DWORD flags)
  287. {
  288. WIN32_FIND_DATAW *finddata;
  289. HANDLE h;
  290. BOOL findres = TRUE;
  291. WCHAR *inputpath, *outputpath;
  292. BOOL copiedFile = FALSE;
  293. DWORD destAttribs, srcAttribs;
  294. BOOL skipFile;
  295. int ret = 0;
  296. /* Allocate some working memory on heap to minimize footprint */
  297. finddata = HeapAlloc(GetProcessHeap(), 0, sizeof(WIN32_FIND_DATAW));
  298. inputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
  299. outputpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR));
  300. /* Build the search info into a single parm */
  301. lstrcpyW(inputpath, srcstem);
  302. lstrcatW(inputpath, srcspec);
  303. /* Search 1 - Look for matching files */
  304. h = FindFirstFileW(inputpath, finddata);
  305. while (h != INVALID_HANDLE_VALUE && findres) {
  306. skipFile = FALSE;
  307. /* Ignore . and .. */
  308. if (lstrcmpW(finddata->cFileName, L".")==0 ||
  309. lstrcmpW(finddata->cFileName, L"..")==0 ||
  310. finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  311. WINE_TRACE("Skipping directory, . or .. (%s)\n", wine_dbgstr_w(finddata->cFileName));
  312. } else {
  313. /* Get the filename information */
  314. lstrcpyW(copyFrom, srcstem);
  315. if (flags & OPT_SHORTNAME) {
  316. lstrcatW(copyFrom, finddata->cAlternateFileName);
  317. } else {
  318. lstrcatW(copyFrom, finddata->cFileName);
  319. }
  320. lstrcpyW(copyTo, deststem);
  321. if (*destspec == 0x00) {
  322. if (flags & OPT_SHORTNAME) {
  323. lstrcatW(copyTo, finddata->cAlternateFileName);
  324. } else {
  325. lstrcatW(copyTo, finddata->cFileName);
  326. }
  327. } else {
  328. lstrcatW(copyTo, destspec);
  329. }
  330. /* Do the copy */
  331. WINE_TRACE("ACTION: Copy '%s' -> '%s'\n", wine_dbgstr_w(copyFrom),
  332. wine_dbgstr_w(copyTo));
  333. if (!copiedFile && !(flags & OPT_SIMULATE)) XCOPY_CreateDirectory(deststem);
  334. /* See if allowed to copy it */
  335. srcAttribs = GetFileAttributesW(copyFrom);
  336. WINE_TRACE("Source attribs: %d\n", srcAttribs);
  337. if ((srcAttribs & FILE_ATTRIBUTE_HIDDEN) ||
  338. (srcAttribs & FILE_ATTRIBUTE_SYSTEM)) {
  339. if (!(flags & OPT_COPYHIDSYS)) {
  340. skipFile = TRUE;
  341. }
  342. }
  343. if (!(srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
  344. (flags & OPT_ARCHIVEONLY)) {
  345. skipFile = TRUE;
  346. }
  347. /* See if file exists */
  348. destAttribs = GetFileAttributesW(copyTo);
  349. WINE_TRACE("Dest attribs: %d\n", srcAttribs);
  350. /* Check date ranges if a destination file already exists */
  351. if (!skipFile && (flags & OPT_DATERANGE) &&
  352. (CompareFileTime(&finddata->ftLastWriteTime, &dateRange) < 0)) {
  353. WINE_TRACE("Skipping file as modified date too old\n");
  354. skipFile = TRUE;
  355. }
  356. /* If just /D supplied, only overwrite if src newer than dest */
  357. if (!skipFile && (flags & OPT_DATENEWER) &&
  358. (destAttribs != INVALID_FILE_ATTRIBUTES)) {
  359. HANDLE h = CreateFileW(copyTo, GENERIC_READ, FILE_SHARE_READ,
  360. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
  361. NULL);
  362. if (h != INVALID_HANDLE_VALUE) {
  363. FILETIME writeTime;
  364. GetFileTime(h, NULL, NULL, &writeTime);
  365. if (CompareFileTime(&finddata->ftLastWriteTime, &writeTime) <= 0) {
  366. WINE_TRACE("Skipping file as dest newer or same date\n");
  367. skipFile = TRUE;
  368. }
  369. CloseHandle(h);
  370. }
  371. }
  372. /* See if exclude list provided. Note since filenames are case
  373. insensitive, need to uppercase the filename before doing
  374. strstr */
  375. if (!skipFile && (flags & OPT_EXCLUDELIST)) {
  376. EXCLUDELIST *pos = excludeList;
  377. WCHAR copyFromUpper[MAX_PATH];
  378. /* Uppercase source filename */
  379. lstrcpyW(copyFromUpper, copyFrom);
  380. CharUpperBuffW(copyFromUpper, lstrlenW(copyFromUpper));
  381. /* Loop through testing each exclude line */
  382. while (pos) {
  383. if (wcsstr(copyFromUpper, pos->name) != NULL) {
  384. WINE_TRACE("Skipping file as matches exclude '%s'\n",
  385. wine_dbgstr_w(pos->name));
  386. skipFile = TRUE;
  387. pos = NULL;
  388. } else {
  389. pos = pos->next;
  390. }
  391. }
  392. }
  393. /* Prompt each file if necessary */
  394. if (!skipFile && (flags & OPT_SRCPROMPT)) {
  395. DWORD count;
  396. char answer[10];
  397. BOOL answered = FALSE;
  398. WCHAR yesChar[2];
  399. WCHAR noChar[2];
  400. /* Read the Y and N characters from the resource file */
  401. wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
  402. wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
  403. while (!answered) {
  404. XCOPY_wprintf(XCOPY_LoadMessage(STRING_SRCPROMPT), copyFrom);
  405. ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
  406. &count, NULL);
  407. answered = TRUE;
  408. if (toupper(answer[0]) == noChar[0])
  409. skipFile = TRUE;
  410. else if (toupper(answer[0]) != yesChar[0])
  411. answered = FALSE;
  412. }
  413. }
  414. if (!skipFile &&
  415. destAttribs != INVALID_FILE_ATTRIBUTES && !(flags & OPT_NOPROMPT)) {
  416. DWORD count;
  417. char answer[10];
  418. BOOL answered = FALSE;
  419. WCHAR yesChar[2];
  420. WCHAR allChar[2];
  421. WCHAR noChar[2];
  422. /* Read the A,Y and N characters from the resource file */
  423. wcscpy(yesChar, XCOPY_LoadMessage(STRING_YES_CHAR));
  424. wcscpy(allChar, XCOPY_LoadMessage(STRING_ALL_CHAR));
  425. wcscpy(noChar, XCOPY_LoadMessage(STRING_NO_CHAR));
  426. while (!answered) {
  427. XCOPY_wprintf(XCOPY_LoadMessage(STRING_OVERWRITE), copyTo);
  428. ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
  429. &count, NULL);
  430. answered = TRUE;
  431. if (toupper(answer[0]) == allChar[0])
  432. flags |= OPT_NOPROMPT;
  433. else if (toupper(answer[0]) == noChar[0])
  434. skipFile = TRUE;
  435. else if (toupper(answer[0]) != yesChar[0])
  436. answered = FALSE;
  437. }
  438. }
  439. /* See if it has to exist! */
  440. if (destAttribs == INVALID_FILE_ATTRIBUTES && (flags & OPT_MUSTEXIST)) {
  441. skipFile = TRUE;
  442. }
  443. /* Output a status message */
  444. if (!skipFile) {
  445. if (!(flags & OPT_QUIET)) {
  446. if (flags & OPT_FULL)
  447. XCOPY_wprintf(L"%1 -> %2\n", copyFrom, copyTo);
  448. else
  449. XCOPY_wprintf(L"%1\n", copyFrom);
  450. }
  451. /* If allowing overwriting of read only files, remove any
  452. write protection */
  453. if ((destAttribs & FILE_ATTRIBUTE_READONLY) &&
  454. (flags & OPT_REPLACEREAD)) {
  455. SetFileAttributesW(copyTo, destAttribs & ~FILE_ATTRIBUTE_READONLY);
  456. }
  457. copiedFile = TRUE;
  458. if (flags & OPT_SIMULATE || flags & OPT_NOCOPY) {
  459. /* Skip copy */
  460. } else if (CopyFileW(copyFrom, copyTo, FALSE) == 0) {
  461. DWORD error = GetLastError();
  462. XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPYFAIL),
  463. copyFrom, copyTo, error);
  464. XCOPY_FailMessage(error);
  465. if (flags & OPT_IGNOREERRORS) {
  466. skipFile = TRUE;
  467. } else {
  468. ret = RC_WRITEERROR;
  469. goto cleanup;
  470. }
  471. } else {
  472. if (!skipFile) {
  473. /* If keeping attributes, update the destination attributes
  474. otherwise remove the read only attribute */
  475. if (flags & OPT_KEEPATTRS) {
  476. SetFileAttributesW(copyTo, srcAttribs | FILE_ATTRIBUTE_ARCHIVE);
  477. } else {
  478. SetFileAttributesW(copyTo,
  479. (GetFileAttributesW(copyTo) & ~FILE_ATTRIBUTE_READONLY));
  480. }
  481. /* If /M supplied, remove the archive bit after successful copy */
  482. if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) &&
  483. (flags & OPT_REMOVEARCH)) {
  484. SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE));
  485. }
  486. filesCopied++;
  487. }
  488. }
  489. }
  490. }
  491. /* Find next file */
  492. findres = FindNextFileW(h, finddata);
  493. }
  494. FindClose(h);
  495. /* Search 2 - do subdirs */
  496. if (flags & OPT_RECURSIVE) {
  497. /* If /E is supplied, create the directory now */
  498. if ((flags & OPT_EMPTYDIR) &&
  499. !(flags & OPT_SIMULATE)) {
  500. XCOPY_CreateDirectory(deststem);
  501. }
  502. lstrcpyW(inputpath, srcstem);
  503. lstrcatW(inputpath, L"*");
  504. findres = TRUE;
  505. WINE_TRACE("Processing subdirs with spec: %s\n", wine_dbgstr_w(inputpath));
  506. h = FindFirstFileW(inputpath, finddata);
  507. while (h != INVALID_HANDLE_VALUE && findres) {
  508. /* Only looking for dirs */
  509. if ((finddata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  510. (lstrcmpW(finddata->cFileName, L".") != 0) &&
  511. (lstrcmpW(finddata->cFileName, L"..") != 0)) {
  512. WINE_TRACE("Handling subdir: %s\n", wine_dbgstr_w(finddata->cFileName));
  513. /* Make up recursive information */
  514. lstrcpyW(inputpath, srcstem);
  515. lstrcatW(inputpath, finddata->cFileName);
  516. lstrcatW(inputpath, L"\\");
  517. lstrcpyW(outputpath, deststem);
  518. if (*destspec == 0x00) {
  519. lstrcatW(outputpath, finddata->cFileName);
  520. lstrcatW(outputpath, L"\\");
  521. }
  522. XCOPY_DoCopy(inputpath, srcspec, outputpath, destspec, flags);
  523. }
  524. /* Find next one */
  525. findres = FindNextFileW(h, finddata);
  526. }
  527. FindClose(h);
  528. }
  529. cleanup:
  530. /* free up memory */
  531. HeapFree(GetProcessHeap(), 0, finddata);
  532. HeapFree(GetProcessHeap(), 0, inputpath);
  533. HeapFree(GetProcessHeap(), 0, outputpath);
  534. return ret;
  535. }
  536. /* =========================================================================
  537. XCOPY_ParseCommandLine - Parses the command line
  538. ========================================================================= */
  539. static inline BOOL is_whitespace(WCHAR c)
  540. {
  541. return c == ' ' || c == '\t';
  542. }
  543. static WCHAR *skip_whitespace(WCHAR *p)
  544. {
  545. for (; *p && is_whitespace(*p); p++);
  546. return p;
  547. }
  548. static inline BOOL is_digit(WCHAR c)
  549. {
  550. return c >= '0' && c <= '9';
  551. }
  552. /* Windows XCOPY uses a simplified command line parsing algorithm
  553. that lacks the escaped-quote logic of build_argv(), because
  554. literal double quotes are illegal in any of its arguments.
  555. Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
  556. static int find_end_of_word(const WCHAR *word, WCHAR **end)
  557. {
  558. BOOL in_quotes = FALSE;
  559. const WCHAR *ptr = word;
  560. for (;;) {
  561. for (; *ptr != '\0' && *ptr != '"' &&
  562. (in_quotes || !is_whitespace(*ptr)); ptr++);
  563. if (*ptr == '"') {
  564. in_quotes = !in_quotes;
  565. ptr++;
  566. }
  567. /* Odd number of double quotes is illegal for XCOPY */
  568. if (in_quotes && *ptr == '\0')
  569. return RC_INITERROR;
  570. if (*ptr == '\0' || (!in_quotes && is_whitespace(*ptr)))
  571. break;
  572. }
  573. *end = (WCHAR*)ptr;
  574. return RC_OK;
  575. }
  576. /* Remove all double quotes from a word */
  577. static void strip_quotes(WCHAR *word, WCHAR **end)
  578. {
  579. WCHAR *rp, *wp;
  580. for (rp = word, wp = word; *rp != '\0'; rp++) {
  581. if (*rp == '"')
  582. continue;
  583. if (wp < rp)
  584. *wp = *rp;
  585. wp++;
  586. }
  587. *wp = '\0';
  588. *end = wp;
  589. }
  590. static int XCOPY_ParseCommandLine(WCHAR *suppliedsource,
  591. WCHAR *supplieddestination, DWORD *pflags)
  592. {
  593. DWORD flags = *pflags;
  594. WCHAR *cmdline, *word, *end, *next;
  595. int rc = RC_INITERROR;
  596. cmdline = _wcsdup(GetCommandLineW());
  597. if (cmdline == NULL)
  598. return rc;
  599. /* Skip first arg, which is the program name */
  600. if ((rc = find_end_of_word(cmdline, &word)) != RC_OK)
  601. goto out;
  602. word = skip_whitespace(word);
  603. while (*word)
  604. {
  605. WCHAR first;
  606. if ((rc = find_end_of_word(word, &end)) != RC_OK)
  607. goto out;
  608. next = skip_whitespace(end);
  609. first = word[0];
  610. *end = '\0';
  611. strip_quotes(word, &end);
  612. WINE_TRACE("Processing Arg: '%s'\n", wine_dbgstr_w(word));
  613. /* First non-switch parameter is source, second is destination */
  614. if (first != '/') {
  615. if (suppliedsource[0] == 0x00) {
  616. lstrcpyW(suppliedsource, word);
  617. } else if (supplieddestination[0] == 0x00) {
  618. lstrcpyW(supplieddestination, word);
  619. } else {
  620. XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARMS));
  621. goto out;
  622. }
  623. } else {
  624. /* Process all the switch options
  625. Note: Windows docs say /P prompts when dest is created
  626. but tests show it is done for each src file
  627. regardless of the destination */
  628. int skip=0;
  629. WCHAR *rest;
  630. while (word[0]) {
  631. rest = NULL;
  632. switch (toupper(word[1])) {
  633. case 'I': flags |= OPT_ASSUMEDIR; break;
  634. case 'S': flags |= OPT_RECURSIVE; break;
  635. case 'Q': flags |= OPT_QUIET; break;
  636. case 'F': flags |= OPT_FULL; break;
  637. case 'L': flags |= OPT_SIMULATE; break;
  638. case 'W': flags |= OPT_PAUSE; break;
  639. case 'T': flags |= OPT_NOCOPY | OPT_RECURSIVE; break;
  640. case 'Y': flags |= OPT_NOPROMPT; break;
  641. case 'N': flags |= OPT_SHORTNAME; break;
  642. case 'U': flags |= OPT_MUSTEXIST; break;
  643. case 'R': flags |= OPT_REPLACEREAD; break;
  644. case 'K': flags |= OPT_KEEPATTRS; break;
  645. case 'H': flags |= OPT_COPYHIDSYS; break;
  646. case 'C': flags |= OPT_IGNOREERRORS; break;
  647. case 'P': flags |= OPT_SRCPROMPT; break;
  648. case 'A': flags |= OPT_ARCHIVEONLY; break;
  649. case 'M': flags |= OPT_ARCHIVEONLY |
  650. OPT_REMOVEARCH; break;
  651. /* E can be /E or /EXCLUDE */
  652. case 'E': if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  653. &word[1], 8, L"EXCLUDE:", -1) == CSTR_EQUAL) {
  654. if (XCOPY_ProcessExcludeList(&word[9])) {
  655. XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
  656. goto out;
  657. } else {
  658. flags |= OPT_EXCLUDELIST;
  659. /* Do not support concatenated switches onto exclude lists yet */
  660. rest = end;
  661. }
  662. } else {
  663. flags |= OPT_EMPTYDIR | OPT_RECURSIVE;
  664. }
  665. break;
  666. /* D can be /D or /D: */
  667. case 'D': if (word[2]==':' && is_digit(word[3])) {
  668. SYSTEMTIME st;
  669. WCHAR *pos = &word[3];
  670. BOOL isError = FALSE;
  671. memset(&st, 0x00, sizeof(st));
  672. /* Microsoft xcopy's usage message implies that the date
  673. * format depends on the locale, but that is false.
  674. * It is hardcoded to month-day-year.
  675. */
  676. st.wMonth = _wtol(pos);
  677. while (*pos && is_digit(*pos)) pos++;
  678. if (*pos++ != '-') isError = TRUE;
  679. if (!isError) {
  680. st.wDay = _wtol(pos);
  681. while (*pos && is_digit(*pos)) pos++;
  682. if (*pos++ != '-') isError = TRUE;
  683. }
  684. if (!isError) {
  685. st.wYear = _wtol(pos);
  686. while (*pos && is_digit(*pos)) pos++;
  687. if (st.wYear < 100) st.wYear+=2000;
  688. }
  689. /* Handle switches straight after the supplied date */
  690. rest = pos;
  691. if (!isError && SystemTimeToFileTime(&st, &dateRange)) {
  692. SYSTEMTIME st;
  693. WCHAR datestring[32], timestring[32];
  694. flags |= OPT_DATERANGE;
  695. /* Debug info: */
  696. FileTimeToSystemTime (&dateRange, &st);
  697. GetDateFormatW(0, DATE_SHORTDATE, &st, NULL, datestring,
  698. ARRAY_SIZE(datestring));
  699. GetTimeFormatW(0, TIME_NOSECONDS, &st,
  700. NULL, timestring, ARRAY_SIZE(timestring));
  701. WINE_TRACE("Date being used is: %s %s\n",
  702. wine_dbgstr_w(datestring), wine_dbgstr_w(timestring));
  703. } else {
  704. XCOPY_FailMessage(ERROR_INVALID_PARAMETER);
  705. goto out;
  706. }
  707. } else {
  708. flags |= OPT_DATENEWER;
  709. }
  710. break;
  711. case '-': if (toupper(word[2])=='Y') {
  712. flags &= ~OPT_NOPROMPT;
  713. rest = &word[3]; /* Skip over 3 characters */
  714. }
  715. break;
  716. case '?': XCOPY_wprintf(XCOPY_LoadMessage(STRING_HELP));
  717. rc = RC_HELP;
  718. goto out;
  719. case 'V':
  720. WINE_FIXME("ignoring /V\n");
  721. break;
  722. default:
  723. WINE_TRACE("Unhandled parameter '%s'\n", wine_dbgstr_w(word));
  724. XCOPY_wprintf(XCOPY_LoadMessage(STRING_INVPARM), word);
  725. goto out;
  726. }
  727. /* Unless overridden above, skip over the '/' and the first character */
  728. if (rest == NULL) rest = &word[2];
  729. /* By now, rest should point either to the null after the
  730. switch, or the beginning of the next switch if there
  731. was no whitespace between them */
  732. if (!skip && *rest && *rest != '/') {
  733. WINE_FIXME("Unexpected characters found and ignored '%s'\n", wine_dbgstr_w(rest));
  734. skip=1;
  735. } else {
  736. word = rest;
  737. }
  738. }
  739. }
  740. word = next;
  741. }
  742. /* Default the destination if not supplied */
  743. if (supplieddestination[0] == 0x00)
  744. lstrcpyW(supplieddestination, L".");
  745. *pflags = flags;
  746. rc = RC_OK;
  747. out:
  748. free(cmdline);
  749. return rc;
  750. }
  751. /* =========================================================================
  752. XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
  753. converts it into a stem and a filespec
  754. ========================================================================= */
  755. static int XCOPY_ProcessSourceParm(WCHAR *suppliedsource, WCHAR *stem,
  756. WCHAR *spec, DWORD flags)
  757. {
  758. WCHAR actualsource[MAX_PATH];
  759. WCHAR *starPos;
  760. WCHAR *questPos;
  761. DWORD attribs;
  762. /*
  763. * Validate the source, expanding to full path ensuring it exists
  764. */
  765. if (GetFullPathNameW(suppliedsource, MAX_PATH, actualsource, NULL) == 0) {
  766. WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
  767. return RC_INITERROR;
  768. }
  769. /* If full names required, convert to using the full path */
  770. if (flags & OPT_FULL) {
  771. lstrcpyW(suppliedsource, actualsource);
  772. }
  773. /*
  774. * Work out the stem of the source
  775. */
  776. /* If a directory is supplied, use that as-is (either fully or
  777. partially qualified)
  778. If a filename is supplied + a directory or drive path, use that
  779. as-is
  780. Otherwise
  781. If no directory or path specified, add eg. C:
  782. stem is Drive/Directory is bit up to last \ (or first :)
  783. spec is bit after that */
  784. starPos = wcschr(suppliedsource, '*');
  785. questPos = wcschr(suppliedsource, '?');
  786. if (starPos || questPos) {
  787. attribs = 0x00; /* Ensures skips invalid or directory check below */
  788. } else {
  789. attribs = GetFileAttributesW(actualsource);
  790. }
  791. if (attribs == INVALID_FILE_ATTRIBUTES) {
  792. XCOPY_FailMessage(GetLastError());
  793. return RC_INITERROR;
  794. /* Directory:
  795. stem should be exactly as supplied plus a '\', unless it was
  796. eg. C: in which case no slash required */
  797. } else if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
  798. WCHAR lastChar;
  799. WINE_TRACE("Directory supplied\n");
  800. lstrcpyW(stem, suppliedsource);
  801. lastChar = stem[lstrlenW(stem)-1];
  802. if (lastChar != '\\' && lastChar != ':')
  803. lstrcatW(stem, L"\\");
  804. lstrcpyW(spec, L"*");
  805. /* File or wildcard search:
  806. stem should be:
  807. Up to and including last slash if directory path supplied
  808. If c:filename supplied, just the c:
  809. Otherwise stem should be the current drive letter + ':' */
  810. } else {
  811. WCHAR *lastDir;
  812. WINE_TRACE("Filename supplied\n");
  813. lastDir = wcsrchr(suppliedsource, '\\');
  814. if (lastDir) {
  815. lstrcpyW(stem, suppliedsource);
  816. stem[(lastDir-suppliedsource) + 1] = 0x00;
  817. lstrcpyW(spec, (lastDir+1));
  818. } else if (suppliedsource[1] == ':') {
  819. lstrcpyW(stem, suppliedsource);
  820. stem[2] = 0x00;
  821. lstrcpyW(spec, suppliedsource+2);
  822. } else {
  823. WCHAR curdir[MAXSTRING];
  824. GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
  825. stem[0] = curdir[0];
  826. stem[1] = curdir[1];
  827. stem[2] = 0x00;
  828. lstrcpyW(spec, suppliedsource);
  829. }
  830. }
  831. return RC_OK;
  832. }
  833. /* =========================================================================
  834. XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
  835. converts it into a stem
  836. ========================================================================= */
  837. static int XCOPY_ProcessDestParm(WCHAR *supplieddestination, WCHAR *stem, WCHAR *spec,
  838. WCHAR *srcspec, DWORD flags)
  839. {
  840. WCHAR actualdestination[MAX_PATH];
  841. DWORD attribs;
  842. BOOL isDir = FALSE;
  843. /*
  844. * Validate the source, expanding to full path ensuring it exists
  845. */
  846. if (GetFullPathNameW(supplieddestination, MAX_PATH, actualdestination, NULL) == 0) {
  847. WINE_FIXME("Unexpected failure expanding source path (%d)\n", GetLastError());
  848. return RC_INITERROR;
  849. }
  850. /* Destination is either a directory or a file */
  851. attribs = GetFileAttributesW(actualdestination);
  852. if (attribs == INVALID_FILE_ATTRIBUTES) {
  853. /* If /I supplied and wildcard copy, assume directory */
  854. /* Also if destination ends with backslash */
  855. if ((flags & OPT_ASSUMEDIR &&
  856. (wcschr(srcspec, '?') || wcschr(srcspec, '*'))) ||
  857. (supplieddestination[lstrlenW(supplieddestination)-1] == '\\')) {
  858. isDir = TRUE;
  859. } else {
  860. DWORD count;
  861. char answer[10] = "";
  862. WCHAR fileChar[2];
  863. WCHAR dirChar[2];
  864. /* Read the F and D characters from the resource file */
  865. wcscpy(fileChar, XCOPY_LoadMessage(STRING_FILE_CHAR));
  866. wcscpy(dirChar, XCOPY_LoadMessage(STRING_DIR_CHAR));
  867. while (answer[0] != fileChar[0] && answer[0] != dirChar[0]) {
  868. XCOPY_wprintf(XCOPY_LoadMessage(STRING_QISDIR), supplieddestination);
  869. ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer), &count, NULL);
  870. WINE_TRACE("User answer %c\n", answer[0]);
  871. answer[0] = toupper(answer[0]);
  872. }
  873. if (answer[0] == dirChar[0]) {
  874. isDir = TRUE;
  875. } else {
  876. isDir = FALSE;
  877. }
  878. }
  879. } else {
  880. isDir = (attribs & FILE_ATTRIBUTE_DIRECTORY);
  881. }
  882. if (isDir) {
  883. lstrcpyW(stem, actualdestination);
  884. *spec = 0x00;
  885. /* Ensure ends with a '\' */
  886. if (stem[lstrlenW(stem)-1] != '\\')
  887. lstrcatW(stem, L"\\");
  888. } else {
  889. WCHAR drive[MAX_PATH];
  890. WCHAR dir[MAX_PATH];
  891. WCHAR fname[MAX_PATH];
  892. WCHAR ext[MAX_PATH];
  893. _wsplitpath(actualdestination, drive, dir, fname, ext);
  894. lstrcpyW(stem, drive);
  895. lstrcatW(stem, dir);
  896. lstrcpyW(spec, fname);
  897. lstrcatW(spec, ext);
  898. }
  899. return RC_OK;
  900. }
  901. /* =========================================================================
  902. main - Main entrypoint for the xcopy command
  903. Processes the args, and drives the actual copying
  904. ========================================================================= */
  905. int __cdecl wmain (int argc, WCHAR *argvW[])
  906. {
  907. int rc = 0;
  908. WCHAR suppliedsource[MAX_PATH] = {0}; /* As supplied on the cmd line */
  909. WCHAR supplieddestination[MAX_PATH] = {0};
  910. WCHAR sourcestem[MAX_PATH] = {0}; /* Stem of source */
  911. WCHAR sourcespec[MAX_PATH] = {0}; /* Filespec of source */
  912. WCHAR destinationstem[MAX_PATH] = {0}; /* Stem of destination */
  913. WCHAR destinationspec[MAX_PATH] = {0}; /* Filespec of destination */
  914. WCHAR copyCmd[MAXSTRING]; /* COPYCMD env var */
  915. DWORD flags = 0; /* Option flags */
  916. /* Preinitialize flags based on COPYCMD */
  917. if (GetEnvironmentVariableW(L"COPYCMD", copyCmd, MAXSTRING)) {
  918. if (wcsstr(copyCmd, L"/Y") != NULL || wcsstr(copyCmd, L"/y") != NULL)
  919. flags |= OPT_NOPROMPT;
  920. }
  921. /* FIXME: On UNIX, files starting with a '.' are treated as hidden under
  922. wine, but on windows these can be normal files. At least one installer
  923. uses files such as .packlist and (validly) expects them to be copied.
  924. Under wine, if we do not copy hidden files by default then they get
  925. lose */
  926. flags |= OPT_COPYHIDSYS;
  927. /*
  928. * Parse the command line
  929. */
  930. if ((rc = XCOPY_ParseCommandLine(suppliedsource, supplieddestination,
  931. &flags)) != RC_OK) {
  932. if (rc == RC_HELP)
  933. return RC_OK;
  934. else
  935. return rc;
  936. }
  937. /* Trace out the supplied information */
  938. WINE_TRACE("Supplied parameters:\n");
  939. WINE_TRACE("Source : '%s'\n", wine_dbgstr_w(suppliedsource));
  940. WINE_TRACE("Destination : '%s'\n", wine_dbgstr_w(supplieddestination));
  941. /* Extract required information from source specification */
  942. rc = XCOPY_ProcessSourceParm(suppliedsource, sourcestem, sourcespec, flags);
  943. if (rc != RC_OK) return rc;
  944. /* Extract required information from destination specification */
  945. rc = XCOPY_ProcessDestParm(supplieddestination, destinationstem,
  946. destinationspec, sourcespec, flags);
  947. if (rc != RC_OK) return rc;
  948. /* Trace out the resulting information */
  949. WINE_TRACE("Resolved parameters:\n");
  950. WINE_TRACE("Source Stem : '%s'\n", wine_dbgstr_w(sourcestem));
  951. WINE_TRACE("Source Spec : '%s'\n", wine_dbgstr_w(sourcespec));
  952. WINE_TRACE("Dest Stem : '%s'\n", wine_dbgstr_w(destinationstem));
  953. WINE_TRACE("Dest Spec : '%s'\n", wine_dbgstr_w(destinationspec));
  954. /* Pause if necessary */
  955. if (flags & OPT_PAUSE) {
  956. DWORD count;
  957. char pausestr[10];
  958. XCOPY_wprintf(XCOPY_LoadMessage(STRING_PAUSE));
  959. ReadFile (GetStdHandle(STD_INPUT_HANDLE), pausestr, sizeof(pausestr),
  960. &count, NULL);
  961. }
  962. /* Now do the hard work... */
  963. rc = XCOPY_DoCopy(sourcestem, sourcespec,
  964. destinationstem, destinationspec,
  965. flags);
  966. /* Clear up exclude list allocated memory */
  967. while (excludeList) {
  968. EXCLUDELIST *pos = excludeList;
  969. excludeList = excludeList -> next;
  970. HeapFree(GetProcessHeap(), 0, pos->name);
  971. HeapFree(GetProcessHeap(), 0, pos);
  972. }
  973. /* Finished - print trailer and exit */
  974. if (flags & OPT_SIMULATE) {
  975. XCOPY_wprintf(XCOPY_LoadMessage(STRING_SIMCOPY), filesCopied);
  976. } else if (!(flags & OPT_NOCOPY)) {
  977. XCOPY_wprintf(XCOPY_LoadMessage(STRING_COPY), filesCopied);
  978. }
  979. return rc;
  980. }