#include #include #include #include #include // #define SEE_EM 1 #define MAX_FILENAME_LENGTH 127 #define MAX_TIMEANDSIZE 50 /* mm-dd-yyyy hh-mm-ss */ #define MAX_FILENAME_PER_BLOCK 1000 #define MAX_MEMBLOCKS 100 #define MAX_THREADS 29 #define MAX_COMMAND_LINE 1024 #define MAX_ARGS 20 // kenhia 15-Mar-1996: add support for -#: // // splante 15-Oct-1996: changed support to the "ntbuilds" server #if defined(_ALPHA_) || defined(_X86_) #define PLATFORM_SPECIFIC_SHARES { "ntbuilds", "ntbuilds", "ntbuilds", "ntbuilds", "ntbuilds", NULL } #endif #ifndef PLATFORM_SPECIFIC_SHARES #pragma message( "WARNING: Platform Specific shares disabled" ) #define PLATFORM_SPECIFIC_SHARES {NULL} #endif typedef struct _filename FILENAME; typedef union _virtual_pointer { FILENAME *mem_ptr; DWORD disk_ptr; } VIRTPTR; struct _filename { DWORD dwStatus; DWORD dwCopy; VIRTPTR fnParent; VIRTPTR fnChild; VIRTPTR fnSibling; DWORD dwFileSizeLow; DWORD dwFileSizeHigh; FILETIME ftFileTime; DWORD dwDATFileSizeLow; DWORD dwDATFileSizeHigh; FILETIME ftDATFileTime; DWORD dwFileNameLen; CHAR cFileName[MAX_FILENAME_LENGTH+1]; }; typedef struct _memblock MEMBLOCK; struct _memblock { HANDLE hMem; LPSTR lpBase; }; typedef struct _fileheader { VIRTPTR fnRoot; // Pointer to root node } FILEHEADER; #define SUCK_INI_FILE ".\\suck.ini" #define SUCK_DAT_FILE ".\\suck.dat" #define DIRECTORY (DWORD)0x80000000 #define STARTED (DWORD)0x40000000 #define COPIED (DWORD)0x20000000 CRITICAL_SECTION cs; CRITICAL_SECTION pcs; INT cAvailable = 0; FILENAME *lpBaseCurrent = NULL; INT nMemBlocks = 0; FILENAME *fnRoot = NULL; LONG nFiles = 0; LONG nDirectories = 0; LONG nDuplicates = 0; LONG nStraglers = 0; BOOL fCopying = FALSE; BOOL fScriptMode = FALSE; BOOL fLogTreeDifferences = FALSE; BOOL fUpdateINIBase = FALSE; BOOL fDestroy = FALSE; BOOL fUseDAT = TRUE; // DavidP 23-Jan-1998: BEGIN Allow multiple levels of quiet BOOL fQuietMode = FALSE; BOOL fProgressMode = FALSE; INT nConsoleWidth = 0; CHAR chLineEnd = '\n'; // DavidP 23-Jan-1998: END Allow multiple levels of quiet FILE *SuckDATFile = NULL; #define MAX_EXCLUDES 1024 CHAR gExcludes[MAX_EXCLUDES+1] = { '\0'}; FILETIME ftZero = { 0, 0}; MEMBLOCK mbBlocks[MAX_MEMBLOCKS]; DWORD dwMasks[] = { 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000, 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000, 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, }; CHAR chPath[MAX_THREADS][MAX_PATH]; DWORD dwTotalSizes[MAX_THREADS]; BOOL EverybodyBailOut = FALSE; BOOL fProblems[MAX_THREADS]; FILENAME *AllocateFileName() { FILENAME *lpResult; /* ** Allocate a new FILENAME */ if ( cAvailable == 0 ) { mbBlocks[nMemBlocks].hMem = GlobalAlloc( GMEM_FIXED | GMEM_ZEROINIT, MAX_FILENAME_PER_BLOCK * sizeof(FILENAME) ); if ( mbBlocks[nMemBlocks].hMem == (HANDLE)0 ) { fprintf(stderr,"Memory Allocation Failed in AllocateFileName\n"); exit(1); } mbBlocks[nMemBlocks].lpBase = GlobalLock( mbBlocks[nMemBlocks].hMem ); lpBaseCurrent = (FILENAME *)mbBlocks[nMemBlocks].lpBase; cAvailable = MAX_FILENAME_PER_BLOCK; nMemBlocks++; } lpResult = lpBaseCurrent; --cAvailable; lpBaseCurrent++; return( lpResult ); } VOID FreeFileNames() { while ( nMemBlocks ) { --nMemBlocks; GlobalUnlock( mbBlocks[nMemBlocks].hMem ); GlobalFree( mbBlocks[nMemBlocks].hMem ); } } VOID AddFile( FILENAME *fn, LPWIN32_FIND_DATA lpwfd, DWORD mask ) { CHAR *pdest; CHAR *psrc; INT count; INT maximum; FILENAME *fnCurrent; FILENAME *fnChild; DWORD dwFileNameLen; CHAR NewName[MAX_FILENAME_LENGTH+1]; FILENAME *fnChildOriginally; if ( *lpwfd->cFileName == '.' ) { return; } dwFileNameLen = strlen(lpwfd->cFileName); if (dwFileNameLen > MAX_FILENAME_LENGTH) { fprintf(stderr, "File name %s too long (%u > %u), complain to BobDay\n", lpwfd->cFileName, dwFileNameLen, MAX_FILENAME_LENGTH); return; } strcpy( NewName, lpwfd->cFileName ); fnChild = fn->fnChild.mem_ptr; fnChildOriginally = fnChild; while ( fnChild ) { if ( fnChild->dwFileNameLen == dwFileNameLen && !strcmp(NewName, fnChild->cFileName) ) { fnChild->dwStatus |= mask; // Atomic instruction if ( fnChild->ftFileTime.dwLowDateTime == ftZero.dwLowDateTime && fnChild->ftFileTime.dwHighDateTime == ftZero.dwHighDateTime ) { EnterCriticalSection( &cs ); fnChild->dwFileSizeLow = lpwfd->nFileSizeLow; fnChild->dwFileSizeHigh = lpwfd->nFileSizeHigh; fnChild->ftFileTime = lpwfd->ftLastWriteTime; LeaveCriticalSection( &cs ); } nDuplicates++; return; } fnChild = fnChild->fnSibling.mem_ptr; } // Probably not there... Enter the critical section now to prove it EnterCriticalSection( &cs ); // Most common case, nobody has changed this directory at all. if ( fn->fnChild.mem_ptr != fnChildOriginally ) { // Otherwise, make another scan inside the critical section. fnChild = fn->fnChild.mem_ptr; while ( fnChild ) { if ( fnChild->dwFileNameLen == dwFileNameLen && !strcmp(NewName, fnChild->cFileName) ) { fnChild->dwStatus |= mask; // Atomic instruction nDuplicates++; LeaveCriticalSection( &cs ); return; } fnChild = fnChild->fnSibling.mem_ptr; } } fnCurrent = AllocateFileName(); strcpy( fnCurrent->cFileName, NewName ); fnCurrent->dwFileNameLen = dwFileNameLen; fnCurrent->dwFileSizeLow = lpwfd->nFileSizeLow; fnCurrent->dwFileSizeHigh = lpwfd->nFileSizeHigh; fnCurrent->ftFileTime = lpwfd->ftLastWriteTime; fnCurrent->dwDATFileSizeLow = 0; fnCurrent->dwDATFileSizeHigh = 0; fnCurrent->ftDATFileTime = ftZero; fnCurrent->dwCopy = 0; fnCurrent->fnParent.mem_ptr = fn; fnCurrent->fnChild.mem_ptr = NULL; fnCurrent->fnSibling.mem_ptr = fn->fnChild.mem_ptr; fn->fnChild.mem_ptr = fnCurrent; if ( lpwfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { fnCurrent->dwStatus = DIRECTORY; nDirectories++; } else { fnCurrent->dwStatus = 0; nFiles++; } fnCurrent->dwStatus |= mask; #ifdef SEE_EM { char text[MAX_FILENAME_LENGTH+1]; memcpy( text, fnCurrent->cFileName, MAX_FILENAME_LENGTH ); text[MAX_FILENAME_LENGTH] = '\0'; if ( fnCurrent->dwStatus & DIRECTORY ) { printf("Munged DirName = %08lX:[%s]\n", fnCurrent, text ); } else { printf("Munged FileName = %08lX:[%s]\n", fnCurrent, text ); } } #endif LeaveCriticalSection( &cs ); } BOOL Excluded( WIN32_FIND_DATA *pwfd ) { CHAR *pszScan = gExcludes; while (*pszScan) { if ((pwfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && _stricmp(pszScan, pwfd->cFileName) == 0) { return(TRUE); } pszScan = strchr(pszScan, 0) + 1; } return(FALSE); } VOID EnumFiles( LPSTR lpSearch, FILENAME *fnParent, DWORD mask, UINT iThread ) { WIN32_FIND_DATA wfd; HANDLE hFind; CHAR NewName[MAX_PATH]; CHAR *pch; CHAR *pchSpot; BOOL f; FILENAME *fnChild; DWORD rc; #ifdef SEE_EM printf("Enuming <%s>\n", lpSearch ); #endif strcpy( NewName, lpSearch ); pch = NewName + strlen(NewName) - 1; if ( *pch != '\\' && *pch != '/' && *pch != ':' ) { *++pch = '\\'; } strcpy( ++pch, "*.*" ); pchSpot = pch; do { hFind = FindFirstFile( NewName, &wfd ); if ( hFind != INVALID_HANDLE_VALUE ) { break; } rc = GetLastError(); switch ( rc ) { default: printf("%s: Error: GetLastError = %08ld What does it mean?\n", NewName, rc ); case ERROR_SHARING_PAUSED: case ERROR_BAD_NETPATH: case ERROR_BAD_NET_NAME: case ERROR_NO_LOGON_SERVERS: case ERROR_VC_DISCONNECTED: case ERROR_UNEXP_NET_ERR: if ( !fProblems[iThread] ) { printf("Error accesing %s, switching to silent retry\n", lpSearch ); fProblems[iThread] = TRUE; } if (EverybodyBailOut) { return; } Sleep( 10000 ); // Wait for 10 seconds break; break; } } while ( TRUE ); if ( hFind != NULL ) { do { if (!Excluded(&wfd)) AddFile( fnParent, &wfd, mask ); f = FindNextFile( hFind, &wfd ); } while ( f ); } FindClose( hFind ); fnChild = fnParent->fnChild.mem_ptr; while ( fnChild ) { /* ** If its a directory and it was one of "our" directories, then enum it */ if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY && (fnChild->dwStatus & mask) == mask ) { pch = pchSpot; strcpy( pch, fnChild->cFileName ); #ifdef SEE_EM printf("NewName = <%s>\n", NewName ); #endif EnumFiles( NewName, fnChild, mask, iThread ); } fnChild = fnChild->fnSibling.mem_ptr; } } BOOL CopyCheck( CHAR *pchPath, FILENAME *fnCurrent ) { WORD wFatDate; WORD wFatTime; WORD wDATFatDate; WORD wDATFatTime; BOOL b; if ( fnCurrent->dwDATFileSizeLow != fnCurrent->dwFileSizeLow ) { return( TRUE ); } if ( fnCurrent->dwDATFileSizeHigh != fnCurrent->dwFileSizeHigh ) { return( TRUE ); } b = FileTimeToDosDateTime( &fnCurrent->ftDATFileTime, &wDATFatDate, &wDATFatTime ); if ( !b ) { return( TRUE ); } b = FileTimeToDosDateTime( &fnCurrent->ftFileTime, &wFatDate, &wFatTime ); if ( !b ) { return( TRUE ); } if ( wDATFatTime != wFatTime ) { return( TRUE ); } if ( wDATFatDate != wFatDate ) { return( TRUE ); } return( FALSE ); } DWORD CopyThem( FILENAME *fnDir, CHAR *chDest, CHAR *chSrc, DWORD nThread, DWORD mask, BOOL f1stPass ) { CHAR *pch; CHAR *pchSpotDest; CHAR *pchSpotSrc; CHAR chTemp[MAX_PATH]; CHAR chTempName[20]; BOOL fCopyIt; FILENAME *fnChild; BOOL fCopy; BOOL fCopied; BOOL fRenamed; BOOL fDeleted; BOOL fAttrib; DWORD dwCount; DWORD dwAttribs; DWORD dw; fnChild = fnDir->fnChild.mem_ptr; pchSpotDest = chDest + strlen(chDest); pchSpotSrc = chSrc + strlen(chSrc); dwCount = 0; while ( fnChild && !EverybodyBailOut) { fCopyIt = TRUE; if ( f1stPass ) { if ( (fnChild->dwStatus & STARTED) == STARTED ) { fCopyIt = FALSE; } } else { if ( (fnChild->dwStatus & COPIED) == COPIED ) { fCopyIt = FALSE; } } // // If the file doesn't exist on this thread's source location, then // don't try to copy it. // if ( (fnChild->dwStatus & mask) != mask ) { fCopyIt = FALSE; } if ( fCopyIt ) { // if ( f1stPass && (fnChild->dwStatus & STARTED) == STARTED ) { // fCopyIt = FALSE; // } else { // fnChild->dwStatus |= STARTED; // } // LeaveCriticalSection( &pcs ); } if ( fCopyIt ) { pch = pchSpotDest; strcpy( pch, fnChild->cFileName ); strcpy( pchSpotSrc, pchSpotDest ); if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY ) { CreateDirectory( chDest, NULL ); strcat( pchSpotDest, "\\" ); strcat( pchSpotSrc, "\\" ); dwCount += CopyThem( fnChild, chDest, chSrc, nThread, mask, f1stPass ); } else { fnChild->dwStatus |= STARTED; strcpy( chTemp, chDest ); *(chTemp+(pchSpotDest-chDest)) = '\0'; sprintf( chTempName, "suck%02lX.tmp", mask ); strcat( chTemp, chTempName ); // // Check if we need to copy this file // fCopy = CopyCheck( chDest, fnChild ); if ( fScriptMode ) { dwCount++; EnterCriticalSection( &pcs ); fnChild->dwStatus |= COPIED; if ( fCopy ) { if ( !fQuietMode ) { printf("copy %s %s\n", chSrc, chDest ); } dwTotalSizes[nThread-1] += fnChild->dwFileSizeLow; } else { if ( !fQuietMode ) { printf("rem copy %s %s\n", chSrc, chDest ); } } LeaveCriticalSection( &pcs ); } else { dwCount++; if ( fCopy ) { dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } fCopied = CopyFile( chSrc, chTemp, FALSE ); if ( !fCopying ) { EnterCriticalSection( &pcs ); if ( !fCopying ) { fCopying = TRUE; printf("Copying files...\n" ); } LeaveCriticalSection( &pcs ); } if ( !fCopied ) { dw = GetLastError(); printf("%s => %s\t[COPY ERROR %08lX]\n", chSrc, chTemp, dw ); dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } DeleteFile( chTemp ); switch ( dw ) { case ERROR_BAD_NETPATH: case ERROR_BAD_NET_NAME: if ( !fProblems[nThread-1] ) { printf("Error accesing %s, switching to silent attempts\n", chSrc ); fProblems[nThread-1] = TRUE; } Sleep( 10000 ); // Wait for 10 seconds break; default: break; } } else { EnterCriticalSection( &pcs ); if ( (fnChild->dwStatus & COPIED) == COPIED ) { // // Copy was done by somebody else // dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } fDeleted = DeleteFile( chTemp ); } else { // // Copy was done by us, attempt rename // fAttrib = TRUE; if ( fDestroy ) { dwAttribs = GetFileAttributes( chDest ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chDest, dwAttribs ); } } if ( !fAttrib ) { dw = GetLastError(); printf("%s => %s\t[ATTRIBUTE CHANGE ERROR %08lX(%s)\n", chSrc, chDest, dw, chDest ); dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } fDeleted = DeleteFile( chTemp ); } else { fDeleted = DeleteFile( chDest ); if ( !fDeleted ) { dw = GetLastError(); fnChild->dwStatus |= COPIED; } if ( fDeleted || dw == ERROR_FILE_NOT_FOUND ) { fRenamed = MoveFile( chTemp, chDest ); if ( fRenamed ) { fnChild->dwStatus |= COPIED; if ( !fQuietMode ) { // DavidP 23-Jan-1998: Allow multiple levels of quiet printf("%*s\r%s => %s\t[OK]%c", nConsoleWidth, "", chSrc, chDest, chLineEnd ); } dwTotalSizes[nThread-1] += fnChild->dwFileSizeLow; } else { dw = GetLastError(); printf("%s => %s\t[RENAME ERROR %08lX (%s)]\n", chSrc, chDest, dw, chTemp ); dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } fDeleted = DeleteFile( chTemp ); } } else { dw = GetLastError(); printf("%s => %s\t[DELETE ERROR %08lX (%s)]\n", chSrc, chDest, dw, chDest ); dwAttribs = GetFileAttributes( chTemp ); if ( dwAttribs & FILE_ATTRIBUTE_READONLY && dwAttribs != 0xFFFFFFFF ) { dwAttribs &= ~FILE_ATTRIBUTE_READONLY; fAttrib = SetFileAttributes( chTemp, dwAttribs ); } fDeleted = DeleteFile( chTemp ); } } } LeaveCriticalSection( &pcs ); } } else { EnterCriticalSection( &pcs ); if ( !fCopying ) { fCopying = TRUE; printf("Copying files...\n" ); } fnChild->dwStatus |= COPIED; if ( !fQuietMode ) { // DavidP 23-Jan-1998: Allow multiple levels of quiet // printf("%*s\r%s => %s\t[OK]%c", nConsoleWidth, "", chSrc, chDest, chLineEnd ); } LeaveCriticalSection( &pcs ); } } } *pchSpotDest = '\0'; *pchSpotSrc = '\0'; } fnChild = fnChild->fnSibling.mem_ptr; } return( dwCount ); } DWORD WINAPI ThreadFunction( LPVOID lpParameter ) { LPSTR lpSearch; DWORD mask; DWORD dw; CHAR chDest[MAX_PATH]; CHAR chSrc[MAX_PATH]; DWORD dwCount; dw = (DWORD)(DWORD_PTR)lpParameter; lpSearch = chPath[dw]; mask = dwMasks[dw-1]; EnumFiles( lpSearch, fnRoot, mask, dw-1 ); strcpy( chDest, chPath[0] ); strcpy( chSrc, chPath[dw] ); CopyThem( fnRoot, chDest, chSrc, dw, mask, TRUE ); strcpy( chDest, chPath[0] ); strcpy( chSrc, chPath[dw] ); do { dwCount = CopyThem( fnRoot, chDest, chSrc, dw, mask, FALSE ); } while ( dwCount != 0 && !EverybodyBailOut); EverybodyBailOut = TRUE; return( 0 ); } VOID EnumStraglers( LPSTR lpPath, FILENAME *fn, DWORD dwTotalMask ) { FILENAME *fnChild; CHAR NewName[MAX_PATH]; CHAR *pch; CHAR *pchSpot; pchSpot = lpPath + strlen(lpPath); fnChild = fn->fnChild.mem_ptr; while ( fnChild ) { pch = pchSpot; strcpy( pch, fnChild->cFileName ); if ( (fnChild->dwStatus & dwTotalMask) != dwTotalMask ) { if ( fLogTreeDifferences ) { printf( "File %s is not on all source locations\n", lpPath ); } nStraglers++; } if ( (fnChild->dwStatus & DIRECTORY) == DIRECTORY ) { strcat( pch, "\\" ); EnumStraglers( lpPath, fnChild, dwTotalMask ); } fnChild = fnChild->fnSibling.mem_ptr; } } VOID DumpStraglers( DWORD dwTotalMask ) { CHAR cPath[MAX_PATH]; strcpy( cPath, "\\" ); EnumStraglers( cPath, fnRoot, dwTotalMask ); if ( nStraglers != 0 ) { printf("Files found on some source locations, but not on others\n"); printf("Run SUCK with -x option to enumerate differences.\n"); } } void EnumDATFileData( FILENAME *fnParent, DWORD dwDiskPtr ) { FILENAME fnDiskName; FILENAME *fnChild = &fnDiskName; FILENAME *fnCurrent; int iSeek; int iCount; // // Read in this level from the DAT file // while ( dwDiskPtr != 0 ) { // Seek to this entry iSeek = fseek( SuckDATFile, dwDiskPtr, SEEK_SET ); if ( iSeek != 0 ) { printf("SUCK.DAT seek error, remove and restart\n"); exit(3); } // Read in this entry iCount = fread( (void *)fnChild, sizeof(FILENAME), 1, SuckDATFile ); if ( iCount != 1 ) { printf("SUCK.DAT read error, remove and restart\n"); exit(4); } #ifdef SEE_EM printf("Reading record [%s], at %08lX Child %08lX Sib %08lX\n", fnChild->cFileName, dwDiskPtr, fnChild->fnChild.disk_ptr, fnChild->fnSibling.disk_ptr ); printf("Size = %d\n", fnChild->dwFileSizeLow ); #endif // // Add this file node to the tree // fnCurrent = AllocateFileName(); fnCurrent->dwStatus = fnChild->dwStatus; fnCurrent->dwCopy = 0; fnCurrent->fnParent.mem_ptr = fnParent; fnCurrent->fnChild.mem_ptr = NULL; fnCurrent->fnSibling.mem_ptr = fnParent->fnChild.mem_ptr; fnCurrent->dwFileSizeLow = 0; fnCurrent->dwFileSizeHigh = 0; fnCurrent->ftFileTime = ftZero; fnCurrent->dwDATFileSizeLow = fnChild->dwFileSizeLow; fnCurrent->dwDATFileSizeHigh = fnChild->dwFileSizeHigh; fnCurrent->ftDATFileTime = fnChild->ftFileTime; fnCurrent->dwFileNameLen = fnChild->dwFileNameLen; strcpy( fnCurrent->cFileName, fnChild->cFileName ); fnParent->fnChild.mem_ptr = fnCurrent; if ( (fnCurrent->dwStatus & DIRECTORY) == DIRECTORY ) { nDirectories++; // // Load this directories children // EnumDATFileData( fnCurrent, fnChild->fnChild.disk_ptr ); } else { fnCurrent->dwStatus = 0; nFiles++; } // Move to next sibling at this level dwDiskPtr = fnChild->fnSibling.disk_ptr; } } void LoadFileTimesAndSizes( BOOL fUseSuckDATFile ) { CHAR cPath[MAX_PATH]; FILEHEADER fileheader; int iCount; // // Initialize the tree root // fnRoot = AllocateFileName(); fnRoot->fnParent.mem_ptr = NULL; fnRoot->fnChild.mem_ptr = NULL; fnRoot->fnSibling.mem_ptr = NULL; strcpy( fnRoot->cFileName, "" ); // Look for SUCK.DAT if ( fUseSuckDATFile ) { SuckDATFile = fopen( SUCK_DAT_FILE, "rb" ); } else { SuckDATFile = NULL; } if ( SuckDATFile != NULL ) { // // If file exists, then load the data from it. // printf("Loading Previous Statistics...\n"); iCount = fread( &fileheader, sizeof(fileheader), 1, SuckDATFile ); if ( iCount != 1 ) { printf("Error reading SUCK.DAT file, remove and restart\n"); exit(1); } EnumDATFileData( fnRoot, fileheader.fnRoot.disk_ptr ); fclose( SuckDATFile ); } } int EnumFileTimesAndSizes( DWORD dwDiskPtr, FILENAME *fn ) { FILENAME fnDiskName; FILENAME *fnChild = &fnDiskName; FILENAME *fnCurrent; VIRTPTR fnChildPtr; VIRTPTR fnSiblingPtr; int nRecords; int nChildren; int iCount; // // The 1st guy in the list will be at the end of the list // fnSiblingPtr.disk_ptr = 0; nRecords = 0; fnCurrent = fn->fnChild.mem_ptr; while ( fnCurrent ) { *fnChild = *fnCurrent; if ( (fnCurrent->dwStatus & DIRECTORY) == DIRECTORY ) { nChildren = EnumFileTimesAndSizes( dwDiskPtr, fnCurrent ); nRecords += nChildren; dwDiskPtr += nChildren * sizeof(FILENAME); if ( nRecords == 0 ) { fnChildPtr.disk_ptr = 0; } else { // Point to previous one, it was our child fnChildPtr.disk_ptr = dwDiskPtr - sizeof(FILENAME); } } else { fnChildPtr.disk_ptr = 0; } fnChild->fnChild.disk_ptr = fnChildPtr.disk_ptr; fnChild->fnSibling.disk_ptr = fnSiblingPtr.disk_ptr; fnSiblingPtr.disk_ptr = dwDiskPtr; #ifdef SEE_EM printf("Writing record [%s], at %08lX Child %08lX Sib %08lX\n", fnChild->cFileName, dwDiskPtr, fnChild->fnChild.disk_ptr, fnChild->fnSibling.disk_ptr ); printf("Size = %d\n", fnChild->dwFileSizeLow ); #endif iCount = fwrite( fnChild, sizeof(FILENAME), 1, SuckDATFile ); if ( iCount != 1 ) { printf("SUCK.DAT error writing data\n"); exit(1); } dwDiskPtr += sizeof(FILENAME); nRecords++; fnCurrent = fnCurrent->fnSibling.mem_ptr; } return( nRecords ); } VOID UpdateFileTimesAndSizes( VOID ) { CHAR cPath[MAX_PATH]; FILEHEADER fileheader; int iSeek; int iCount; DWORD dwDiskPtr; int nChildren; printf("Updating Statistics...\n"); SuckDATFile = fopen( SUCK_DAT_FILE, "wb+" ); if ( SuckDATFile == NULL ) { printf( "Error creating file '%s', update aborted\n", SUCK_DAT_FILE ); return; } fileheader.fnRoot.disk_ptr = 0; // Temporary... iCount = fwrite( &fileheader, sizeof(fileheader), 1, SuckDATFile ); if ( iCount != 1 ) { printf("SUCK.DAT error writing header\n"); exit(1); } dwDiskPtr = sizeof(fileheader); nChildren = EnumFileTimesAndSizes( dwDiskPtr, fnRoot ); dwDiskPtr += nChildren * sizeof(FILENAME); if ( nChildren == 0 ) { dwDiskPtr = 0; } else { dwDiskPtr -= sizeof(FILENAME); } fileheader.fnRoot.disk_ptr = dwDiskPtr; // Now update for real... iSeek = fseek( SuckDATFile, 0, SEEK_SET ); if ( iSeek != 0 ) { printf("SUCK.DAT error seeking to write header\n"); exit(3); } iCount = fwrite( &fileheader, sizeof(fileheader), 1, SuckDATFile ); if ( iCount != 1 ) { printf("SUCK.DAT error writing header\n"); exit(1); } fclose( SuckDATFile ); } CHAR *NewArgv[MAX_ARGS]; CHAR chCommand[MAX_COMMAND_LINE+1]; VOID LookForLastCommand( INT *pargc, CHAR **pargv[] ) { CHAR *pSpace; CHAR *pNextArg; GetPrivateProfileString("Init", "LastCommand", "", chCommand, MAX_COMMAND_LINE, SUCK_INI_FILE ); if ( strlen(chCommand) == 0 ) { return; } pNextArg = chCommand; *pargv = NewArgv; (*pargv)[1] = ""; *pargc = 1; do { (*pargc)++; pSpace = strchr( pNextArg, ' ' ); if ( pSpace ) { *pSpace = '\0'; } (*pargv)[(*pargc)-1] = pNextArg; pNextArg = pSpace + 1; } while ( pSpace != NULL ); } VOID UpdateLastCommandLine( INT argc, CHAR *argv[] ) { CHAR chLastCommand[MAX_COMMAND_LINE+1]; INT nArg; chLastCommand[0] = '\0'; nArg = 1; while ( nArg < argc ) { strcat( chLastCommand, argv[nArg] ); nArg++; if ( nArg != argc ) { strcat( chLastCommand, " " ); } } WritePrivateProfileString("Init", "LastCommand", chLastCommand, SUCK_INI_FILE ); } VOID ReplaceEnvironmentStrings( CHAR *pText ) { CHAR *pOpenPercent; CHAR *pClosePercent; CHAR chBuffer[MAX_PATH]; CHAR *pSrc; CHAR *pEnvString; chBuffer[0] = '\0'; pSrc = pText; do { pOpenPercent = strchr( pSrc, '%' ); if ( pOpenPercent == NULL ) { strcat( chBuffer, pSrc ); break; } pEnvString = pOpenPercent + 1; pClosePercent = strchr( pEnvString, '%' ); if ( pClosePercent == NULL ) { strcat( chBuffer, pSrc ); break; } if ( pEnvString == pClosePercent ) { strcat( chBuffer, "%" ); } else { *pOpenPercent = '\0'; *pClosePercent = '\0'; strcat( chBuffer, pSrc ); GetEnvironmentVariable( pEnvString, chBuffer + strlen(chBuffer), MAX_PATH ); } pSrc = pClosePercent+1; } while ( TRUE ); strcpy( pText, chBuffer ); } DWORD DiffTimes( SYSTEMTIME *start, SYSTEMTIME *end ) { DWORD nSecStart; DWORD nSecEnd; nSecStart = start->wHour*60*60 + start->wMinute*60 + start->wSecond; nSecEnd = end->wHour*60*60 + end->wMinute*60 + end->wSecond; return nSecEnd - nSecStart; } VOID Usage( VOID ) { fputs("SUCK: Usage suck [-options] [...]\n" " (maximum of 29 src directories)\n" "\n" " where options are: x - List source differences (if any)\n" " s - Produce script, don't copy\n" " q - Quiet mode (no stdout)\n" " p - Display progress on one line\n" " (cannot be used with -q or -s)\n" " z - Copy over readonly files\n" " e - Exclude directory\n" " e.g. -eidw -emstools\n" , stderr); } VOID ArgError( INT nArg, CHAR * pszArg ) { fprintf( stderr, "\nError in arg #%d - '%s'\n\n", nArg, pszArg ); Usage(); exit(1); } int __cdecl main( int argc, char *argv[] ) { HANDLE hThreads[MAX_THREADS]; DWORD dwThreadId; DWORD nThreads; INT nArg; DWORD nPaths; CHAR *pch; CHAR *pch2; DWORD dwTotalMask; OFSTRUCT ofs; BOOL fUpdateCommandLine = TRUE; CHAR * pchNextExclude = gExcludes; SYSTEMTIME stStart; SYSTEMTIME stEnd; DWORD nSeconds; DWORD nMinutes; // kenhia 15-Mar-1996: add support for -#: CHAR * PlatformPoundArray[] = PLATFORM_SPECIFIC_SHARES; DWORD nPound = 0; // DavidP 23-Jan-1998: Allow multiple levels of quiet DWORD dwConsoleMode = 0; BOOL fWasConsoleModeSet = FALSE; HANDLE hStdOut = NULL; if ( argc < 2 ) { LookForLastCommand( &argc, &argv ); fUpdateCommandLine = FALSE; } if ( argc < 3 ) { Usage(); exit(1); } nArg = 1; nPaths = 0; while ( nArg < argc ) { pch = argv[nArg]; if ( *pch == '-' ) { BOOL fExitSwitchLoop = FALSE; pch++; while ( *pch && !fExitSwitchLoop) { switch ( *pch ) { case 's': // DavidP 23-Jan-1998: Allow multiple levels of quiet if ( fProgressMode ) { ArgError( nArg, argv[nArg] ); } fScriptMode = TRUE; break; case 'q': // DavidP 23-Jan-1998: Allow multiple levels of quiet if ( fProgressMode ) { ArgError( nArg, argv[nArg] ); } fQuietMode = TRUE; break; case 'p': // DavidP 23-Jan-1998: Allow multiple levels of quiet if ( fQuietMode || fScriptMode ) { ArgError( nArg, argv[nArg] ); } fProgressMode = TRUE; chLineEnd = '\r'; break; case 'x': fLogTreeDifferences = TRUE; break; case 'y': fUpdateINIBase = TRUE; break; case 'z': fDestroy = TRUE; break; case 'e': if ( pchNextExclude - gExcludes + strlen(++pch) + 2 > MAX_EXCLUDES ) { ArgError( nArg, argv[nArg] ); } strcpy(pchNextExclude, pch); pchNextExclude += strlen(pchNextExclude)+1; *pchNextExclude = 0; fExitSwitchLoop = TRUE; break; // kenhia 15-Mar-1996: add support for -#: case '#': pch++; if ( *pch != ':' ) { Usage(); exit(1); } while ( PlatformPoundArray[nPound] ) { if ( nPaths >= MAX_THREADS ) { Usage(); exit(1); } strcpy( chPath[nPaths], "\\\\" ); strcat( chPath[nPaths], PlatformPoundArray[nPound] ); strcat( chPath[nPaths], "\\" ); strcat( chPath[nPaths], pch+1 ); pch2 = chPath[nPaths] + strlen(chPath[nPaths]) - 1; if ( *pch2 != '\\' && *pch2 != '/' && *pch2 != ':' ) { *++pch2 = '\\'; *++pch2 = '\0'; } ReplaceEnvironmentStrings( chPath[nPaths] ); nPound++; nPaths++; } fExitSwitchLoop = TRUE; break; default: Usage(); exit(1); } pch++; } } else { if ( nPaths >= MAX_THREADS ) { Usage(); exit(1); } strcpy( chPath[nPaths], argv[nArg] ); pch = chPath[nPaths] + strlen(chPath[nPaths]) - 1; if ( *pch != '\\' && *pch != '/' && *pch != ':' ) { *++pch = '\\'; *++pch = '\0'; } ReplaceEnvironmentStrings( chPath[nPaths] ); nPaths++; } nArg++; } nThreads = --nPaths; if ( nThreads == 0 ) { Usage(); exit(1); } // DavidP 23-Jan-1998: Allow multiple levels of quiet if ( fProgressMode ) { hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); if ( hStdOut != INVALID_HANDLE_VALUE ) { CONSOLE_SCREEN_BUFFER_INFO csbi; if ( GetConsoleScreenBufferInfo( hStdOut, &csbi ) ) { nConsoleWidth = csbi.dwSize.X - 1; } } fWasConsoleModeSet = GetConsoleMode( hStdOut, &dwConsoleMode ); if ( fWasConsoleModeSet ) { SetConsoleMode( hStdOut, dwConsoleMode & ~ENABLE_WRAP_AT_EOL_OUTPUT ); } } InitializeCriticalSection( &cs ); InitializeCriticalSection( &pcs ); printf("Streamlined Utility for Copying Kernel v1.1 (%d %s)\n", nThreads, (nThreads == 1 ? "thread" : "threads") ); GetSystemTime( &stStart ); LoadFileTimesAndSizes( fUseDAT ); dwTotalMask = 0; while ( nPaths ) { hThreads[nPaths-1] = CreateThread( NULL, 0L, ThreadFunction, (LPVOID)UlongToPtr(nPaths), 0, &dwThreadId ); dwTotalMask |= dwMasks[nPaths-1]; --nPaths; } WaitForMultipleObjects( nThreads, hThreads, TRUE, // WaitAll (DWORD)-1 ); // DavidP 23-Jan-1998: Allow multiple levels of quiet if ( fProgressMode ) { printf("%*s\r", nConsoleWidth, ""); if ( fWasConsoleModeSet ) { SetConsoleMode( hStdOut, dwConsoleMode ); } } printf("Copy complete, %ld file entries\n", nFiles+nDirectories); nPaths = 0; while ( nPaths < nThreads ) { printf("%11ld bytes from %s\n", dwTotalSizes[nPaths], chPath[nPaths+1] ); nPaths++; } nPaths = nThreads; while ( nPaths ) { nPaths--; CloseHandle( hThreads[nPaths] ); } DumpStraglers( dwTotalMask ); if ( fUpdateINIBase ) { UpdateFileTimesAndSizes(); } FreeFileNames(); DeleteCriticalSection( &cs ); DeleteCriticalSection( &pcs ); if ( fUpdateCommandLine ) { UpdateLastCommandLine( argc, argv ); } GetSystemTime( &stEnd ); nSeconds = DiffTimes( &stStart, &stEnd ); nMinutes = nSeconds / 60; nSeconds = nSeconds % 60; printf("Done, Elapsed time: %02d:%02d\n", nMinutes, nSeconds); return( 0 ); }