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.

1450 lines
41 KiB

  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <ctype.h>
  6. // #define SEE_EM 1
  7. #define MAX_FILENAME_LENGTH 127
  8. #define MAX_TIMEANDSIZE 50 /* mm-dd-yyyy hh-mm-ss */
  9. #define MAX_FILENAME_PER_BLOCK 1000
  10. #define MAX_MEMBLOCKS 100
  11. #define MAX_THREADS 29
  12. #define MAX_COMMAND_LINE 1024
  13. #define MAX_ARGS 20
  14. // kenhia 15-Mar-1996: add support for -#:<share>
  15. //
  16. // splante 15-Oct-1996: changed support to the "ntbuilds" server
  17. #if defined(_ALPHA_) || defined(_X86_)
  18. #define PLATFORM_SPECIFIC_SHARES { "ntbuilds", "ntbuilds", "ntbuilds", "ntbuilds", "ntbuilds", NULL }
  19. #endif
  20. #ifndef PLATFORM_SPECIFIC_SHARES
  21. #pragma message( "WARNING: Platform Specific shares disabled" )
  22. #define PLATFORM_SPECIFIC_SHARES {NULL}
  23. #endif
  24. typedef struct _filename FILENAME;
  25. typedef union _virtual_pointer {
  26. FILENAME *mem_ptr;
  27. DWORD disk_ptr;
  28. } VIRTPTR;
  29. struct _filename {
  30. DWORD dwStatus;
  31. DWORD dwCopy;
  32. VIRTPTR fnParent;
  33. VIRTPTR fnChild;
  34. VIRTPTR fnSibling;
  35. DWORD dwFileSizeLow;
  36. DWORD dwFileSizeHigh;
  37. FILETIME ftFileTime;
  38. DWORD dwDATFileSizeLow;
  39. DWORD dwDATFileSizeHigh;
  40. FILETIME ftDATFileTime;
  41. DWORD dwFileNameLen;
  42. CHAR cFileName[MAX_FILENAME_LENGTH+1];
  43. };
  44. typedef struct _memblock MEMBLOCK;
  45. struct _memblock {
  46. HANDLE hMem;
  47. LPSTR lpBase;
  48. };
  49. typedef struct _fileheader {
  50. VIRTPTR fnRoot; // Pointer to root node
  51. } FILEHEADER;
  52. #define SUCK_INI_FILE ".\\suck.ini"
  53. #define SUCK_DAT_FILE ".\\suck.dat"
  54. #define DIRECTORY (DWORD)0x80000000
  55. #define STARTED (DWORD)0x40000000
  56. #define COPIED (DWORD)0x20000000
  57. CRITICAL_SECTION cs;
  58. CRITICAL_SECTION pcs;
  59. INT cAvailable = 0;
  60. FILENAME *lpBaseCurrent = NULL;
  61. INT nMemBlocks = 0;
  62. FILENAME *fnRoot = NULL;
  63. LONG nFiles = 0;
  64. LONG nDirectories = 0;
  65. LONG nDuplicates = 0;
  66. LONG nStraglers = 0;
  67. BOOL fCopying = FALSE;
  68. BOOL fScriptMode = FALSE;
  69. BOOL fLogTreeDifferences = FALSE;
  70. BOOL fUpdateINIBase = FALSE;
  71. BOOL fDestroy = FALSE;
  72. BOOL fUseDAT = TRUE;
  73. // DavidP 23-Jan-1998: BEGIN Allow multiple levels of quiet
  74. BOOL fQuietMode = FALSE;
  75. BOOL fProgressMode = FALSE;
  76. INT nConsoleWidth = 0;
  77. CHAR chLineEnd = '\n';
  78. // DavidP 23-Jan-1998: END Allow multiple levels of quiet
  79. FILE *SuckDATFile = NULL;
  80. #define MAX_EXCLUDES 1024
  81. CHAR gExcludes[MAX_EXCLUDES+1] = { '\0'};
  82. FILETIME ftZero = { 0, 0};
  83. MEMBLOCK mbBlocks[MAX_MEMBLOCKS];
  84. DWORD dwMasks[] = {
  85. 0x00000001,
  86. 0x00000002,
  87. 0x00000004,
  88. 0x00000008,
  89. 0x00000010,
  90. 0x00000020,
  91. 0x00000040,
  92. 0x00000080,
  93. 0x00000100,
  94. 0x00000200,
  95. 0x00000400,
  96. 0x00000800,
  97. 0x00001000,
  98. 0x00002000,
  99. 0x00004000,
  100. 0x00008000,
  101. 0x00010000,
  102. 0x00020000,
  103. 0x00040000,
  104. 0x00080000,
  105. 0x00100000,
  106. 0x00200000,
  107. 0x00400000,
  108. 0x00800000,
  109. 0x01000000,
  110. 0x02000000,
  111. 0x04000000,
  112. 0x08000000,
  113. 0x10000000,
  114. };
  115. CHAR chPath[MAX_THREADS][MAX_PATH];
  116. DWORD dwTotalSizes[MAX_THREADS];
  117. BOOL EverybodyBailOut = FALSE;
  118. BOOL fProblems[MAX_THREADS];
  119. FILENAME *AllocateFileName()
  120. {
  121. FILENAME *lpResult;
  122. /*
  123. ** Allocate a new FILENAME
  124. */
  125. if ( cAvailable == 0 ) {
  126. mbBlocks[nMemBlocks].hMem = GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT,
  127. MAX_FILENAME_PER_BLOCK * sizeof(FILENAME) );
  128. if ( mbBlocks[nMemBlocks].hMem == (HANDLE)0 ) {
  129. fprintf(stderr,"Memory Allocation Failed in AllocateFileName\n");
  130. exit(1);
  131. }
  132. mbBlocks[nMemBlocks].lpBase = GlobalLock( mbBlocks[nMemBlocks].hMem );
  133. lpBaseCurrent = (FILENAME *)mbBlocks[nMemBlocks].lpBase;
  134. cAvailable = MAX_FILENAME_PER_BLOCK;
  135. nMemBlocks++;
  136. }
  137. lpResult = lpBaseCurrent;
  138. --cAvailable;
  139. lpBaseCurrent++;
  140. return( lpResult );
  141. }
  142. VOID FreeFileNames()
  143. {
  144. while ( nMemBlocks ) {
  145. --nMemBlocks;
  146. GlobalUnlock( mbBlocks[nMemBlocks].hMem );
  147. GlobalFree( mbBlocks[nMemBlocks].hMem );
  148. }
  149. }
  150. VOID
  151. AddFile(
  152. FILENAME *fn,
  153. LPWIN32_FIND_DATA lpwfd,
  154. DWORD mask
  155. )
  156. {
  157. CHAR *pdest;
  158. CHAR *psrc;
  159. INT count;
  160. INT maximum;
  161. FILENAME *fnCurrent;
  162. FILENAME *fnChild;
  163. DWORD dwFileNameLen;
  164. CHAR NewName[MAX_FILENAME_LENGTH+1];
  165. FILENAME *fnChildOriginally;
  166. if ( *lpwfd->cFileName == '.' ) {
  167. return;
  168. }
  169. dwFileNameLen = strlen(lpwfd->cFileName);
  170. if (dwFileNameLen > MAX_FILENAME_LENGTH) {
  171. fprintf(stderr, "File name %s too long (%u > %u), complain to BobDay\n",
  172. lpwfd->cFileName, dwFileNameLen, MAX_FILENAME_LENGTH);
  173. return;
  174. }
  175. strcpy( NewName, lpwfd->cFileName );
  176. fnChild = fn->fnChild.mem_ptr;
  177. fnChildOriginally = fnChild;
  178. while ( fnChild ) {
  179. if ( fnChild->dwFileNameLen == dwFileNameLen &&
  180. !strcmp(NewName, fnChild->cFileName)
  181. ) {
  182. fnChild->dwStatus |= mask; // Atomic instruction
  183. if ( fnChild->ftFileTime.dwLowDateTime == ftZero.dwLowDateTime
  184. && fnChild->ftFileTime.dwHighDateTime == ftZero.dwHighDateTime ) {
  185. EnterCriticalSection( &cs );
  186. fnChild->dwFileSizeLow = lpwfd->nFileSizeLow;
  187. fnChild->dwFileSizeHigh = lpwfd->nFileSizeHigh;
  188. fnChild->ftFileTime = lpwfd->ftLastWriteTime;
  189. LeaveCriticalSection( &cs );
  190. }
  191. nDuplicates++;
  192. return;
  193. }
  194. fnChild = fnChild->fnSibling.mem_ptr;
  195. }
  196. // Probably not there... Enter the critical section now to prove it
  197. EnterCriticalSection( &cs );
  198. // Most common case, nobody has changed this directory at all.
  199. if ( fn->fnChild.mem_ptr != fnChildOriginally ) {
  200. // Otherwise, make another scan inside the critical section.
  201. fnChild = fn->fnChild.mem_ptr;
  202. while ( fnChild ) {
  203. if ( fnChild->dwFileNameLen == dwFileNameLen &&
  204. !strcmp(NewName, fnChild->cFileName)
  205. ) {
  206. fnChild->dwStatus |= mask; // Atomic instruction
  207. nDuplicates++;
  208. LeaveCriticalSection( &cs );
  209. return;
  210. }
  211. fnChild = fnChild->fnSibling.mem_ptr;
  212. }
  213. }
  214. fnCurrent = AllocateFileName();
  215. strcpy( fnCurrent->cFileName, NewName );
  216. fnCurrent->dwFileNameLen = dwFileNameLen;
  217. fnCurrent->dwFileSizeLow = lpwfd->nFileSizeLow;
  218. fnCurrent->dwFileSizeHigh = lpwfd->nFileSizeHigh;
  219. fnCurrent->ftFileTime = lpwfd->ftLastWriteTime;
  220. fnCurrent->dwDATFileSizeLow = 0;
  221. fnCurrent->dwDATFileSizeHigh = 0;
  222. fnCurrent->ftDATFileTime = ftZero;
  223. fnCurrent->dwCopy = 0;
  224. fnCurrent->fnParent.mem_ptr = fn;
  225. fnCurrent->fnChild.mem_ptr = NULL;
  226. fnCurrent->fnSibling.mem_ptr = fn->fnChild.mem_ptr;
  227. fn->fnChild.mem_ptr = fnCurrent;
  228. if ( lpwfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
  229. fnCurrent->dwStatus = DIRECTORY;
  230. nDirectories++;
  231. } else {
  232. fnCurrent->dwStatus = 0;
  233. nFiles++;
  234. }
  235. fnCurrent->dwStatus |= mask;
  236. #ifdef SEE_EM
  237. { char text[MAX_FILENAME_LENGTH+1];
  238. memcpy( text, fnCurrent->cFileName, MAX_FILENAME_LENGTH );
  239. text[MAX_FILENAME_LENGTH] = '\0';
  240. if ( fnCurrent->dwStatus & DIRECTORY ) {
  241. printf("Munged DirName = %08lX:[%s]\n", fnCurrent, text );
  242. } else {
  243. printf("Munged FileName = %08lX:[%s]\n", fnCurrent, text );
  244. }
  245. }
  246. #endif
  247. LeaveCriticalSection( &cs );
  248. }
  249. BOOL
  250. Excluded(
  251. WIN32_FIND_DATA *pwfd
  252. )
  253. {
  254. CHAR *pszScan = gExcludes;
  255. while (*pszScan) {
  256. if ((pwfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  257. _stricmp(pszScan, pwfd->cFileName) == 0) {
  258. return(TRUE);
  259. }
  260. pszScan = strchr(pszScan, 0) + 1;
  261. }
  262. return(FALSE);
  263. }
  264. VOID
  265. EnumFiles(
  266. LPSTR lpSearch,
  267. FILENAME *fnParent,
  268. DWORD mask,
  269. UINT iThread
  270. )
  271. {
  272. WIN32_FIND_DATA wfd;
  273. HANDLE hFind;
  274. CHAR NewName[MAX_PATH];
  275. CHAR *pch;
  276. CHAR *pchSpot;
  277. BOOL f;
  278. FILENAME *fnChild;
  279. DWORD rc;
  280. #ifdef SEE_EM
  281. printf("Enuming <%s>\n", lpSearch );
  282. #endif
  283. strcpy( NewName, lpSearch );
  284. pch = NewName + strlen(NewName) - 1;
  285. if ( *pch != '\\' && *pch != '/' && *pch != ':' ) {
  286. *++pch = '\\';
  287. }
  288. strcpy( ++pch, "*.*" );
  289. pchSpot = pch;
  290. do {
  291. hFind = FindFirstFile( NewName, &wfd );
  292. if ( hFind != INVALID_HANDLE_VALUE ) {
  293. break;
  294. }
  295. rc = GetLastError();
  296. switch ( rc ) {
  297. default:
  298. printf("%s: Error: GetLastError = %08ld What does it mean?\n", NewName, rc );
  299. case ERROR_SHARING_PAUSED:
  300. case ERROR_BAD_NETPATH:
  301. case ERROR_BAD_NET_NAME:
  302. case ERROR_NO_LOGON_SERVERS:
  303. case ERROR_VC_DISCONNECTED:
  304. case ERROR_UNEXP_NET_ERR:
  305. if ( !fProblems[iThread] ) {
  306. printf("Error accesing %s, switching to silent retry\n", lpSearch );
  307. fProblems[iThread] = TRUE;
  308. }
  309. if (EverybodyBailOut) {
  310. return;
  311. }
  312. Sleep( 10000 ); // Wait for 10 seconds
  313. break;
  314. break;
  315. }
  316. } while ( TRUE );
  317. if ( hFind != NULL ) {
  318. do {
  319. if (!Excluded(&wfd))
  320. AddFile( fnParent, &wfd, mask );
  321. f = FindNextFile( hFind, &wfd );
  322. } while ( f );
  323. }
  324. FindClose( hFind );
  325. fnChild = fnParent->fnChild.mem_ptr;
  326. while ( fnChild ) {
  327. /*
  328. ** If its a directory and it was one of "our" directories, then enum it
  329. */
  330. if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY
  331. && (fnChild->dwStatus & mask) == mask ) {
  332. pch = pchSpot;
  333. strcpy( pch, fnChild->cFileName );
  334. #ifdef SEE_EM
  335. printf("NewName = <%s>\n", NewName );
  336. #endif
  337. EnumFiles( NewName, fnChild, mask, iThread );
  338. }
  339. fnChild = fnChild->fnSibling.mem_ptr;
  340. }
  341. }
  342. BOOL
  343. CopyCheck(
  344. CHAR *pchPath,
  345. FILENAME *fnCurrent
  346. )
  347. {
  348. WORD wFatDate;
  349. WORD wFatTime;
  350. WORD wDATFatDate;
  351. WORD wDATFatTime;
  352. BOOL b;
  353. if ( fnCurrent->dwDATFileSizeLow != fnCurrent->dwFileSizeLow ) {
  354. return( TRUE );
  355. }
  356. if ( fnCurrent->dwDATFileSizeHigh != fnCurrent->dwFileSizeHigh ) {
  357. return( TRUE );
  358. }
  359. b = FileTimeToDosDateTime( &fnCurrent->ftDATFileTime, &wDATFatDate, &wDATFatTime );
  360. if ( !b ) {
  361. return( TRUE );
  362. }
  363. b = FileTimeToDosDateTime( &fnCurrent->ftFileTime, &wFatDate, &wFatTime );
  364. if ( !b ) {
  365. return( TRUE );
  366. }
  367. if ( wDATFatTime != wFatTime ) {
  368. return( TRUE );
  369. }
  370. if ( wDATFatDate != wFatDate ) {
  371. return( TRUE );
  372. }
  373. return( FALSE );
  374. }
  375. DWORD
  376. CopyThem(
  377. FILENAME *fnDir,
  378. CHAR *chDest,
  379. CHAR *chSrc,
  380. DWORD nThread,
  381. DWORD mask,
  382. BOOL f1stPass
  383. )
  384. {
  385. CHAR *pch;
  386. CHAR *pchSpotDest;
  387. CHAR *pchSpotSrc;
  388. CHAR chTemp[MAX_PATH];
  389. CHAR chTempName[20];
  390. BOOL fCopyIt;
  391. FILENAME *fnChild;
  392. BOOL fCopy;
  393. BOOL fCopied;
  394. BOOL fRenamed;
  395. BOOL fDeleted;
  396. BOOL fAttrib;
  397. DWORD dwCount;
  398. DWORD dwAttribs;
  399. DWORD dw;
  400. fnChild = fnDir->fnChild.mem_ptr;
  401. pchSpotDest = chDest + strlen(chDest);
  402. pchSpotSrc = chSrc + strlen(chSrc);
  403. dwCount = 0;
  404. while ( fnChild && !EverybodyBailOut) {
  405. fCopyIt = TRUE;
  406. if ( f1stPass ) {
  407. if ( (fnChild->dwStatus & STARTED) == STARTED ) {
  408. fCopyIt = FALSE;
  409. }
  410. } else {
  411. if ( (fnChild->dwStatus & COPIED) == COPIED ) {
  412. fCopyIt = FALSE;
  413. }
  414. }
  415. //
  416. // If the file doesn't exist on this thread's source location, then
  417. // don't try to copy it.
  418. //
  419. if ( (fnChild->dwStatus & mask) != mask ) {
  420. fCopyIt = FALSE;
  421. }
  422. if ( fCopyIt ) {
  423. // if ( f1stPass && (fnChild->dwStatus & STARTED) == STARTED ) {
  424. // fCopyIt = FALSE;
  425. // } else {
  426. // fnChild->dwStatus |= STARTED;
  427. // }
  428. // LeaveCriticalSection( &pcs );
  429. }
  430. if ( fCopyIt ) {
  431. pch = pchSpotDest;
  432. strcpy( pch, fnChild->cFileName );
  433. strcpy( pchSpotSrc, pchSpotDest );
  434. if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY ) {
  435. CreateDirectory( chDest, NULL );
  436. strcat( pchSpotDest, "\\" );
  437. strcat( pchSpotSrc, "\\" );
  438. dwCount += CopyThem( fnChild, chDest, chSrc, nThread, mask, f1stPass );
  439. } else {
  440. fnChild->dwStatus |= STARTED;
  441. strcpy( chTemp, chDest );
  442. *(chTemp+(pchSpotDest-chDest)) = '\0';
  443. sprintf( chTempName, "suck%02lX.tmp", mask );
  444. strcat( chTemp, chTempName );
  445. //
  446. // Check if we need to copy this file
  447. //
  448. fCopy = CopyCheck( chDest, fnChild );
  449. if ( fScriptMode ) {
  450. dwCount++;
  451. EnterCriticalSection( &pcs );
  452. fnChild->dwStatus |= COPIED;
  453. if ( fCopy ) {
  454. if ( !fQuietMode ) {
  455. printf("copy %s %s\n", chSrc, chDest );
  456. }
  457. dwTotalSizes[nThread-1] += fnChild->dwFileSizeLow;
  458. } else {
  459. if ( !fQuietMode ) {
  460. printf("rem copy %s %s\n", chSrc, chDest );
  461. }
  462. }
  463. LeaveCriticalSection( &pcs );
  464. } else {
  465. dwCount++;
  466. if ( fCopy ) {
  467. dwAttribs = GetFileAttributes( chTemp );
  468. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  469. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  470. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  471. }
  472. fCopied = CopyFile( chSrc, chTemp, FALSE );
  473. if ( !fCopying ) {
  474. EnterCriticalSection( &pcs );
  475. if ( !fCopying ) {
  476. fCopying = TRUE;
  477. printf("Copying files...\n" );
  478. }
  479. LeaveCriticalSection( &pcs );
  480. }
  481. if ( !fCopied ) {
  482. dw = GetLastError();
  483. printf("%s => %s\t[COPY ERROR %08lX]\n", chSrc, chTemp, dw );
  484. dwAttribs = GetFileAttributes( chTemp );
  485. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  486. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  487. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  488. }
  489. DeleteFile( chTemp );
  490. switch ( dw ) {
  491. case ERROR_BAD_NETPATH:
  492. case ERROR_BAD_NET_NAME:
  493. if ( !fProblems[nThread-1] ) {
  494. printf("Error accesing %s, switching to silent attempts\n", chSrc );
  495. fProblems[nThread-1] = TRUE;
  496. }
  497. Sleep( 10000 ); // Wait for 10 seconds
  498. break;
  499. default:
  500. break;
  501. }
  502. } else {
  503. EnterCriticalSection( &pcs );
  504. if ( (fnChild->dwStatus & COPIED) == COPIED ) {
  505. //
  506. // Copy was done by somebody else
  507. //
  508. dwAttribs = GetFileAttributes( chTemp );
  509. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  510. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  511. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  512. }
  513. fDeleted = DeleteFile( chTemp );
  514. } else {
  515. //
  516. // Copy was done by us, attempt rename
  517. //
  518. fAttrib = TRUE;
  519. if ( fDestroy ) {
  520. dwAttribs = GetFileAttributes( chDest );
  521. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  522. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  523. fAttrib = SetFileAttributes( chDest, dwAttribs );
  524. }
  525. }
  526. if ( !fAttrib ) {
  527. dw = GetLastError();
  528. printf("%s => %s\t[ATTRIBUTE CHANGE ERROR %08lX(%s)\n", chSrc, chDest, dw, chDest );
  529. dwAttribs = GetFileAttributes( chTemp );
  530. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  531. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  532. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  533. }
  534. fDeleted = DeleteFile( chTemp );
  535. } else {
  536. fDeleted = DeleteFile( chDest );
  537. if ( !fDeleted ) {
  538. dw = GetLastError();
  539. fnChild->dwStatus |= COPIED;
  540. }
  541. if ( fDeleted || dw == ERROR_FILE_NOT_FOUND ) {
  542. fRenamed = MoveFile( chTemp, chDest );
  543. if ( fRenamed ) {
  544. fnChild->dwStatus |= COPIED;
  545. if ( !fQuietMode ) {
  546. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  547. printf("%*s\r%s => %s\t[OK]%c", nConsoleWidth, "", chSrc, chDest, chLineEnd );
  548. }
  549. dwTotalSizes[nThread-1] += fnChild->dwFileSizeLow;
  550. } else {
  551. dw = GetLastError();
  552. printf("%s => %s\t[RENAME ERROR %08lX (%s)]\n", chSrc, chDest, dw, chTemp );
  553. dwAttribs = GetFileAttributes( chTemp );
  554. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  555. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  556. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  557. }
  558. fDeleted = DeleteFile( chTemp );
  559. }
  560. } else {
  561. dw = GetLastError();
  562. printf("%s => %s\t[DELETE ERROR %08lX (%s)]\n", chSrc, chDest, dw, chDest );
  563. dwAttribs = GetFileAttributes( chTemp );
  564. if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) {
  565. dwAttribs &= ~FILE_ATTRIBUTE_READONLY;
  566. fAttrib = SetFileAttributes( chTemp, dwAttribs );
  567. }
  568. fDeleted = DeleteFile( chTemp );
  569. }
  570. }
  571. }
  572. LeaveCriticalSection( &pcs );
  573. }
  574. } else {
  575. EnterCriticalSection( &pcs );
  576. if ( !fCopying ) {
  577. fCopying = TRUE;
  578. printf("Copying files...\n" );
  579. }
  580. fnChild->dwStatus |= COPIED;
  581. if ( !fQuietMode ) {
  582. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  583. // printf("%*s\r%s => %s\t[OK]%c", nConsoleWidth, "", chSrc, chDest, chLineEnd );
  584. }
  585. LeaveCriticalSection( &pcs );
  586. }
  587. }
  588. }
  589. *pchSpotDest = '\0';
  590. *pchSpotSrc = '\0';
  591. }
  592. fnChild = fnChild->fnSibling.mem_ptr;
  593. }
  594. return( dwCount );
  595. }
  596. DWORD
  597. WINAPI
  598. ThreadFunction(
  599. LPVOID lpParameter
  600. )
  601. {
  602. LPSTR lpSearch;
  603. DWORD mask;
  604. DWORD dw;
  605. CHAR chDest[MAX_PATH];
  606. CHAR chSrc[MAX_PATH];
  607. DWORD dwCount;
  608. dw = (DWORD)(DWORD_PTR)lpParameter;
  609. lpSearch = chPath[dw];
  610. mask = dwMasks[dw-1];
  611. EnumFiles( lpSearch, fnRoot, mask, dw-1 );
  612. strcpy( chDest, chPath[0] );
  613. strcpy( chSrc, chPath[dw] );
  614. CopyThem( fnRoot, chDest, chSrc, dw, mask, TRUE );
  615. strcpy( chDest, chPath[0] );
  616. strcpy( chSrc, chPath[dw] );
  617. do {
  618. dwCount = CopyThem( fnRoot, chDest, chSrc, dw, mask, FALSE );
  619. } while ( dwCount != 0 && !EverybodyBailOut);
  620. EverybodyBailOut = TRUE;
  621. return( 0 );
  622. }
  623. VOID
  624. EnumStraglers(
  625. LPSTR lpPath,
  626. FILENAME *fn,
  627. DWORD dwTotalMask
  628. )
  629. {
  630. FILENAME *fnChild;
  631. CHAR NewName[MAX_PATH];
  632. CHAR *pch;
  633. CHAR *pchSpot;
  634. pchSpot = lpPath + strlen(lpPath);
  635. fnChild = fn->fnChild.mem_ptr;
  636. while ( fnChild ) {
  637. pch = pchSpot;
  638. strcpy( pch, fnChild->cFileName );
  639. if ( (fnChild->dwStatus & dwTotalMask) != dwTotalMask ) {
  640. if ( fLogTreeDifferences ) {
  641. printf( "File %s is not on all source locations\n", lpPath );
  642. }
  643. nStraglers++;
  644. }
  645. if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY ) {
  646. strcat( pch, "\\" );
  647. EnumStraglers( lpPath, fnChild, dwTotalMask );
  648. }
  649. fnChild = fnChild->fnSibling.mem_ptr;
  650. }
  651. }
  652. VOID
  653. DumpStraglers(
  654. DWORD dwTotalMask
  655. )
  656. {
  657. CHAR cPath[MAX_PATH];
  658. strcpy( cPath, "<SRC>\\" );
  659. EnumStraglers( cPath, fnRoot, dwTotalMask );
  660. if ( nStraglers != 0 ) {
  661. printf("Files found on some source locations, but not on others\n");
  662. printf("Run SUCK with -x option to enumerate differences.\n");
  663. }
  664. }
  665. void
  666. EnumDATFileData(
  667. FILENAME *fnParent,
  668. DWORD dwDiskPtr
  669. )
  670. {
  671. FILENAME fnDiskName;
  672. FILENAME *fnChild = &fnDiskName;
  673. FILENAME *fnCurrent;
  674. int iSeek;
  675. int iCount;
  676. //
  677. // Read in this level from the DAT file
  678. //
  679. while ( dwDiskPtr != 0 ) {
  680. // Seek to this entry
  681. iSeek = fseek( SuckDATFile, dwDiskPtr, SEEK_SET );
  682. if ( iSeek != 0 ) {
  683. printf("SUCK.DAT seek error, remove and restart\n");
  684. exit(3);
  685. }
  686. // Read in this entry
  687. iCount = fread( (void *)fnChild, sizeof(FILENAME), 1, SuckDATFile );
  688. if ( iCount != 1 ) {
  689. printf("SUCK.DAT read error, remove and restart\n");
  690. exit(4);
  691. }
  692. #ifdef SEE_EM
  693. printf("Reading record [%s], at %08lX Child %08lX Sib %08lX\n", fnChild->cFileName, dwDiskPtr, fnChild->fnChild.disk_ptr, fnChild->fnSibling.disk_ptr );
  694. printf("Size = %d\n", fnChild->dwFileSizeLow );
  695. #endif
  696. //
  697. // Add this file node to the tree
  698. //
  699. fnCurrent = AllocateFileName();
  700. fnCurrent->dwStatus = fnChild->dwStatus;
  701. fnCurrent->dwCopy = 0;
  702. fnCurrent->fnParent.mem_ptr = fnParent;
  703. fnCurrent->fnChild.mem_ptr = NULL;
  704. fnCurrent->fnSibling.mem_ptr = fnParent->fnChild.mem_ptr;
  705. fnCurrent->dwFileSizeLow = 0;
  706. fnCurrent->dwFileSizeHigh = 0;
  707. fnCurrent->ftFileTime = ftZero;
  708. fnCurrent->dwDATFileSizeLow = fnChild->dwFileSizeLow;
  709. fnCurrent->dwDATFileSizeHigh = fnChild->dwFileSizeHigh;
  710. fnCurrent->ftDATFileTime = fnChild->ftFileTime;
  711. fnCurrent->dwFileNameLen = fnChild->dwFileNameLen;
  712. strcpy( fnCurrent->cFileName, fnChild->cFileName );
  713. fnParent->fnChild.mem_ptr = fnCurrent;
  714. if ( (fnCurrent->dwStatus & DIRECTORY) == DIRECTORY ) {
  715. nDirectories++;
  716. //
  717. // Load this directories children
  718. //
  719. EnumDATFileData( fnCurrent, fnChild->fnChild.disk_ptr );
  720. } else {
  721. fnCurrent->dwStatus = 0;
  722. nFiles++;
  723. }
  724. // Move to next sibling at this level
  725. dwDiskPtr = fnChild->fnSibling.disk_ptr;
  726. }
  727. }
  728. void
  729. LoadFileTimesAndSizes(
  730. BOOL fUseSuckDATFile
  731. )
  732. {
  733. CHAR cPath[MAX_PATH];
  734. FILEHEADER fileheader;
  735. int iCount;
  736. //
  737. // Initialize the tree root
  738. //
  739. fnRoot = AllocateFileName();
  740. fnRoot->fnParent.mem_ptr = NULL;
  741. fnRoot->fnChild.mem_ptr = NULL;
  742. fnRoot->fnSibling.mem_ptr = NULL;
  743. strcpy( fnRoot->cFileName, "<ROOT>" );
  744. // Look for SUCK.DAT
  745. if ( fUseSuckDATFile ) {
  746. SuckDATFile = fopen( SUCK_DAT_FILE, "rb" );
  747. } else {
  748. SuckDATFile = NULL;
  749. }
  750. if ( SuckDATFile != NULL ) {
  751. //
  752. // If file exists, then load the data from it.
  753. //
  754. printf("Loading Previous Statistics...\n");
  755. iCount = fread( &fileheader, sizeof(fileheader), 1, SuckDATFile );
  756. if ( iCount != 1 ) {
  757. printf("Error reading SUCK.DAT file, remove and restart\n");
  758. exit(1);
  759. }
  760. EnumDATFileData( fnRoot, fileheader.fnRoot.disk_ptr );
  761. fclose( SuckDATFile );
  762. }
  763. }
  764. int
  765. EnumFileTimesAndSizes(
  766. DWORD dwDiskPtr,
  767. FILENAME *fn
  768. )
  769. {
  770. FILENAME fnDiskName;
  771. FILENAME *fnChild = &fnDiskName;
  772. FILENAME *fnCurrent;
  773. VIRTPTR fnChildPtr;
  774. VIRTPTR fnSiblingPtr;
  775. int nRecords;
  776. int nChildren;
  777. int iCount;
  778. //
  779. // The 1st guy in the list will be at the end of the list
  780. //
  781. fnSiblingPtr.disk_ptr = 0;
  782. nRecords = 0;
  783. fnCurrent = fn->fnChild.mem_ptr;
  784. while ( fnCurrent ) {
  785. *fnChild = *fnCurrent;
  786. if ( (fnCurrent->dwStatus & DIRECTORY) == DIRECTORY ) {
  787. nChildren = EnumFileTimesAndSizes( dwDiskPtr, fnCurrent );
  788. nRecords += nChildren;
  789. dwDiskPtr += nChildren * sizeof(FILENAME);
  790. if ( nRecords == 0 ) {
  791. fnChildPtr.disk_ptr = 0;
  792. } else {
  793. // Point to previous one, it was our child
  794. fnChildPtr.disk_ptr = dwDiskPtr - sizeof(FILENAME);
  795. }
  796. } else {
  797. fnChildPtr.disk_ptr = 0;
  798. }
  799. fnChild->fnChild.disk_ptr = fnChildPtr.disk_ptr;
  800. fnChild->fnSibling.disk_ptr = fnSiblingPtr.disk_ptr;
  801. fnSiblingPtr.disk_ptr = dwDiskPtr;
  802. #ifdef SEE_EM
  803. printf("Writing record [%s], at %08lX Child %08lX Sib %08lX\n", fnChild->cFileName, dwDiskPtr, fnChild->fnChild.disk_ptr, fnChild->fnSibling.disk_ptr );
  804. printf("Size = %d\n", fnChild->dwFileSizeLow );
  805. #endif
  806. iCount = fwrite( fnChild, sizeof(FILENAME), 1, SuckDATFile );
  807. if ( iCount != 1 ) {
  808. printf("SUCK.DAT error writing data\n");
  809. exit(1);
  810. }
  811. dwDiskPtr += sizeof(FILENAME);
  812. nRecords++;
  813. fnCurrent = fnCurrent->fnSibling.mem_ptr;
  814. }
  815. return( nRecords );
  816. }
  817. VOID
  818. UpdateFileTimesAndSizes(
  819. VOID
  820. )
  821. {
  822. CHAR cPath[MAX_PATH];
  823. FILEHEADER fileheader;
  824. int iSeek;
  825. int iCount;
  826. DWORD dwDiskPtr;
  827. int nChildren;
  828. printf("Updating Statistics...\n");
  829. SuckDATFile = fopen( SUCK_DAT_FILE, "wb+" );
  830. if ( SuckDATFile == NULL ) {
  831. printf( "Error creating file '%s', update aborted\n", SUCK_DAT_FILE );
  832. return;
  833. }
  834. fileheader.fnRoot.disk_ptr = 0; // Temporary...
  835. iCount = fwrite( &fileheader, sizeof(fileheader), 1, SuckDATFile );
  836. if ( iCount != 1 ) {
  837. printf("SUCK.DAT error writing header\n");
  838. exit(1);
  839. }
  840. dwDiskPtr = sizeof(fileheader);
  841. nChildren = EnumFileTimesAndSizes( dwDiskPtr, fnRoot );
  842. dwDiskPtr += nChildren * sizeof(FILENAME);
  843. if ( nChildren == 0 ) {
  844. dwDiskPtr = 0;
  845. } else {
  846. dwDiskPtr -= sizeof(FILENAME);
  847. }
  848. fileheader.fnRoot.disk_ptr = dwDiskPtr; // Now update for real...
  849. iSeek = fseek( SuckDATFile, 0, SEEK_SET );
  850. if ( iSeek != 0 ) {
  851. printf("SUCK.DAT error seeking to write header\n");
  852. exit(3);
  853. }
  854. iCount = fwrite( &fileheader, sizeof(fileheader), 1, SuckDATFile );
  855. if ( iCount != 1 ) {
  856. printf("SUCK.DAT error writing header\n");
  857. exit(1);
  858. }
  859. fclose( SuckDATFile );
  860. }
  861. CHAR *NewArgv[MAX_ARGS];
  862. CHAR chCommand[MAX_COMMAND_LINE+1];
  863. VOID
  864. LookForLastCommand(
  865. INT *pargc,
  866. CHAR **pargv[]
  867. )
  868. {
  869. CHAR *pSpace;
  870. CHAR *pNextArg;
  871. GetPrivateProfileString("Init", "LastCommand", "", chCommand, MAX_COMMAND_LINE, SUCK_INI_FILE );
  872. if ( strlen(chCommand) == 0 ) {
  873. return;
  874. }
  875. pNextArg = chCommand;
  876. *pargv = NewArgv;
  877. (*pargv)[1] = "";
  878. *pargc = 1;
  879. do {
  880. (*pargc)++;
  881. pSpace = strchr( pNextArg, ' ' );
  882. if ( pSpace ) {
  883. *pSpace = '\0';
  884. }
  885. (*pargv)[(*pargc)-1] = pNextArg;
  886. pNextArg = pSpace + 1;
  887. } while ( pSpace != NULL );
  888. }
  889. VOID
  890. UpdateLastCommandLine(
  891. INT argc,
  892. CHAR *argv[]
  893. )
  894. {
  895. CHAR chLastCommand[MAX_COMMAND_LINE+1];
  896. INT nArg;
  897. chLastCommand[0] = '\0';
  898. nArg = 1;
  899. while ( nArg < argc ) {
  900. strcat( chLastCommand, argv[nArg] );
  901. nArg++;
  902. if ( nArg != argc ) {
  903. strcat( chLastCommand, " " );
  904. }
  905. }
  906. WritePrivateProfileString("Init", "LastCommand", chLastCommand, SUCK_INI_FILE );
  907. }
  908. VOID
  909. ReplaceEnvironmentStrings(
  910. CHAR *pText
  911. )
  912. {
  913. CHAR *pOpenPercent;
  914. CHAR *pClosePercent;
  915. CHAR chBuffer[MAX_PATH];
  916. CHAR *pSrc;
  917. CHAR *pEnvString;
  918. chBuffer[0] = '\0';
  919. pSrc = pText;
  920. do {
  921. pOpenPercent = strchr( pSrc, '%' );
  922. if ( pOpenPercent == NULL ) {
  923. strcat( chBuffer, pSrc );
  924. break;
  925. }
  926. pEnvString = pOpenPercent + 1;
  927. pClosePercent = strchr( pEnvString, '%' );
  928. if ( pClosePercent == NULL ) {
  929. strcat( chBuffer, pSrc );
  930. break;
  931. }
  932. if ( pEnvString == pClosePercent ) {
  933. strcat( chBuffer, "%" );
  934. } else {
  935. *pOpenPercent = '\0';
  936. *pClosePercent = '\0';
  937. strcat( chBuffer, pSrc );
  938. GetEnvironmentVariable( pEnvString,
  939. chBuffer + strlen(chBuffer),
  940. MAX_PATH );
  941. }
  942. pSrc = pClosePercent+1;
  943. } while ( TRUE );
  944. strcpy( pText, chBuffer );
  945. }
  946. DWORD
  947. DiffTimes(
  948. SYSTEMTIME *start,
  949. SYSTEMTIME *end
  950. )
  951. {
  952. DWORD nSecStart;
  953. DWORD nSecEnd;
  954. nSecStart = start->wHour*60*60 +
  955. start->wMinute*60 +
  956. start->wSecond;
  957. nSecEnd = end->wHour*60*60 +
  958. end->wMinute*60 +
  959. end->wSecond;
  960. return nSecEnd - nSecStart;
  961. }
  962. VOID
  963. Usage(
  964. VOID
  965. )
  966. {
  967. fputs("SUCK: Usage suck [-options] <dest> <src> [<src>...]\n"
  968. " (maximum of 29 src directories)\n"
  969. "\n"
  970. " where options are: x - List source differences (if any)\n"
  971. " s - Produce script, don't copy\n"
  972. " q - Quiet mode (no stdout)\n"
  973. " p - Display progress on one line\n"
  974. " (cannot be used with -q or -s)\n"
  975. " z - Copy over readonly files\n"
  976. " e - Exclude directory\n"
  977. " e.g. -eidw -emstools\n"
  978. , stderr);
  979. }
  980. VOID
  981. ArgError(
  982. INT nArg,
  983. CHAR * pszArg
  984. )
  985. {
  986. fprintf( stderr, "\nError in arg #%d - '%s'\n\n", nArg, pszArg );
  987. Usage();
  988. exit(1);
  989. }
  990. int
  991. __cdecl
  992. main(
  993. int argc,
  994. char *argv[]
  995. )
  996. {
  997. HANDLE hThreads[MAX_THREADS];
  998. DWORD dwThreadId;
  999. DWORD nThreads;
  1000. INT nArg;
  1001. DWORD nPaths;
  1002. CHAR *pch;
  1003. CHAR *pch2;
  1004. DWORD dwTotalMask;
  1005. OFSTRUCT ofs;
  1006. BOOL fUpdateCommandLine = TRUE;
  1007. CHAR * pchNextExclude = gExcludes;
  1008. SYSTEMTIME stStart;
  1009. SYSTEMTIME stEnd;
  1010. DWORD nSeconds;
  1011. DWORD nMinutes;
  1012. // kenhia 15-Mar-1996: add support for -#:<share>
  1013. CHAR * PlatformPoundArray[] = PLATFORM_SPECIFIC_SHARES;
  1014. DWORD nPound = 0;
  1015. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1016. DWORD dwConsoleMode = 0;
  1017. BOOL fWasConsoleModeSet = FALSE;
  1018. HANDLE hStdOut = NULL;
  1019. if ( argc < 2 ) {
  1020. LookForLastCommand( &argc, &argv );
  1021. fUpdateCommandLine = FALSE;
  1022. }
  1023. if ( argc < 3 ) {
  1024. Usage();
  1025. exit(1);
  1026. }
  1027. nArg = 1;
  1028. nPaths = 0;
  1029. while ( nArg < argc ) {
  1030. pch = argv[nArg];
  1031. if ( *pch == '-' ) {
  1032. BOOL fExitSwitchLoop = FALSE;
  1033. pch++;
  1034. while ( *pch && !fExitSwitchLoop) {
  1035. switch ( *pch ) {
  1036. case 's':
  1037. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1038. if ( fProgressMode ) {
  1039. ArgError( nArg, argv[nArg] );
  1040. }
  1041. fScriptMode = TRUE;
  1042. break;
  1043. case 'q':
  1044. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1045. if ( fProgressMode ) {
  1046. ArgError( nArg, argv[nArg] );
  1047. }
  1048. fQuietMode = TRUE;
  1049. break;
  1050. case 'p': // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1051. if ( fQuietMode || fScriptMode ) {
  1052. ArgError( nArg, argv[nArg] );
  1053. }
  1054. fProgressMode = TRUE;
  1055. chLineEnd = '\r';
  1056. break;
  1057. case 'x':
  1058. fLogTreeDifferences = TRUE;
  1059. break;
  1060. case 'y':
  1061. fUpdateINIBase = TRUE;
  1062. break;
  1063. case 'z':
  1064. fDestroy = TRUE;
  1065. break;
  1066. case 'e':
  1067. if ( pchNextExclude - gExcludes + strlen(++pch) + 2 > MAX_EXCLUDES ) {
  1068. ArgError( nArg, argv[nArg] );
  1069. }
  1070. strcpy(pchNextExclude, pch);
  1071. pchNextExclude += strlen(pchNextExclude)+1;
  1072. *pchNextExclude = 0;
  1073. fExitSwitchLoop = TRUE;
  1074. break;
  1075. // kenhia 15-Mar-1996: add support for -#:<share>
  1076. case '#':
  1077. pch++;
  1078. if ( *pch != ':' ) {
  1079. Usage();
  1080. exit(1);
  1081. }
  1082. while ( PlatformPoundArray[nPound] ) {
  1083. if ( nPaths >= MAX_THREADS ) {
  1084. Usage();
  1085. exit(1);
  1086. }
  1087. strcpy( chPath[nPaths], "\\\\" );
  1088. strcat( chPath[nPaths], PlatformPoundArray[nPound] );
  1089. strcat( chPath[nPaths], "\\" );
  1090. strcat( chPath[nPaths], pch+1 );
  1091. pch2 = chPath[nPaths] + strlen(chPath[nPaths]) - 1;
  1092. if ( *pch2 != '\\' && *pch2 != '/' && *pch2 != ':' ) {
  1093. *++pch2 = '\\';
  1094. *++pch2 = '\0';
  1095. }
  1096. ReplaceEnvironmentStrings( chPath[nPaths] );
  1097. nPound++;
  1098. nPaths++;
  1099. }
  1100. fExitSwitchLoop = TRUE;
  1101. break;
  1102. default:
  1103. Usage();
  1104. exit(1);
  1105. }
  1106. pch++;
  1107. }
  1108. } else {
  1109. if ( nPaths >= MAX_THREADS ) {
  1110. Usage();
  1111. exit(1);
  1112. }
  1113. strcpy( chPath[nPaths], argv[nArg] );
  1114. pch = chPath[nPaths] + strlen(chPath[nPaths]) - 1;
  1115. if ( *pch != '\\' && *pch != '/' && *pch != ':' ) {
  1116. *++pch = '\\';
  1117. *++pch = '\0';
  1118. }
  1119. ReplaceEnvironmentStrings( chPath[nPaths] );
  1120. nPaths++;
  1121. }
  1122. nArg++;
  1123. }
  1124. nThreads = --nPaths;
  1125. if ( nThreads == 0 ) {
  1126. Usage();
  1127. exit(1);
  1128. }
  1129. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1130. if ( fProgressMode ) {
  1131. hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
  1132. if ( hStdOut != INVALID_HANDLE_VALUE ) {
  1133. CONSOLE_SCREEN_BUFFER_INFO csbi;
  1134. if ( GetConsoleScreenBufferInfo( hStdOut, &csbi ) ) {
  1135. nConsoleWidth = csbi.dwSize.X - 1;
  1136. }
  1137. }
  1138. fWasConsoleModeSet = GetConsoleMode( hStdOut, &dwConsoleMode );
  1139. if ( fWasConsoleModeSet ) {
  1140. SetConsoleMode( hStdOut, dwConsoleMode & ~ENABLE_WRAP_AT_EOL_OUTPUT );
  1141. }
  1142. }
  1143. InitializeCriticalSection( &cs );
  1144. InitializeCriticalSection( &pcs );
  1145. printf("Streamlined Utility for Copying Kernel v1.1 (%d %s)\n", nThreads, (nThreads == 1 ? "thread" : "threads") );
  1146. GetSystemTime( &stStart );
  1147. LoadFileTimesAndSizes( fUseDAT );
  1148. dwTotalMask = 0;
  1149. while ( nPaths ) {
  1150. hThreads[nPaths-1] = CreateThread( NULL,
  1151. 0L,
  1152. ThreadFunction,
  1153. (LPVOID)UlongToPtr(nPaths),
  1154. 0,
  1155. &dwThreadId );
  1156. dwTotalMask |= dwMasks[nPaths-1];
  1157. --nPaths;
  1158. }
  1159. WaitForMultipleObjects( nThreads,
  1160. hThreads,
  1161. TRUE, // WaitAll
  1162. (DWORD)-1 );
  1163. // DavidP 23-Jan-1998: Allow multiple levels of quiet
  1164. if ( fProgressMode ) {
  1165. printf("%*s\r", nConsoleWidth, "");
  1166. if ( fWasConsoleModeSet ) {
  1167. SetConsoleMode( hStdOut, dwConsoleMode );
  1168. }
  1169. }
  1170. printf("Copy complete, %ld file entries\n", nFiles+nDirectories);
  1171. nPaths = 0;
  1172. while ( nPaths < nThreads ) {
  1173. printf("%11ld bytes from %s\n", dwTotalSizes[nPaths], chPath[nPaths+1] );
  1174. nPaths++;
  1175. }
  1176. nPaths = nThreads;
  1177. while ( nPaths ) {
  1178. nPaths--;
  1179. CloseHandle( hThreads[nPaths] );
  1180. }
  1181. DumpStraglers( dwTotalMask );
  1182. if ( fUpdateINIBase ) {
  1183. UpdateFileTimesAndSizes();
  1184. }
  1185. FreeFileNames();
  1186. DeleteCriticalSection( &cs );
  1187. DeleteCriticalSection( &pcs );
  1188. if ( fUpdateCommandLine ) {
  1189. UpdateLastCommandLine( argc, argv );
  1190. }
  1191. GetSystemTime( &stEnd );
  1192. nSeconds = DiffTimes( &stStart, &stEnd );
  1193. nMinutes = nSeconds / 60;
  1194. nSeconds = nSeconds % 60;
  1195. printf("Done, Elapsed time: %02d:%02d\n", nMinutes, nSeconds);
  1196. return( 0 );
  1197. }