Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1929 lines
54 KiB

  1. #include "stdinc.h" // actually from dll\whistler directory
  2. /*-----------------------------------------------------------------------------
  3. Side X ("by") Side Test
  4. -----------------------------------------------------------------------------*/
  5. #include "nt.h"
  6. #include "ntrtl.h"
  7. #include "nturtl.h"
  8. #include "windows.h"
  9. #include "fusionlastwin32error.h"
  10. #include <stddef.h>
  11. #include <stdlib.h>
  12. #include <stdio.h>
  13. #include <stdarg.h>
  14. #pragma warning(push)
  15. #pragma warning(disable: 4511)
  16. #pragma warning(disable: 4512)
  17. #pragma warning(disable: 4663)
  18. #include <yvals.h>
  19. #pragma warning(disable: 4663)
  20. #include <string>
  21. #include <deque>
  22. #include <vector>
  23. #pragma warning(pop)
  24. #include "fusionbuffer.h"
  25. #include "fusion.h"
  26. #include "sxsasmname.h"
  27. #include "util.h"
  28. #include "filestream.cpp"
  29. #include "sxsapi.h"
  30. #include "fusiontrace.h"
  31. #include "cresourcestream.cpp"
  32. #include "cmemorystream.cpp"
  33. #include "wintrust.h"
  34. #include "softpub.h"
  35. #include "perfclocking.h"
  36. #include "strongname.h"
  37. #include "fusionversion.h"
  38. #include "fusionhash.h"
  39. #include "fusiondeque.h"
  40. BOOL
  41. MySetFilePointerEx(
  42. HANDLE File, // handle to file
  43. LARGE_INTEGER DistanceToMove, // bytes to move pointer
  44. PLARGE_INTEGER NewFilePointer, // new file pointer
  45. DWORD MoveMethod // starting point
  46. )
  47. {
  48. LONG DistanceToMoveLow = static_cast<LONG>(DistanceToMove.LowPart);
  49. LONG DistanceToMoveHigh = DistanceToMove.HighPart;
  50. DWORD NewPositionLow = SetFilePointer(File, DistanceToMoveLow, &DistanceToMoveHigh, MoveMethod);
  51. if (NewPositionLow == INVALID_SET_FILE_POINTER)
  52. {
  53. if (GetLastError() != NO_ERROR)
  54. return FALSE;
  55. }
  56. if (NewFilePointer != NULL)
  57. {
  58. NewFilePointer->LowPart = NewPositionLow;
  59. NewFilePointer->HighPart = DistanceToMoveHigh;
  60. }
  61. return TRUE;
  62. }
  63. BOOL FusionpAreWeInOSSetupMode(BOOL* pfIsInSetup) { *pfIsInSetup = FALSE; return TRUE; }
  64. void ReportFailure(const char szFormat[], ...);
  65. PCWSTR g_pszImage = L"mjgcopy";
  66. CRITICAL_SECTION g_cs;
  67. ULONGLONG g_FilesCopied = 0;
  68. ULONGLONG g_BytesCopied = 0;
  69. ULONGLONG g_FilesLinked = 0;
  70. ULONGLONG g_DirectoriesCopied = 0;
  71. ULONGLONG g_CopiesSkipped = 0;
  72. ULONGLONG g_LinksSkipped = 0;
  73. ULONGLONG g_FileCopiesProcessed = 0;
  74. ULONGLONG g_FileLinksProcessed = 0;
  75. ULONGLONG g_DirScansProcessed = 0;
  76. ULONGLONG g_BytesToCopy = 0;
  77. bool g_fAnnounceDirectories = true;
  78. bool g_fAnnounceCopies = false;
  79. bool g_fAnnounceDeletes = true;
  80. bool g_fAnnounceLinks = true;
  81. bool g_fAnnounceSkips = false;
  82. bool g_fSilent = false;
  83. bool g_fShowProgress = true;
  84. HANDLE g_hIoCompletionPort = NULL;
  85. HANDLE g_hWorkItemDoneEvent = INVALID_HANDLE_VALUE;
  86. DWORD g_dwDestinationSectorsPerCluster = 0;
  87. DWORD g_dwDestinationBytesPerSector = 0;
  88. DWORD g_dwDestinationNumberOfFreeClusters = 0;
  89. DWORD g_dwDestinationTotalNumberOfClusters = 0;
  90. ULONG g_nThreads = 3;
  91. HANDLE g_rghThreads[32];
  92. class CFileCopy;
  93. class CFileLink;
  94. class CDir;
  95. // BOOL ScanAndCopyDir(PCWSTR szSource, PCWSTR szDest);
  96. BOOL BuildDirList(const CBaseStringBuffer &rbuffSource, const CBaseStringBuffer &rbuffDestination);
  97. BOOL MakeDirectoryStructure();
  98. BOOL QueueDirScans();
  99. BOOL QueueFileCopies();
  100. BOOL QueueFileLinks();
  101. DWORD WINAPI WorkerThreadProc(LPVOID pvParameter);
  102. void ComputeTimeDeltas(const SYSTEMTIME &rstStart, const SYSTEMTIME &rstEnd, SYSTEMTIME &rstDelta);
  103. BOOL ProcessFileCopy(CFileCopy *pFileCopy, bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer);
  104. BOOL ProcessFileLink(CFileLink *pFileLink, bool &rfReQueue);
  105. BOOL ProcessDirScan(CDir *pDir, bool &rfReQueue);
  106. BOOL ResumeWorkerThreads();
  107. BOOL SuspendWorkerThreads();
  108. BOOL
  109. WaitForWorkersToComplete(
  110. ULONGLONG &rullCounter,
  111. ULONGLONG ullLimit,
  112. PCSTR pszOperationName
  113. );
  114. BOOL
  115. MyCopyFile(
  116. BYTE *pBuffer,
  117. DWORD cbBuffer,
  118. PCWSTR lpExistingFileName,
  119. PCWSTR lpNewFileName,
  120. BOOL fFailIfExists,
  121. HANDLE &rhNewFileHandle
  122. );
  123. class CEntry
  124. {
  125. private:
  126. CEntry(const CEntry&); // deliberately not implemented
  127. void operator=(const CEntry&); // deliberately not implemented
  128. public:
  129. CEntry() : m_cRetries(0) { }
  130. ~CEntry() { }
  131. virtual BOOL BaseDoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer)
  132. {
  133. LARGE_INTEGER liStart, liEnd;
  134. ::QueryPerformanceCounter(&liStart);
  135. BOOL fResult = this->DoYourThing(rfReQueue, pBuffer, cbBuffer);
  136. if (fResult && rfReQueue)
  137. {
  138. m_cRetries++;
  139. if (m_cRetries > 100)
  140. fResult = FALSE;
  141. }
  142. CSxsPreserveLastError ple;
  143. ::QueryPerformanceCounter(&liEnd);
  144. ple.Restore();
  145. m_ullStart = static_cast<ULONGLONG>(liStart.QuadPart);
  146. m_ullEnd = static_cast<ULONGLONG>(liEnd.QuadPart);
  147. return fResult;
  148. }
  149. virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pbBuffer, DWORD cbBuffer) = 0;
  150. ULONGLONG m_ullStart, m_ullEnd;
  151. ULONG m_cRetries;
  152. };
  153. class CDir : public CEntry
  154. {
  155. private:
  156. CDir(const CDir&); // deliberately not implemented
  157. void operator=(const CDir&); // deliberately not implemented
  158. public:
  159. CDir() { };
  160. ~CDir() { };
  161. BOOL Initialize(const CBaseStringBuffer &rbuffSource, const CBaseStringBuffer &rbuffDestination)
  162. {
  163. BOOL fSuccess = FALSE;
  164. if (!m_buffSource.Win32Assign(rbuffSource))
  165. goto Exit;
  166. if (!m_buffDestination.Win32Assign(rbuffDestination))
  167. goto Exit;
  168. fSuccess = TRUE;
  169. Exit:
  170. return fSuccess;
  171. }
  172. virtual BOOL DoYourThing(bool &rfReQueue, BYTE *, DWORD) { return ::ProcessDirScan(this, rfReQueue); }
  173. CStringBuffer m_buffSource;
  174. CStringBuffer m_buffDestination;
  175. CDequeLinkage m_linkage;
  176. };
  177. class CFileBase : public CEntry
  178. {
  179. private:
  180. CFileBase(const CFileBase&); // deliberately not implemented
  181. void operator=(const CFileBase&); // deliberately not implemented
  182. public:
  183. CFileBase() : m_fDone(false) { }
  184. ~CFileBase() { }
  185. BOOL Initialize(
  186. CDir *pDir,
  187. PCWSTR pszFilename,
  188. FILETIME ftSourceCreationTime,
  189. FILETIME ftSourceLastAccessTime,
  190. FILETIME ftSourceLastWriteTime,
  191. ULONGLONG ullFileIndex,
  192. ULONGLONG cbSize
  193. )
  194. {
  195. BOOL fSuccess = FALSE;
  196. FN_TRACE_WIN32(fSuccess);
  197. IFW32FALSE_EXIT(m_buffFilename.Win32Assign(pszFilename, wcslen(pszFilename)));
  198. m_ftSourceCreationTime = ftSourceCreationTime;
  199. m_ftSourceLastAccessTime = ftSourceLastAccessTime;
  200. m_ftSourceLastWriteTime = ftSourceLastWriteTime;
  201. m_ullFileIndex = ullFileIndex;
  202. m_cbSize = cbSize;
  203. m_pDir = pDir;
  204. fSuccess = TRUE;
  205. Exit:
  206. return fSuccess;
  207. }
  208. virtual BOOL GetSource(CBaseStringBuffer &rbuff)
  209. {
  210. BOOL fSuccess = FALSE;
  211. FN_TRACE_WIN32(fSuccess);
  212. IFW32FALSE_EXIT(rbuff.Win32Assign(m_pDir->m_buffSource));
  213. IFW32FALSE_EXIT(rbuff.Win32Append(m_buffFilename));
  214. fSuccess = TRUE;
  215. Exit:
  216. return fSuccess;
  217. }
  218. virtual BOOL GetDestination(CBaseStringBuffer &rbuff)
  219. {
  220. BOOL fSuccess = FALSE;
  221. FN_TRACE_WIN32(fSuccess);
  222. IFW32FALSE_EXIT(rbuff.Win32Assign(m_pDir->m_buffDestination));
  223. IFW32FALSE_EXIT(rbuff.Win32Append(m_buffFilename));
  224. fSuccess = TRUE;
  225. Exit:
  226. return fSuccess;
  227. }
  228. CStringBuffer m_buffFilename;
  229. FILETIME m_ftSourceCreationTime;
  230. FILETIME m_ftSourceLastAccessTime;
  231. FILETIME m_ftSourceLastWriteTime;
  232. ULONGLONG m_ullFileIndex;
  233. ULONGLONG m_cbSize;
  234. CDir *m_pDir;
  235. bool m_fSkipped; // used to avoid skewed statistics about number of bytes copied/sec etc.
  236. bool m_fDone;
  237. CDequeLinkage m_linkage;
  238. };
  239. class CFileCopy : public CFileBase
  240. {
  241. public:
  242. CFileCopy() { }
  243. ~CFileCopy() { }
  244. virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer) { return ::ProcessFileCopy(this, rfReQueue, pBuffer, cbBuffer); }
  245. static int __cdecl QSortBySize(const void *param1, const void *param2)
  246. {
  247. CFileCopy **pp1 = (CFileCopy **) param1;
  248. CFileCopy **pp2 = (CFileCopy **) param2;
  249. CFileCopy *p1 = *pp1;
  250. CFileCopy *p2 = *pp2;
  251. int iRet = 0;
  252. if (p1->m_fSkipped)
  253. {
  254. if (p2->m_fSkipped)
  255. {
  256. if (p1->m_cbSize < p2->m_cbSize)
  257. iRet = 1;
  258. else if (p1->m_cbSize > p2->m_cbSize)
  259. iRet = -1;
  260. else
  261. {
  262. if (p1->m_ullFileIndex < p2->m_ullFileIndex)
  263. iRet = -1;
  264. else
  265. iRet = 1;
  266. }
  267. }
  268. else
  269. iRet = -1;
  270. }
  271. else
  272. {
  273. if (p2->m_fSkipped)
  274. iRet = 1;
  275. else
  276. {
  277. if (p1->m_cbSize < p2->m_cbSize)
  278. iRet = 1;
  279. else if (p1->m_cbSize > p2->m_cbSize)
  280. iRet = -1;
  281. else
  282. {
  283. if (p1->m_ullFileIndex < p2->m_ullFileIndex)
  284. iRet = -1;
  285. else
  286. iRet = 1;
  287. }
  288. }
  289. }
  290. return iRet;
  291. }
  292. private:
  293. CFileCopy(const CFileCopy &r);
  294. void operator =(const CFileCopy &r);
  295. };
  296. class CFileLink : public CFileBase
  297. {
  298. public:
  299. CFileLink() { }
  300. ~CFileLink() { }
  301. virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer) { return ::ProcessFileLink(this, rfReQueue); }
  302. BOOL GetSource(CBaseStringBuffer &rbuff);
  303. private:
  304. CFileLink(const CFileLink &r);
  305. void operator =(const CFileLink &r);
  306. };
  307. CDeque<CDir, offsetof(CDir, m_linkage)> *g_pDirs = NULL;
  308. CDir **g_prgpDirs = NULL;
  309. CDeque<CFileCopy, offsetof(CFileCopy, m_linkage)> *g_pFileCopies = NULL;
  310. CFileCopy **g_prgpFileCopies = NULL;
  311. CDeque<CFileLink, offsetof(CFileLink, m_linkage)> *g_pFileLinks = NULL;
  312. CFileLink **g_prgpFileLinks = NULL;
  313. class CFileIdHashHelper : public CHashTableHelper<ULONGLONG, ULONGLONG, PCWSTR, CStringBuffer>
  314. {
  315. private:
  316. CFileIdHashHelper(const CFileIdHashHelper&); // deliberately not implemented
  317. void operator=(const CFileIdHashHelper&); // deliberately not implemented
  318. public:
  319. static BOOL HashKey(ULONGLONG keyin, ULONG &rulPseudoKey) { rulPseudoKey = static_cast<ULONG>(keyin); return TRUE; }
  320. static BOOL CompareKey(ULONGLONG keyin, const ULONGLONG &rtkeystored, bool &rfMatch) { rfMatch = keyin == rtkeystored; return TRUE; }
  321. static VOID PreInitializeKey(ULONGLONG &rtkeystored) { rtkeystored = 0; }
  322. static VOID PreInitializeValue(CFileCopy *&rtvaluestored) { rtvaluestored = NULL; }
  323. static BOOL InitializeKey(ULONGLONG keyin, ULONGLONG &rtkeystored) { rtkeystored = keyin; return TRUE; }
  324. static BOOL InitializeValue(CFileCopy *vin, CFileCopy *&rvstored) { rvstored = vin; return TRUE; }
  325. static BOOL UpdateValue(CFileCopy *vin, CFileCopy *&rvstored) { rvstored = vin; return TRUE; }
  326. static VOID FinalizeKey(ULONGLONG &rtkeystored) { }
  327. static VOID FinalizeValue(CFileCopy *&rvstored) { rvstored = NULL; }
  328. };
  329. class CFileIdHashTable : public CHashTable<ULONGLONG, ULONGLONG, CFileCopy *, CFileCopy *, CFileIdHashHelper>
  330. {
  331. private:
  332. CFileIdHashTable(const CFileIdHashTable&); // deliberately not implemented
  333. void operator=(const CFileIdHashTable&); // deliberately not implemented
  334. public:
  335. CFileIdHashTable() { }
  336. ~CFileIdHashTable() { }
  337. };
  338. CFileIdHashTable *g_pFiles = NULL;
  339. FILE *g_pLogFile = NULL;
  340. BOOL
  341. CFileLink::GetSource(CBaseStringBuffer &rbuff)
  342. {
  343. BOOL fSuccess = FALSE;
  344. FN_TRACE_WIN32(fSuccess);
  345. CStringBuffer *pbuff = NULL;
  346. CFileCopy **ppFileCopy = NULL;
  347. if (!g_pFiles->Find(m_ullFileIndex, ppFileCopy))
  348. {
  349. ::ReportFailure("Finding file index %I64u in the file table failed.\n");
  350. goto Exit;
  351. }
  352. IFW32FALSE_EXIT((*ppFileCopy)->GetDestination(rbuff));
  353. FN_EPILOG
  354. }
  355. extern "C" int __cdecl wmain(int argc, wchar_t** argv)
  356. {
  357. int iReturnStatus = EXIT_FAILURE;
  358. SYSTEMTIME stStart, stAfterScan, stAfterDirCreation, stAfterCopies, stAfterLinks, stEnd;
  359. ULONG i;
  360. CStringBuffer buffSource;
  361. CStringBuffer buffDestination;
  362. int iSource = 0;
  363. int iDestination = 0;
  364. SYSTEMTIME stAfterScanDelta, stAfterDirCreationDelta, stAfterCopiesDelta, stAfterLinksDelta, stEndDelta;
  365. ULONGLONG ullTemp;
  366. int iArg;
  367. // DWORD dwRetVal;
  368. WCHAR rgwchSourceVolumePath[MAX_PATH];
  369. WCHAR rgwchSourceVolumeName[MAX_PATH];
  370. DWORD dwSourceVolumeSerialNumber;
  371. DWORD dwSourceMaximumComponentLength;
  372. DWORD dwSourceFileSystemFlags;
  373. WCHAR rgwchSourceFileSystemNameBuffer[MAX_PATH];
  374. WCHAR rgwchSourceRemoteName[MAX_PATH];
  375. DWORD dwSourceRemoteNameLength = NUMBER_OF(rgwchSourceRemoteName);
  376. UINT uiSourceDriveType;
  377. WCHAR rgwchDestinationVolumePath[MAX_PATH];
  378. WCHAR rgwchDestinationVolumeName[MAX_PATH];
  379. DWORD dwDestinationVolumeSerialNumber;
  380. DWORD dwDestinationMaximumComponentLength;
  381. DWORD dwDestinationFileSystemFlags;
  382. WCHAR rgwchDestinationFileSystemNameBuffer[MAX_PATH];
  383. WCHAR rgwchDestinationRemoteName[MAX_PATH];
  384. DWORD dwDestinationRemoteNameLength = NUMBER_OF(rgwchDestinationRemoteName);
  385. UINT uiDestinationDriveType;
  386. if (!::FusionpInitializeHeap(NULL))
  387. goto Exit;
  388. iArg = 1;
  389. while (iArg < argc)
  390. {
  391. PCWSTR arg = argv[iArg];
  392. // Let's see if we see some switches...
  393. if ((arg[0] == L'-') || (arg[0] == L'/'))
  394. {
  395. arg++;
  396. if ((_wcsicmp(arg, L"threads") == 0) ||
  397. (_wcsicmp(arg, L"t") == 0))
  398. {
  399. PWSTR pszDummy;
  400. iArg++;
  401. if (iArg >= argc)
  402. break;
  403. g_nThreads = wcstol(argv[iArg], &pszDummy, 10);
  404. if (g_nThreads < 1)
  405. g_nThreads = 1;
  406. if (g_nThreads > RTL_NUMBER_OF(g_rghThreads))
  407. g_nThreads = RTL_NUMBER_OF(g_rghThreads);
  408. iArg++;
  409. continue;
  410. }
  411. else if ((_wcsicmp(arg, L"quiet") == 0) ||
  412. (_wcsicmp(arg, L"q") == 0))
  413. {
  414. g_fSilent = true;
  415. iArg++;
  416. continue;
  417. }
  418. else if (_wcsicmp(arg, L"logfile") == 0)
  419. {
  420. iArg++;
  421. if (iArg >= argc)
  422. break;
  423. g_pLogFile = ::_wfopen(argv[iArg], L"w+");
  424. if (g_pLogFile == NULL)
  425. {
  426. ::perror("Error opening logfile");
  427. goto Exit;
  428. }
  429. iArg++;
  430. }
  431. }
  432. // This must be it! We hope; there should be two things left
  433. if ((iArg + 2) != argc)
  434. break;
  435. iSource = iArg;
  436. iDestination = iArg + 1;
  437. break;
  438. }
  439. if (iSource == 0)
  440. {
  441. fprintf(stderr,
  442. "%ls: usage:\n"
  443. " %ls [-threads n] [-quiet] <source> <dest>\n",
  444. argv[0], argv[0]);
  445. goto Exit;
  446. }
  447. // Abuse these buffers for debugging purposes...
  448. {
  449. HANDLE h;
  450. if (!::GetLogicalDriveStringsW(NUMBER_OF(rgwchSourceVolumePath), rgwchSourceVolumePath))
  451. {
  452. ::ReportFailure("GetLogicalDriveStringsW failed\n");
  453. goto Exit;
  454. }
  455. h = ::FindFirstVolumeW(rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath));
  456. if (h == INVALID_HANDLE_VALUE)
  457. {
  458. ::ReportFailure("FindFirstVolumeW failed\n");
  459. goto Exit;
  460. }
  461. for (;;)
  462. {
  463. DWORD cchReturnLength;
  464. if (!::GetVolumePathNamesForVolumeNameW(rgwchSourceVolumePath, rgwchDestinationVolumePath, NUMBER_OF(rgwchDestinationVolumePath), &cchReturnLength))
  465. {
  466. ::ReportFailure("GetVolumePathNamesForVolumeNameW failed\n");
  467. goto Exit;
  468. }
  469. if (!::FindNextVolumeW(h, rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath)))
  470. {
  471. const DWORD dwLastError = ::GetLastError();
  472. if (dwLastError != ERROR_NO_MORE_FILES)
  473. {
  474. ::ReportFailure("FindNextVolumeW failed\n");
  475. goto Exit;
  476. }
  477. break;
  478. }
  479. }
  480. ::FindVolumeClose(h);
  481. }
  482. if (!::GetVolumePathNameW(argv[iSource], rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath)))
  483. {
  484. ::ReportFailure("GetVolumePathName(L\"%ls\", ...) failed\n", argv[iSource]);
  485. goto Exit;
  486. }
  487. uiSourceDriveType = ::GetDriveTypeW(rgwchSourceVolumePath);
  488. if (!::GetVolumePathNameW(argv[iDestination], rgwchDestinationVolumePath, NUMBER_OF(rgwchDestinationVolumePath)))
  489. {
  490. ::ReportFailure("GetVolumePathName(L\"%ls\", ...) failed\n", argv[iDestination]);
  491. goto Exit;
  492. }
  493. uiDestinationDriveType = ::GetDriveTypeW(rgwchDestinationVolumePath);
  494. if (!::GetVolumeInformationW(
  495. rgwchSourceVolumePath,
  496. rgwchSourceVolumeName,
  497. NUMBER_OF(rgwchSourceVolumeName),
  498. &dwSourceVolumeSerialNumber,
  499. &dwSourceMaximumComponentLength,
  500. &dwSourceFileSystemFlags,
  501. rgwchSourceFileSystemNameBuffer,
  502. NUMBER_OF(rgwchSourceFileSystemNameBuffer)))
  503. {
  504. ::ReportFailure("GetVolumeInformation(L\"%ls\", ...) failed\n", rgwchSourceVolumePath);
  505. goto Exit;
  506. }
  507. if (!::GetVolumeInformationW(
  508. rgwchDestinationVolumePath,
  509. rgwchDestinationVolumeName,
  510. NUMBER_OF(rgwchDestinationVolumeName),
  511. &dwDestinationVolumeSerialNumber,
  512. &dwDestinationMaximumComponentLength,
  513. &dwDestinationFileSystemFlags,
  514. rgwchDestinationFileSystemNameBuffer,
  515. NUMBER_OF(rgwchDestinationFileSystemNameBuffer)))
  516. {
  517. ::ReportFailure("GetVolumeInformation(L\"%ls\", ...) failed\n", rgwchDestinationVolumePath);
  518. goto Exit;
  519. }
  520. if (!::GetDiskFreeSpaceW(
  521. rgwchDestinationVolumePath,
  522. &g_dwDestinationSectorsPerCluster,
  523. &g_dwDestinationBytesPerSector,
  524. &g_dwDestinationNumberOfFreeClusters,
  525. &g_dwDestinationTotalNumberOfClusters))
  526. {
  527. ::ReportFailure("GetDiskFreeSpaceW(L\"%ls\", ...) failed\n", rgwchDestinationVolumePath);
  528. goto Exit;
  529. }
  530. #if 0
  531. if ((dwRetVal = ::WNetGetConnectionW(rgwchSourceVolumePath, rgwchSourceRemoteName, &dwSourceRemoteNameLength)) != NO_ERROR)
  532. {
  533. ::SetLastError(dwRetVal);
  534. ::ReportFailure("WNetGetConnection(L\"%ls\", ...) failed\n", rgwchSourceVolumePath);
  535. goto Exit;
  536. }
  537. #endif // 0
  538. InitializeCriticalSection(&g_cs);
  539. g_pFiles = new CFileIdHashTable;
  540. g_pDirs = new CDeque<CDir, offsetof(CDir, m_linkage)>;
  541. g_pFileCopies = new CDeque<CFileCopy, offsetof(CFileCopy, m_linkage)>;
  542. g_pFileLinks = new CDeque<CFileLink, offsetof(CFileLink, m_linkage)>;
  543. g_pszImage = wcsrchr(argv[0], L'\\');
  544. if (g_pszImage == NULL)
  545. g_pszImage = argv[0];
  546. else
  547. g_pszImage++;
  548. g_hIoCompletionPort = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, g_nThreads);
  549. if (g_hIoCompletionPort == NULL)
  550. {
  551. ::ReportFailure("Creating I/O Completion Port with %lu concurrent threads failed.\n", g_nThreads);
  552. goto Exit;
  553. }
  554. g_hWorkItemDoneEvent = ::CreateEventW(NULL, FALSE, FALSE, NULL);
  555. if (g_hWorkItemDoneEvent == NULL)
  556. {
  557. ::ReportFailure("Creating the file copied event failed.\n");
  558. goto Exit;
  559. }
  560. for (i=0; i<g_nThreads; i++)
  561. {
  562. g_rghThreads[i] = ::CreateThread(NULL, 0, &WorkerThreadProc, NULL, CREATE_SUSPENDED, NULL);
  563. if (g_rghThreads[i] == NULL)
  564. {
  565. ::ReportFailure("Creating worker thread number %lu failed.\n", i);
  566. goto Exit;
  567. }
  568. }
  569. ::GetSystemTime(&stStart);
  570. if (!buffSource.Win32Assign(argv[iSource], wcslen(argv[iSource])))
  571. goto Exit;
  572. if (!buffDestination.Win32Assign(argv[iDestination], wcslen(argv[iDestination])))
  573. goto Exit;
  574. if (!::BuildDirList(buffSource, buffDestination))
  575. goto Exit;
  576. if (!g_fSilent)
  577. printf("%ls: Found %Iu directories.\n", g_pszImage, g_pDirs->GetEntryCount());
  578. if (!::QueueDirScans())
  579. goto Exit;
  580. if (!::WaitForWorkersToComplete(g_DirScansProcessed, g_pDirs->GetEntryCount(), "Directory scans"))
  581. goto Exit;
  582. ::GetSystemTime(&stAfterScan);
  583. if (!g_fSilent)
  584. {
  585. printf("%ls: Copying %Iu files (%I64u bytes)\n", g_pszImage, g_pFileCopies->GetEntryCount(), g_BytesToCopy);
  586. printf("%ls: Linking %Iu files\n", g_pszImage, g_pFileLinks->GetEntryCount());
  587. }
  588. if (!::MakeDirectoryStructure())
  589. goto Exit;
  590. if (!g_fSilent)
  591. printf("%ls: Created %Iu directories.\n", g_pszImage, g_pDirs->GetEntryCount());
  592. ::GetSystemTime(&stAfterDirCreation);
  593. if (!::QueueFileCopies())
  594. goto Exit;
  595. if (!::WaitForWorkersToComplete(g_FileCopiesProcessed, g_pFileCopies->GetEntryCount(), "File copies"))
  596. goto Exit;
  597. ::GetSystemTime(&stAfterCopies);
  598. ::qsort(g_prgpFileCopies, g_pFileCopies->GetEntryCount(), sizeof(CFileCopy *), &CFileCopy::QSortBySize);
  599. if (g_pLogFile != NULL)
  600. {
  601. LARGE_INTEGER liFreq;
  602. ULONGLONG ullFreqDiv100;
  603. CStringBuffer buffDestination;
  604. ::QueryPerformanceFrequency(&liFreq);
  605. ullFreqDiv100 = (liFreq.QuadPart / 100);
  606. for (i=0; i<g_pFileCopies->GetEntryCount(); i++)
  607. {
  608. ULONGLONG diff = g_prgpFileCopies[i]->m_ullEnd - g_prgpFileCopies[i]->m_ullStart;
  609. diff = diff / ullFreqDiv100;
  610. if (!g_prgpFileCopies[i]->GetDestination(buffDestination))
  611. goto Exit;
  612. fprintf(g_pLogFile, "%ls,%Iu,%Iu\n", static_cast<PCWSTR>(buffDestination), diff, g_prgpFileCopies[i]->m_cbSize);
  613. }
  614. }
  615. if (!::QueueFileLinks())
  616. goto Exit;
  617. if (!::WaitForWorkersToComplete(g_FileLinksProcessed, g_pFileLinks->GetEntryCount(), "File links"))
  618. goto Exit;
  619. ::GetSystemTime(&stAfterLinks);
  620. ::GetSystemTime(&stEnd);
  621. ::ComputeTimeDeltas(stStart, stAfterScan, stAfterScanDelta);
  622. ::ComputeTimeDeltas(stAfterScan, stAfterDirCreation, stAfterDirCreationDelta);
  623. ::ComputeTimeDeltas(stAfterDirCreation, stAfterCopies, stAfterCopiesDelta);
  624. ::ComputeTimeDeltas(stAfterCopies, stAfterLinks, stAfterLinksDelta);
  625. ::ComputeTimeDeltas(stStart, stEnd, stEndDelta);
  626. printf(
  627. "%ls: Statistics:\n"
  628. " Directories Copied: %I64u\n"
  629. " Files Copied: %I64u\n"
  630. " Bytes Copied: %I64u\n"
  631. " Files Linked: %I64u\n"
  632. " Copies Skipped: %I64u\n"
  633. " Links Skipped: %I64u\n",
  634. g_pszImage,
  635. g_DirectoriesCopied,
  636. g_FilesCopied,
  637. g_BytesCopied,
  638. g_FilesLinked,
  639. g_CopiesSkipped,
  640. g_LinksSkipped);
  641. printf(
  642. " Times:\n"
  643. " Scan: %u:%02u:%02u.%03u\n"
  644. " Directory Creation: %u:%02u:%02u.%03u\n"
  645. " Copying Files: %u:%02u:%02u.%03u\n"
  646. " Linking Files: %u:%02u:%02u.%03u\n"
  647. " Total: %u:%02u:%02u.%03u\n",
  648. stAfterScanDelta.wHour, stAfterScanDelta.wMinute, stAfterScanDelta.wSecond, stAfterScanDelta.wMilliseconds,
  649. stAfterDirCreationDelta.wHour, stAfterDirCreationDelta.wMinute, stAfterDirCreationDelta.wSecond, stAfterDirCreationDelta.wMilliseconds,
  650. stAfterCopiesDelta.wHour, stAfterCopiesDelta.wMinute, stAfterCopiesDelta.wSecond, stAfterCopiesDelta.wMilliseconds,
  651. stAfterLinksDelta.wHour, stAfterLinksDelta.wMinute, stAfterLinksDelta.wSecond, stAfterLinksDelta.wMilliseconds,
  652. stEndDelta.wHour, stEndDelta.wMinute, stEndDelta.wSecond, stEndDelta.wMilliseconds
  653. );
  654. ullTemp = (((((stAfterCopiesDelta.wHour * 60) + stAfterCopiesDelta.wMinute) * 60) + stAfterCopiesDelta.wSecond) * 1000) + stAfterCopiesDelta.wMilliseconds;
  655. if (ullTemp != 0)
  656. {
  657. ULONGLONG ullFilesPerMS = ((g_FilesCopied * 1000000ui64) / ullTemp);
  658. ULONGLONG ullBytesPerMS = ((g_BytesCopied * 1000000ui64) / ullTemp);
  659. printf(
  660. " Files copied per second: %I64u.%03u\n"
  661. " Bytes copied per second: %I64u.%03u\n",
  662. static_cast<ULONGLONG>(ullFilesPerMS / 1000ui64), static_cast<ULONG>(ullFilesPerMS % 1000ui64),
  663. static_cast<ULONGLONG>(ullBytesPerMS / 1000ui64), static_cast<ULONG>(ullBytesPerMS % 1000ui64));
  664. #if 0
  665. static_cast<ULONGLONG>((g_FilesCopied / ullTemp) * 1000ui64), static_cast<unsigned int>((g_FilesCopied / ullTemp) % 1000),
  666. static_cast<ULONGLONG>((g_BytesCopied / ullTemp) * 1000ui64), static_cast<unsigned int>((g_BytesCopied / ullTemp) % 1000));
  667. #endif
  668. }
  669. ullTemp = (((stAfterLinksDelta.wHour * 60) + stAfterLinksDelta.wMinute) * 60) + stAfterLinksDelta.wSecond;
  670. if (ullTemp != 0)
  671. {
  672. printf(
  673. " Files linked per second: %I64u\n",
  674. static_cast<ULONGLONG>(g_FilesLinked / ullTemp));
  675. }
  676. ullTemp = (((stEndDelta.wHour * 60) + stEndDelta.wMinute) * 60) + stEndDelta.wSecond;
  677. if (ullTemp != 0)
  678. {
  679. printf(
  680. " Overall files per second: %I64u\n",
  681. static_cast<ULONGLONG>((g_FilesCopied + g_CopiesSkipped + g_FilesLinked + g_LinksSkipped + g_pDirs->GetEntryCount()) / ullTemp));
  682. }
  683. iReturnStatus = EXIT_SUCCESS;
  684. Exit:
  685. // Wake the children; process termination doesn't seem to work if we don't.
  686. ::ResumeWorkerThreads();
  687. if (g_pLogFile != NULL)
  688. {
  689. fflush(g_pLogFile);
  690. fclose(g_pLogFile);
  691. }
  692. return iReturnStatus;
  693. }
  694. BOOL
  695. ResumeWorkerThreads()
  696. {
  697. BOOL fSuccess = FALSE;
  698. ULONG i;
  699. for (i=0; i<g_nThreads; i++)
  700. {
  701. if (::ResumeThread(g_rghThreads[i]) == -1)
  702. {
  703. ::ReportFailure("Failed to resume worker thread %lu\n", i + 1);
  704. goto Exit;
  705. }
  706. }
  707. fSuccess = TRUE;
  708. Exit:
  709. return fSuccess;
  710. }
  711. BOOL
  712. SuspendWorkerThreads()
  713. {
  714. BOOL fSuccess = FALSE;
  715. ULONG i;
  716. for (i=0; i<g_nThreads; i++)
  717. {
  718. if (::SuspendThread(g_rghThreads[i]) == -1)
  719. {
  720. ::ReportFailure("Failed to suspend worker thread %lu\n", i + 1);
  721. goto Exit;
  722. }
  723. }
  724. fSuccess = TRUE;
  725. Exit:
  726. return fSuccess;
  727. }
  728. BOOL
  729. WaitForWorkersToComplete(
  730. ULONGLONG &rullCounter,
  731. ULONGLONG ullLimit,
  732. PCSTR pszOperationName
  733. )
  734. {
  735. BOOL fSuccess = FALSE;
  736. ULONGLONG i;
  737. if (!::ResumeWorkerThreads())
  738. goto Exit;
  739. i = 0;
  740. while (rullCounter < ullLimit)
  741. {
  742. // Don't wake more than every tenth of a second...
  743. ::Sleep(100);
  744. DWORD dwWFSO = ::WaitForSingleObject(g_hWorkItemDoneEvent, INFINITE);
  745. if (dwWFSO == WAIT_FAILED)
  746. {
  747. ::ReportFailure("Waiting for work item done event failed\n");
  748. goto Exit;
  749. }
  750. i++;
  751. if ((i % 50) == 0)
  752. {
  753. if (!g_fSilent)
  754. printf("%ls: %s processed (%I64u total): %I64u\n", g_pszImage, pszOperationName, ullLimit, rullCounter);
  755. }
  756. }
  757. if (!::SuspendWorkerThreads())
  758. goto Exit;
  759. fSuccess = TRUE;
  760. Exit:
  761. return fSuccess;
  762. }
  763. void
  764. ComputeTimeDeltas(
  765. const SYSTEMTIME &rstStart,
  766. const SYSTEMTIME &rstEnd,
  767. SYSTEMTIME &rstDelta
  768. )
  769. {
  770. FILETIME ftStart, ftEnd;
  771. ULARGE_INTEGER uliStart, uliEnd;
  772. ULONGLONG ullDiff, ullTemp;
  773. ULONG ulHours, ulMinutes, ulSeconds, ulMilliseconds;
  774. ::SystemTimeToFileTime(&rstStart, &ftStart);
  775. ::SystemTimeToFileTime(&rstEnd, &ftEnd);
  776. uliStart.LowPart = ftStart.dwLowDateTime;
  777. uliStart.HighPart = ftStart.dwHighDateTime;
  778. uliEnd.LowPart = ftEnd.dwLowDateTime;
  779. uliEnd.HighPart = ftEnd.dwHighDateTime;
  780. ullDiff = (uliEnd.QuadPart - uliStart.QuadPart);
  781. ulHours = (ULONG) (ullDiff / (10000000ui64 * 60 * 60));
  782. ullTemp = ullDiff - ((ULONGLONG) ulHours) * (10000000ui64 * 60 * 60);
  783. ulMinutes = (ULONG) (ullTemp / (10000000ui64 * 60));
  784. ullTemp -= ((ULONGLONG) ulMinutes) * (10000000ui64 * 60);
  785. ulSeconds = (ULONG) (ullTemp / 10000000ui64);
  786. ullTemp -= ((ULONGLONG) ulSeconds) * 10000000ui64;
  787. ulMilliseconds = (ULONG) (ullTemp / 10000ui64);
  788. rstDelta.wYear = 0;
  789. rstDelta.wMonth = 0;
  790. rstDelta.wDayOfWeek = 0;
  791. rstDelta.wDay = 0;
  792. rstDelta.wHour = (WORD) ulHours;
  793. rstDelta.wMinute = (WORD) ulMinutes;
  794. rstDelta.wSecond = (WORD) ulSeconds;
  795. rstDelta.wMilliseconds = (WORD) ulMilliseconds;
  796. }
  797. BOOL
  798. BuildDirList(
  799. const CBaseStringBuffer &rbuffSource,
  800. const CBaseStringBuffer &rbuffDestination
  801. )
  802. {
  803. PCWSTR szSrc = rbuffSource;
  804. BOOL fSuccess = FALSE;
  805. DWORD dwFileAttributes;
  806. CStringBuffer buffSrc;
  807. CStringBuffer buffSrcWildcard;
  808. CStringBuffer buffDst;
  809. WIN32_FIND_DATAW wfd;
  810. HANDLE hFind = INVALID_HANDLE_VALUE;
  811. HANDLE hFile = INVALID_HANDLE_VALUE;
  812. SIZE_T cchSrc, cchDst;
  813. CDir *pDir = NULL;
  814. dwFileAttributes = ::GetFileAttributesW(rbuffSource);
  815. if (dwFileAttributes == ((DWORD) -1))
  816. {
  817. ::ReportFailure("GetFileAttributesW() on the source \"%ls\" failed\n", szSrc);
  818. goto Exit;
  819. }
  820. if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  821. {
  822. ::SetLastError(ERROR_INVALID_PARAMETER);
  823. ::ReportFailure("Source directory \"%ls\" is not a directory!\n");
  824. goto Exit;
  825. }
  826. if (!buffSrc.Win32Assign(rbuffSource))
  827. goto Exit;
  828. if (!buffSrc.Win32EnsureTrailingPathSeparator())
  829. goto Exit;
  830. if (!buffDst.Win32Assign(rbuffDestination))
  831. goto Exit;
  832. if (!buffDst.Win32EnsureTrailingPathSeparator())
  833. goto Exit;
  834. cchSrc = buffSrc.Cch();
  835. cchDst = buffDst.Cch();
  836. pDir = new CDir;
  837. if (pDir == NULL)
  838. {
  839. ::SetLastError(ERROR_OUTOFMEMORY);
  840. ::ReportFailure("Failed to allocate new CDir object\n");
  841. goto Exit;
  842. }
  843. if (!pDir->Initialize(buffSrc, buffDst))
  844. goto Exit;
  845. g_pDirs->AddToTail(pDir);
  846. pDir = NULL;
  847. if ((g_pDirs->GetEntryCount() % 50) == 0)
  848. {
  849. if (!g_fSilent)
  850. printf("%ls: Found %Iu directories...\n", g_pszImage, g_pDirs->GetEntryCount());
  851. }
  852. if (!buffSrcWildcard.Win32Assign(buffSrc))
  853. goto Exit;
  854. if (!buffSrcWildcard.Win32Append(L"*", 1))
  855. goto Exit;
  856. if ((hFind = ::FindFirstFileExW(
  857. buffSrcWildcard,
  858. FindExInfoStandard,
  859. &wfd,
  860. FindExSearchLimitToDirectories,
  861. NULL,
  862. 0)) == INVALID_HANDLE_VALUE)
  863. {
  864. const DWORD dwLastError = ::GetLastError();
  865. if (dwLastError != ERROR_NO_MORE_FILES)
  866. {
  867. ::ReportFailure("FindFirstFileW(L\"%ls\", ...) failed.\n", static_cast<PCWSTR>(buffSrcWildcard));
  868. goto Exit;
  869. }
  870. }
  871. for (;;)
  872. {
  873. if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  874. (wcscmp(wfd.cFileName, L".") != 0) &&
  875. (wcscmp(wfd.cFileName, L"..") != 0))
  876. {
  877. buffSrc.Left(cchSrc);
  878. buffDst.Left(cchDst);
  879. if (!buffSrc.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
  880. goto Exit;
  881. if (!buffDst.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
  882. goto Exit;
  883. if (!::BuildDirList(buffSrc, buffDst))
  884. goto Exit;
  885. }
  886. if (!::FindNextFileW(hFind, &wfd))
  887. {
  888. if (::GetLastError() == ERROR_NO_MORE_FILES)
  889. break;
  890. ::ReportFailure("FindNextFileW() failed\n");
  891. goto Exit;
  892. }
  893. }
  894. fSuccess = TRUE;
  895. Exit:
  896. if (hFind != INVALID_HANDLE_VALUE)
  897. {
  898. CSxsPreserveLastError ple;
  899. ::FindClose(hFind);
  900. ple.Restore();
  901. }
  902. if (pDir != NULL)
  903. delete pDir;
  904. return fSuccess;
  905. }
  906. BOOL
  907. BuildDirFileList(
  908. CSxsLockCriticalSection &rlcs,
  909. CDir *pDir
  910. )
  911. {
  912. PCWSTR szSrc = pDir->m_buffSource;
  913. BOOL fSuccess = FALSE;
  914. DWORD dwFileAttributes;
  915. CStringBuffer buffSrc;
  916. CStringBuffer buffSrcWildcard;
  917. CStringBuffer buffDst;
  918. WIN32_FIND_DATAW wfd;
  919. HANDLE hFind = INVALID_HANDLE_VALUE;
  920. HANDLE hFile = INVALID_HANDLE_VALUE;
  921. SIZE_T cchSrc, cchDst;
  922. CFileLink *pFileLink = NULL;
  923. CFileCopy *pFileCopy = NULL;
  924. dwFileAttributes = ::GetFileAttributesW(pDir->m_buffSource);
  925. if (dwFileAttributes == ((DWORD) -1))
  926. {
  927. ::ReportFailure("GetFileAttributesW() on the source \"%ls\" failed", szSrc);
  928. goto Exit;
  929. }
  930. if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  931. {
  932. ::SetLastError(ERROR_INVALID_PARAMETER);
  933. ::ReportFailure("Source directory \"%ls\" is not a directory!\n", szSrc);
  934. goto Exit;
  935. }
  936. if (!buffSrc.Win32Assign(pDir->m_buffSource))
  937. goto Exit;
  938. if (!buffSrc.Win32EnsureTrailingPathSeparator())
  939. goto Exit;
  940. if (!buffDst.Win32Assign(pDir->m_buffDestination))
  941. goto Exit;
  942. if (!buffDst.Win32EnsureTrailingPathSeparator())
  943. goto Exit;
  944. cchSrc = buffSrc.Cch();
  945. cchDst = buffDst.Cch();
  946. if (!buffSrcWildcard.Win32Assign(buffSrc))
  947. goto Exit;
  948. if (!buffSrcWildcard.Win32Append(L"*", 1))
  949. goto Exit;
  950. if ((hFind = ::FindFirstFileW(buffSrcWildcard, &wfd)) == INVALID_HANDLE_VALUE)
  951. {
  952. const DWORD dwLastError = ::GetLastError();
  953. if (::GetLastError() != ERROR_NO_MORE_FILES)
  954. {
  955. ::ReportFailure("FindFirstFileW(L\"%ls\", ...) failed\n", static_cast<PCWSTR>(buffSrcWildcard));
  956. goto Exit;
  957. }
  958. }
  959. for (;;)
  960. {
  961. if ((wcscmp(wfd.cFileName, L".") != 0) && (wcscmp(wfd.cFileName, L"..") != 0))
  962. {
  963. buffSrc.Left(cchSrc);
  964. buffDst.Left(cchDst);
  965. if (!buffSrc.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
  966. goto Exit;
  967. if (!buffDst.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
  968. goto Exit;
  969. dwFileAttributes = ::GetFileAttributesW(buffSrc);
  970. if (dwFileAttributes == ((DWORD) -1))
  971. {
  972. ::ReportFailure("GetFileAttribuesW(L\"%ls\") failed\n", static_cast<PCWSTR>(buffSrc));
  973. goto Exit;
  974. }
  975. if ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
  976. {
  977. BY_HANDLE_FILE_INFORMATION bhfi;
  978. ULONGLONG ullFileIndex;
  979. ULONGLONG ullFileSize;
  980. CFileCopy **ppFileCopy = NULL;
  981. hFile = ::CreateFileW(buffSrc, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  982. if (hFile == INVALID_HANDLE_VALUE)
  983. {
  984. ::ReportFailure("CreateFileW(L\"%ls\", ...) failed\n", static_cast<PCWSTR>(buffSrc));
  985. goto Exit;
  986. }
  987. if (!::GetFileInformationByHandle(hFile, &bhfi))
  988. {
  989. ::ReportFailure("Failed to GetFileInformationByHandle(%p, ...) on file \"%ls\"\n", hFile, static_cast<PCWSTR>(buffSrc));
  990. goto Exit;
  991. }
  992. ::CloseHandle(hFile);
  993. hFile = INVALID_HANDLE_VALUE;
  994. ullFileIndex = (((ULONGLONG) bhfi.nFileIndexHigh) << 32) | ((ULONGLONG) bhfi.nFileIndexLow);
  995. ullFileSize = (((ULONGLONG) bhfi.nFileSizeHigh) << 32) | ((ULONGLONG) bhfi.nFileSizeLow);
  996. g_BytesToCopy += ullFileSize;
  997. if (!rlcs.Lock())
  998. {
  999. ::ReportFailure("Failed to lock global critical section");
  1000. goto Exit;
  1001. }
  1002. if (!g_pFiles->Find(ullFileIndex, ppFileCopy))
  1003. {
  1004. ::ReportFailure("Finding file index %I64u in file table failed.\n", ullFileIndex);
  1005. goto Exit;
  1006. }
  1007. if (ppFileCopy != NULL)
  1008. {
  1009. pFileLink = new CFileLink;
  1010. if (pFileLink == NULL)
  1011. {
  1012. ::SetLastError(ERROR_OUTOFMEMORY);
  1013. goto Exit;
  1014. }
  1015. if (!pFileLink->Initialize(
  1016. pDir,
  1017. wfd.cFileName,
  1018. bhfi.ftCreationTime,
  1019. bhfi.ftLastAccessTime,
  1020. bhfi.ftLastWriteTime,
  1021. ullFileIndex,
  1022. ullFileSize))
  1023. goto Exit;
  1024. g_pFileLinks->AddToTail(pFileLink);
  1025. pFileLink = NULL;
  1026. }
  1027. else
  1028. {
  1029. pFileCopy = new CFileCopy;
  1030. if (pFileCopy == NULL)
  1031. {
  1032. ::SetLastError(ERROR_OUTOFMEMORY);
  1033. ::ReportFailure("Allocating new CFileCopy object failed.\n");
  1034. goto Exit;
  1035. }
  1036. if (!pFileCopy->Initialize(
  1037. pDir,
  1038. wfd.cFileName,
  1039. bhfi.ftCreationTime,
  1040. bhfi.ftLastAccessTime,
  1041. bhfi.ftLastWriteTime,
  1042. ullFileIndex,
  1043. ullFileSize))
  1044. goto Exit;
  1045. if (!g_pFiles->Insert(ullFileIndex, pFileCopy))
  1046. goto Exit;
  1047. g_pFileCopies->AddToTail(pFileCopy);
  1048. pFileCopy = NULL;
  1049. }
  1050. rlcs.Unlock();
  1051. }
  1052. }
  1053. if (!::FindNextFileW(hFind, &wfd))
  1054. {
  1055. const DWORD dwLastError = ::GetLastError();
  1056. if (dwLastError == ERROR_NO_MORE_FILES)
  1057. break;
  1058. ::ReportFailure("FindNextFileW() call failed\n");
  1059. goto Exit;
  1060. }
  1061. }
  1062. fSuccess = TRUE;
  1063. Exit:
  1064. if (hFind != INVALID_HANDLE_VALUE)
  1065. {
  1066. CSxsPreserveLastError ple;
  1067. ::FindClose(hFind);
  1068. ple.Restore();
  1069. }
  1070. if (pFileLink != NULL)
  1071. delete pFileLink;
  1072. if (pFileCopy != NULL)
  1073. delete pFileCopy;
  1074. return fSuccess;
  1075. }
  1076. BOOL
  1077. MakeDirectoryStructure()
  1078. {
  1079. BOOL fSuccess = FALSE;
  1080. CDequeIterator<CDir, offsetof(CDir, m_linkage)> dirIter;
  1081. dirIter.Rebind(g_pDirs);
  1082. for (dirIter.Reset(); dirIter.More(); dirIter.Next())
  1083. {
  1084. if (!::CreateDirectoryW(dirIter->m_buffDestination, NULL))
  1085. {
  1086. const DWORD dwLastError = ::GetLastError();
  1087. if (dwLastError != ERROR_ALREADY_EXISTS)
  1088. {
  1089. ::ReportFailure("Unable to create directory \"%ls\"", static_cast<PCWSTR>(dirIter->m_buffDestination));
  1090. goto Exit;
  1091. }
  1092. }
  1093. }
  1094. fSuccess = TRUE;
  1095. Exit:
  1096. return fSuccess;
  1097. }
  1098. void
  1099. ReportFailure(
  1100. const char szFormat[],
  1101. ...
  1102. )
  1103. {
  1104. const DWORD dwLastError = ::GetLastError();
  1105. va_list ap;
  1106. char rgchBuffer[4096];
  1107. WCHAR rgchWin32Error[4096];
  1108. va_start(ap, szFormat);
  1109. _vsnprintf(rgchBuffer, sizeof(rgchBuffer) / sizeof(rgchBuffer[0]), szFormat, ap);
  1110. va_end(ap);
  1111. if (!::FormatMessageW(
  1112. FORMAT_MESSAGE_FROM_SYSTEM,
  1113. NULL,
  1114. dwLastError,
  1115. 0,
  1116. rgchWin32Error,
  1117. NUMBER_OF(rgchWin32Error),
  1118. &ap))
  1119. {
  1120. const DWORD dwLastError2 = ::GetLastError();
  1121. _snwprintf(rgchWin32Error, sizeof(rgchWin32Error) / sizeof(rgchWin32Error[0]), L"Error formatting Win32 error %lu\nError from FormatMessage is %lu", dwLastError, dwLastError2);
  1122. }
  1123. fprintf(stderr, "%ls: %s\n%ls\n", g_pszImage, rgchBuffer, rgchWin32Error);
  1124. }
  1125. DWORD
  1126. WINAPI
  1127. WorkerThreadProc(
  1128. LPVOID pvParameter
  1129. )
  1130. {
  1131. ULONG nThread = (ULONG)(ULONG_PTR)pvParameter;
  1132. DWORD dwReturnValue = 0;
  1133. BYTE *pBuffer = NULL;
  1134. DWORD cbBuffer;
  1135. cbBuffer = 8192 * 32;
  1136. pBuffer = (BYTE *) ::VirtualAlloc(NULL, cbBuffer, MEM_COMMIT, PAGE_READWRITE);
  1137. if (pBuffer == NULL)
  1138. {
  1139. ReportFailure("VirtualAlloc() for thread %lu failed.\n", nThread);
  1140. goto Exit;
  1141. }
  1142. for (;;)
  1143. {
  1144. DWORD nBytes = 0;
  1145. ULONG_PTR ulpCompletionKey = 0;
  1146. LPOVERLAPPED lpo = NULL;
  1147. CEntry *pEntry = NULL;
  1148. bool fReQueue = false;
  1149. if (!::GetQueuedCompletionStatus(g_hIoCompletionPort, &nBytes, &ulpCompletionKey, &lpo, INFINITE))
  1150. {
  1151. ReportFailure("Thread %lu failed call to GetQueuedCompletionStatus()\n", nThread);
  1152. dwReturnValue = ::GetLastError();
  1153. goto Exit;
  1154. }
  1155. pEntry = (CEntry *) lpo;
  1156. DoItAgain:
  1157. if (!pEntry->BaseDoYourThing(fReQueue, pBuffer, cbBuffer))
  1158. goto Exit;
  1159. if (fReQueue)
  1160. {
  1161. if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, NULL, lpo))
  1162. {
  1163. ReportFailure("Thread %lu failed to requeue item; retrying directly.\n", nThread);
  1164. goto DoItAgain;
  1165. }
  1166. }
  1167. ::SetEvent(g_hWorkItemDoneEvent);
  1168. }
  1169. Exit:
  1170. // Heavy handed but what else can we do?
  1171. ::ExitProcess(dwReturnValue);
  1172. return dwReturnValue;
  1173. }
  1174. BOOL
  1175. QueueDirScans()
  1176. {
  1177. BOOL fSuccess = FALSE;
  1178. SIZE_T i;
  1179. g_prgpDirs = new (CDir *[g_pDirs->GetEntryCount()]);
  1180. CDequeIterator<CDir, offsetof(CDir, m_linkage)> dirIter;
  1181. dirIter.Rebind(g_pDirs);
  1182. for (dirIter.Reset(), i=0; dirIter.More(); dirIter.Next(), i++)
  1183. {
  1184. g_prgpDirs[i] = dirIter;
  1185. if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) dirIter.Current()))
  1186. {
  1187. ReportFailure("Failed to queue dir scan\n");
  1188. goto Exit;
  1189. }
  1190. }
  1191. fSuccess = TRUE;
  1192. Exit:
  1193. return fSuccess;
  1194. }
  1195. BOOL
  1196. QueueFileCopies()
  1197. {
  1198. BOOL fSuccess = FALSE;
  1199. SIZE_T i;
  1200. FN_TRACE_WIN32(fSuccess);
  1201. CDequeIterator<CFileCopy, offsetof(CFileCopy, m_linkage)> fileIter;
  1202. g_prgpFileCopies = new (CFileCopy *[g_pFileCopies->GetEntryCount()]);
  1203. fileIter.Rebind(g_pFileCopies);
  1204. i=0;
  1205. for (fileIter.Reset(); fileIter.More(); fileIter.Next())
  1206. {
  1207. if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) fileIter.Current()))
  1208. {
  1209. ReportFailure("Failed to queue file copy\n");
  1210. goto Exit;
  1211. }
  1212. g_prgpFileCopies[i++] = fileIter;
  1213. }
  1214. fSuccess = TRUE;
  1215. Exit:
  1216. return fSuccess;
  1217. }
  1218. BOOL
  1219. QueueFileLinks()
  1220. {
  1221. BOOL fSuccess = FALSE;
  1222. SIZE_T i;
  1223. CDequeIterator<CFileLink, offsetof(CFileLink, m_linkage)> fileIter;
  1224. g_prgpFileLinks = new (CFileLink *[g_pFileLinks->GetEntryCount()]);
  1225. fileIter.Rebind(g_pFileLinks);
  1226. i=0;
  1227. for (fileIter.Reset(); fileIter.More(); fileIter.Next())
  1228. {
  1229. if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) fileIter.Current()))
  1230. {
  1231. ReportFailure("Failed to queue file link\n");
  1232. goto Exit;
  1233. }
  1234. g_prgpFileLinks[i++] = fileIter;
  1235. }
  1236. fSuccess = TRUE;
  1237. Exit:
  1238. return fSuccess;
  1239. }
  1240. BOOL
  1241. ProcessDirScan(
  1242. CDir *pDir,
  1243. bool &rfReQueue
  1244. )
  1245. {
  1246. BOOL fSuccess = FALSE;
  1247. CSxsLockCriticalSection l(g_cs);
  1248. rfReQueue = false;
  1249. if (!::BuildDirFileList(l, pDir))
  1250. goto Exit;
  1251. if (!l.Lock())
  1252. {
  1253. ReportFailure("Failed to lock global critical section");
  1254. goto Exit;
  1255. }
  1256. g_DirScansProcessed++;
  1257. l.Unlock();
  1258. fSuccess = TRUE;
  1259. Exit:
  1260. return fSuccess;
  1261. }
  1262. BOOL
  1263. ProcessFileCopy(
  1264. CFileCopy *pFile,
  1265. bool &rfReQueue,
  1266. BYTE *pBuffer,
  1267. DWORD cbBuffer
  1268. )
  1269. {
  1270. BOOL fSuccess = FALSE;
  1271. FN_TRACE_WIN32(fSuccess);
  1272. CSxsLockCriticalSection l(g_cs);
  1273. bool fDoCopy = true;
  1274. DWORD dwFileAttributes;
  1275. HANDLE hFile = INVALID_HANDLE_VALUE;
  1276. CStringBuffer buffSource, buffDestination;
  1277. rfReQueue = false;
  1278. // If we're about to copy it, it's worth taking a look at the target to see if it's more-or-less
  1279. // out of date.
  1280. WIN32_FILE_ATTRIBUTE_DATA wfad;
  1281. IFW32FALSE_EXIT(pFile->GetSource(buffSource));
  1282. IFW32FALSE_EXIT(pFile->GetDestination(buffDestination));
  1283. if (!::GetFileAttributesExW(buffDestination, GetFileExInfoStandard, &wfad))
  1284. {
  1285. const DWORD dwLastError = ::GetLastError();
  1286. if (dwLastError != ERROR_FILE_NOT_FOUND)
  1287. {
  1288. ::ReportFailure("Error opening target file \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
  1289. goto Exit;
  1290. }
  1291. wfad.dwFileAttributes = ((DWORD) -1);
  1292. }
  1293. else
  1294. {
  1295. ULONGLONG cbFileSize = (wfad.nFileSizeHigh << 32) | wfad.nFileSizeLow;
  1296. ASSERT_NTC(cbFileSize >= wfad.nFileSizeLow);
  1297. if ((cbFileSize == pFile->m_cbSize) &&
  1298. (wfad.ftCreationTime == pFile->m_ftSourceCreationTime))
  1299. {
  1300. pFile->m_fSkipped = true;
  1301. fDoCopy = false;
  1302. }
  1303. }
  1304. if (fDoCopy)
  1305. {
  1306. bool fSetTimestamp = true;
  1307. bool fClearedReadOnly = false;
  1308. // Destructively clear the read-only bit if th destination file exists and is read-only.
  1309. dwFileAttributes = wfad.dwFileAttributes;
  1310. // We already filtered out all other reasons for failure other than FILE_NOT_FOUND.
  1311. if (dwFileAttributes != ((DWORD) -1))
  1312. {
  1313. if (dwFileAttributes & FILE_ATTRIBUTE_READONLY)
  1314. {
  1315. // If it's readonly, clear that bit as well as all the others that
  1316. // are illegal to set via SetFileAttributes()
  1317. dwFileAttributes &=
  1318. ~(FILE_ATTRIBUTE_READONLY |
  1319. FILE_ATTRIBUTE_COMPRESSED |
  1320. FILE_ATTRIBUTE_DEVICE |
  1321. FILE_ATTRIBUTE_DIRECTORY |
  1322. FILE_ATTRIBUTE_ENCRYPTED |
  1323. FILE_ATTRIBUTE_REPARSE_POINT |
  1324. FILE_ATTRIBUTE_SPARSE_FILE);
  1325. if (!::SetFileAttributesW(buffDestination, dwFileAttributes))
  1326. {
  1327. ::ReportFailure("Error setting file attributes for target file \"%ls\" to 0x%08lx to allow overwrite\n", static_cast<PCWSTR>(buffDestination), dwFileAttributes);
  1328. goto Exit;
  1329. }
  1330. }
  1331. }
  1332. if (g_fAnnounceCopies)
  1333. printf("Copying \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
  1334. // Hey, it's not there. Let's copy it and put the entry in the table.
  1335. if (!::MyCopyFile(pBuffer, cbBuffer, buffSource, buffDestination, FALSE, hFile))
  1336. {
  1337. ::ReportFailure("Failed to copy \"%ls\" to \"%ls\".\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
  1338. goto Exit;
  1339. }
  1340. if (fSetTimestamp)
  1341. {
  1342. if (!::SetFileTime(hFile, &pFile->m_ftSourceCreationTime, &pFile->m_ftSourceLastAccessTime, &pFile->m_ftSourceLastWriteTime))
  1343. {
  1344. ::ReportFailure("Failed call to SetFileTime on file \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
  1345. goto Exit;
  1346. }
  1347. ::CloseHandle(hFile);
  1348. hFile = INVALID_HANDLE_VALUE;
  1349. }
  1350. if (!l.Lock())
  1351. {
  1352. ::ReportFailure("Failed to lock global critical section\n");
  1353. goto Exit;
  1354. }
  1355. g_FilesCopied++;
  1356. g_FileCopiesProcessed++;
  1357. g_BytesCopied += pFile->m_cbSize;
  1358. l.Unlock();
  1359. }
  1360. else
  1361. {
  1362. if (g_fAnnounceSkips)
  1363. ::printf("Skipping copy from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
  1364. if (!l.Lock())
  1365. {
  1366. ::ReportFailure("Failed to lock global critical section\n");
  1367. goto Exit;
  1368. }
  1369. g_CopiesSkipped++;
  1370. g_FileCopiesProcessed++;
  1371. l.Unlock();
  1372. }
  1373. fSuccess = TRUE;
  1374. Exit:
  1375. if (hFile != INVALID_HANDLE_VALUE)
  1376. {
  1377. CSxsPreserveLastError ple;
  1378. ::CloseHandle(hFile);
  1379. ple.Restore();
  1380. }
  1381. if (::GetLastError() == ERROR_TOO_MANY_OPEN_FILES)
  1382. {
  1383. rfReQueue = true;
  1384. fSuccess = TRUE;
  1385. }
  1386. return fSuccess;
  1387. }
  1388. BOOL
  1389. ProcessFileLink(
  1390. CFileLink *pFile,
  1391. bool &rfReQueue
  1392. )
  1393. {
  1394. BOOL fSuccess = FALSE;
  1395. FN_TRACE_WIN32(fSuccess);
  1396. CSxsLockCriticalSection l(g_cs);
  1397. bool fDoLink = true;
  1398. CStringBuffer buffSource, buffDestination;
  1399. WIN32_FILE_ATTRIBUTE_DATA wfad;
  1400. rfReQueue = false;
  1401. IFW32FALSE_EXIT(pFile->GetSource(buffSource));
  1402. IFW32FALSE_EXIT(pFile->GetDestination(buffDestination));
  1403. // CreateHardLinkW() doesn't deal with replace-existing like copyfile, so we'll see if a
  1404. // file by that name is already present and if so, delete it.
  1405. if (!::GetFileAttributesExW(buffDestination, GetFileExInfoStandard, &wfad))
  1406. {
  1407. // It failed. But did it fail for the right reason?
  1408. DWORD dwLastError = ::GetLastError();
  1409. if (dwLastError != ERROR_FILE_NOT_FOUND)
  1410. {
  1411. ::ReportFailure("Error probing destination for \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
  1412. goto Exit;
  1413. }
  1414. }
  1415. else
  1416. {
  1417. ULONGLONG cbFileSize = (wfad.nFileSizeHigh << 32) | wfad.nFileSizeLow;
  1418. ASSERT_NTC(cbFileSize >= wfad.nFileSizeLow);
  1419. if ((pFile->m_cbSize == cbFileSize) &&
  1420. (pFile->m_ftSourceCreationTime == wfad.ftCreationTime))
  1421. {
  1422. fDoLink = false;
  1423. pFile->m_fSkipped = true;
  1424. }
  1425. else
  1426. {
  1427. if (g_fAnnounceDeletes)
  1428. ::printf("Deleting file \"%ls\" in preparation for hard link creation\n", static_cast<PCWSTR>(buffDestination));
  1429. if (!::DeleteFileW(buffDestination))
  1430. {
  1431. ::ReportFailure("Error deleting destination \"%ls\" in preparation of hard link creation\n", static_cast<PCWSTR>(buffDestination));
  1432. goto Exit;
  1433. }
  1434. }
  1435. }
  1436. if (fDoLink)
  1437. {
  1438. if (g_fAnnounceLinks)
  1439. ::printf("Creating hard link from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
  1440. // Hey, it's already there. Let's link it.
  1441. if (!::CreateHardLinkW(buffDestination, buffSource, NULL))
  1442. {
  1443. ::ReportFailure("Error creating hard link from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
  1444. goto Exit;
  1445. }
  1446. if (!l.Lock())
  1447. {
  1448. ::ReportFailure("Failed to lock global critical section\n");
  1449. goto Exit;
  1450. }
  1451. g_FilesLinked++;
  1452. g_FileLinksProcessed++;
  1453. l.Unlock();
  1454. }
  1455. else
  1456. {
  1457. if (g_fAnnounceSkips)
  1458. ::printf("Skipping hard link creation from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
  1459. if (!l.Lock())
  1460. {
  1461. ::ReportFailure("Failed to lock global critical section\n");
  1462. goto Exit;
  1463. }
  1464. g_LinksSkipped++;
  1465. g_FileLinksProcessed++;
  1466. l.Unlock();
  1467. }
  1468. fSuccess = TRUE;
  1469. Exit:
  1470. if (::GetLastError() == ERROR_TOO_MANY_OPEN_FILES)
  1471. {
  1472. rfReQueue = true;
  1473. fSuccess = TRUE;
  1474. }
  1475. return fSuccess;
  1476. }
  1477. BOOL
  1478. MyCopyFile(
  1479. BYTE *pBuffer,
  1480. DWORD cbBuffer,
  1481. PCWSTR lpExistingFileName,
  1482. PCWSTR lpNewFileName,
  1483. BOOL fFailIfExists,
  1484. HANDLE &rhFile
  1485. )
  1486. {
  1487. BOOL fSuccess = FALSE;
  1488. FN_TRACE_WIN32(fSuccess);
  1489. LARGE_INTEGER liFileSize;
  1490. rhFile = INVALID_HANDLE_VALUE;
  1491. HANDLE hIn = INVALID_HANDLE_VALUE, hOut = INVALID_HANDLE_VALUE;
  1492. hIn = ::CreateFileW(lpExistingFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
  1493. if (hIn == INVALID_HANDLE_VALUE)
  1494. {
  1495. ::ReportFailure("Failed to open input file \"%ls\"\n", lpExistingFileName);
  1496. goto Exit;
  1497. }
  1498. hOut = ::CreateFileW(lpNewFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);
  1499. if (hOut == INVALID_HANDLE_VALUE)
  1500. {
  1501. ::ReportFailure("Failed to open output file \"%ls\"\n", lpNewFileName);
  1502. goto Exit;
  1503. }
  1504. liFileSize.QuadPart = 0;
  1505. // let the games begin...
  1506. for (;;)
  1507. {
  1508. DWORD dwBytesRead, dwBytesWritten;
  1509. DWORD dwBytesToWrite = 0;
  1510. if (!::ReadFile(hIn, pBuffer, cbBuffer, &dwBytesRead, NULL))
  1511. {
  1512. ::ReportFailure("Error reading from file \"%ls\"\n", lpExistingFileName);
  1513. goto Exit;
  1514. }
  1515. if (dwBytesRead == 0)
  1516. break;
  1517. liFileSize.QuadPart += dwBytesRead;
  1518. if (dwBytesRead != cbBuffer)
  1519. {
  1520. // We have to round to the cluster size for the write...
  1521. dwBytesToWrite = dwBytesRead + (g_dwDestinationBytesPerSector - 1);
  1522. dwBytesToWrite -= (dwBytesToWrite % g_dwDestinationBytesPerSector);
  1523. }
  1524. else
  1525. dwBytesToWrite = dwBytesRead;
  1526. if (!::WriteFile(hOut, pBuffer, dwBytesToWrite, &dwBytesWritten, NULL))
  1527. {
  1528. ::ReportFailure("Error writing to file \"%ls\"\n", lpNewFileName);
  1529. goto Exit;
  1530. }
  1531. }
  1532. // If the file size wasn't a multiple of the sector size, we need to open the
  1533. // file without the unbuffered flag so that we can set its size to the exact
  1534. // correct byte count.
  1535. if ((liFileSize.QuadPart % g_dwDestinationBytesPerSector) != 0)
  1536. {
  1537. ::CloseHandle(hOut);
  1538. hOut = INVALID_HANDLE_VALUE;
  1539. hOut = ::CreateFileW(lpNewFileName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
  1540. if (hOut == INVALID_HANDLE_VALUE)
  1541. {
  1542. ::ReportFailure("Unable to reopen output file \"%ls\"\n", lpNewFileName);
  1543. goto Exit;
  1544. }
  1545. // Truncate the file appropriately
  1546. if (!::MySetFilePointerEx(hOut, liFileSize, NULL, FILE_BEGIN))
  1547. {
  1548. ::ReportFailure("Error setting file pointer on file \"%ls\"\n", lpNewFileName);
  1549. goto Exit;
  1550. }
  1551. if (!::SetEndOfFile(hOut))
  1552. {
  1553. ::ReportFailure("Error setting end of file on file \"%ls\"\n", lpNewFileName);
  1554. goto Exit;
  1555. }
  1556. }
  1557. ::CloseHandle(hIn);
  1558. hIn = INVALID_HANDLE_VALUE;
  1559. // Pass the handle back out and set it to INVALID_HANDLE_VALUE so that we don't
  1560. // try to close it in the exit path.
  1561. rhFile = hOut;
  1562. hOut = INVALID_HANDLE_VALUE;
  1563. fSuccess = TRUE;
  1564. Exit:
  1565. if (hIn != INVALID_HANDLE_VALUE)
  1566. {
  1567. CSxsPreserveLastError ple;
  1568. ::CloseHandle(hIn);
  1569. ple.Restore();
  1570. }
  1571. if (hOut != INVALID_HANDLE_VALUE)
  1572. {
  1573. CSxsPreserveLastError ple;
  1574. ::CloseHandle(hOut);
  1575. ple.Restore();
  1576. }
  1577. return fSuccess;
  1578. }