Leaked source code of windows server 2003
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.

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