|
|
//+-------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 1996.
//
// File: ftc.cxx
//
// Contents: Fast multi-threaded tree copy program.
//
// History: ?-?-94 IsaacHe Created
// 11-Jun-96 BruceFo Fixed bugs, put this header here.
//
//--------------------------------------------------------------------------
#if defined( UNICODE )
#undef UNICODE
#endif
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <direct.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <conio.h>
#include <errno.h>
#include <process.h>
#include <ctype.h>
#define MAXQUEUE 10000
#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0]))
/*
* These are the attributes we use to compare for file attribute identity */ const DWORD FILE_ATTRIBUTE_MASK = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE;
class CProtectedLong { CRITICAL_SECTION _cs; LONG _value;
public: CProtectedLong() { InitializeCriticalSection( &_cs ); _value = 0; } LONG operator++(int) { EnterCriticalSection( &_cs ); LONG tmp = _value++; LeaveCriticalSection( &_cs ); return tmp; } LONG operator--( int ) { EnterCriticalSection( &_cs ); LONG tmp = _value--; LeaveCriticalSection( &_cs ); return tmp; } LONG operator+=( LONG incr ) { EnterCriticalSection( &_cs ); LONG tmp = (_value += incr ); LeaveCriticalSection( &_cs ); return tmp; } LONG operator=( LONG val ) { EnterCriticalSection( &_cs ); _value = val; LeaveCriticalSection( &_cs ); return val; } operator LONG() { return _value; } operator int() { return _value; } };
class CHandle { HANDLE _h;
public: CHandle() : _h(INVALID_HANDLE_VALUE) { } ~CHandle(); HANDLE operator=( HANDLE h ); BOOL operator==( HANDLE h ) { return (h == _h) ? TRUE : FALSE; } BOOL operator!=( HANDLE h ) { return (h != _h) ? TRUE : FALSE; } operator HANDLE() { return _h; } };
CHandle::~CHandle() { if( _h != INVALID_HANDLE_VALUE && _h != NULL ) CloseHandle( _h ); }
HANDLE CHandle::operator=( HANDLE h ) { if( _h != INVALID_HANDLE_VALUE && _h != NULL ) CloseHandle( _h ); return _h = h; }
DWORD dwElapsedTime; // time we've been copying data
CProtectedLong ulTotalBytesCopied; // running total count of bytes
CProtectedLong ulTotalBytesSkipped; // obvious?
CProtectedLong ulTotalBytesScanned; CProtectedLong nFilesOnQueue; // number of files to copy or examine
CProtectedLong nFcopy; // number of files copied
CProtectedLong nSkipped; // number of files skipped over
CProtectedLong nMappedCopy; // number of files copied using MapFile...
CProtectedLong nCopyFile; // number of files copied using CopyFile()...
CProtectedLong nInProgress; // number of copies currently in progress
CProtectedLong MaxThreads; // Max number of threads for copying
DWORD ExitCode = 0; // each thread's exit code
BOOL bThreadStop = FALSE; // are we trying to exit?
BOOL bWorkListComplete = FALSE; // have we scanned all the directories yet?
BOOL tFlag = FALSE; // only copy if newer
BOOL iFlag = FALSE; // skip seemingly identical files
BOOL rFlag = FALSE; // replace read-only files
BOOL vFlag = FALSE; // verbose
BOOL AFlag = FALSE; // keep going even if there are errors
BOOL FFlag = FALSE; // just produce file list. No copies
BOOL oFlag = TRUE; // should we overwrite files already at dest?
BOOL wFlag = FALSE; // should we wait for the source to show up?
BOOL qFlag = FALSE; // quiet mode?
BOOL yFlag = FALSE; // no recurse on target?
BOOL zFlag = FALSE; // no recurse on source?
BOOL pFlag = FALSE; // pattern?
CHAR szPattern[100]; // pattern string, if pFlag is TRUE
struct WorkList // copy file at 'src' to 'dest'
{ struct WorkList *next; char *src; // Pathname relative to the source
WIN32_FIND_DATA srcfind; char *dest; } *WorkList = NULL; struct WorkList* WorkListTail = NULL; // always add files to copy to the *tail*
// of the work list. This is to make
// sure we always do work in order, instead
// of starting on a directory but finishing
// it much much much later, because we've
// pushed all the work to the deep tail of
// the list and never returned to it!
CHandle hWorkAvailSem; // signalled whenever there's work on the list
CHandle hMaxWorkQueueSem; // used to control lenght of work queue
CRITICAL_SECTION csMsg; // used to serialize screen output
CRITICAL_SECTION csWorkList; // used when manipulating the linked list
CRITICAL_SECTION csSourceList; // used when manipulating the source list
struct SourceList { char *name; // pathname of the source. Ends in '\'
LONG count; // number of files currently being copied
LONG ulTotalFiles; // total for the entire copy
struct { unsigned valid : 1; // do we know that the source is valid?
} flags; } SourceList[ 20 ]; int MaxSources = 0;
char *DirectoryExcludeList[ 50 ]; int MaxDirectoryExcludes = 0;
char OldConsoleTitle[ 100 ];
void __cdecl errormsg( char const *pszfmt, ... ) { va_list ArgList; va_start( ArgList, pszfmt );
if( bThreadStop == FALSE && pszfmt != NULL ) { EnterCriticalSection( &csMsg ); vprintf( pszfmt, ArgList ); LeaveCriticalSection( &csMsg ); }
va_end( ArgList ); }
DWORD __stdcall StatusWorker( void *arg ) { char ostatbuf[ 100 ]; char nstatbuf[ 100 ];
while( bThreadStop == FALSE ) { ULONG Remaining = (int)ulTotalBytesScanned - (int)ulTotalBytesCopied - (int)ulTotalBytesSkipped; if (Remaining < 1000) { sprintf(nstatbuf, "Remaining Files %d Bytes %d", (int)nFilesOnQueue, Remaining); } else if (Remaining < 1000000) { sprintf(nstatbuf, "Remaining Files %d Bytes %d,%03.3d", (int)nFilesOnQueue, Remaining / 1000, Remaining % 1000); } else if (Remaining < 1000000000) { sprintf(nstatbuf, "Remaining Files %d Bytes %d,%03.3d,%03.3d", (int)nFilesOnQueue, Remaining / 1000000, (Remaining / 1000) % 1000, Remaining % 1000, Remaining); } else { sprintf(nstatbuf, "Remaining Files %d Bytes %d,%03.3d,%03.3d,%03.3d", (int)nFilesOnQueue, Remaining / 1000000000, (Remaining / 1000000) % 1000, (Remaining / 1000) % 1000, Remaining % 1000); }
if( strcmp( ostatbuf, nstatbuf ) ) { SetConsoleTitle( nstatbuf ); strcpy( ostatbuf, nstatbuf ); } Sleep( 1 * 1000 ); }
SetConsoleTitle( OldConsoleTitle ); ExitThread( ExitCode ); arg = arg; return 0; }
void __cdecl msg( char const *pszfmt, ... ) { if( qFlag ) return;
va_list ArgList; va_start( ArgList, pszfmt );
EnterCriticalSection( &csMsg ); vprintf( pszfmt, ArgList ); LeaveCriticalSection( &csMsg ); va_end( ArgList ); }
void __cdecl errorexit (char const *pszfmt, ... ) {
if( bThreadStop == FALSE && pszfmt != NULL ) { va_list ArgList; va_start( ArgList, pszfmt );
EnterCriticalSection( &csMsg ); vprintf( pszfmt, ArgList ); LeaveCriticalSection( &csMsg );
va_end( ArgList ); }
if( AFlag == FALSE ) { bThreadStop = TRUE;
EnterCriticalSection( &csWorkList ); WorkList = NULL; WorkListTail = NULL; LeaveCriticalSection( &csWorkList );
if( hWorkAvailSem != NULL ) ReleaseSemaphore( hWorkAvailSem, (int)MaxThreads+1, NULL );
if( hMaxWorkQueueSem != NULL ) ReleaseSemaphore( hMaxWorkQueueSem, 1, NULL );
SetConsoleTitle( OldConsoleTitle ); ExitThread (ExitCode = 1); } }
DWORD fcopy( char *src, WIN32_FIND_DATA *srcfind, char *dst, char *errorbuf ) { CHandle srcfh; CHandle dstfh; CHandle hsrc; DWORD nBytesWritten, totalbytes; char *result = NULL; char *psrc; BOOL ret = TRUE; char dostatus = 0; DWORD errcode;
*errorbuf = '\0';
if( srcfind->nFileSizeHigh != 0 ) { if( CopyFile( src, dst, TRUE ) == FALSE ) { errcode = GetLastError(); sprintf( errorbuf, "CopyFile failed, error %d", errcode ); return errcode; } nCopyFile++;
} else {
srcfh = CreateFile( src, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if( srcfh == INVALID_HANDLE_VALUE ) { errcode = GetLastError(); sprintf( errorbuf, "Unable to open source file, error %d", errcode); return errcode; }
dstfh = CreateFile( dst, GENERIC_WRITE, FILE_SHARE_WRITE,NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, srcfh);
if( dstfh == INVALID_HANDLE_VALUE ) { errcode = GetLastError(); sprintf( errorbuf, "Unable to create dest file, error %d", errcode); return errcode; }
if( srcfind->nFileSizeLow != 0 ) { hsrc = CreateFileMapping( srcfh, NULL, PAGE_READONLY, 0, srcfind->nFileSizeLow, NULL ); if( hsrc == NULL ) { dstfh = INVALID_HANDLE_VALUE; DeleteFile( dst ); if( CopyFile( src, dst, TRUE ) == FALSE ) { errcode = GetLastError(); sprintf( errorbuf, "Unable to create file mapping, and CopyFile failed, error %d", errcode ); return errcode; } nCopyFile++; ulTotalBytesCopied += srcfind->nFileSizeLow; goto DoTime; } if( (psrc = (char *)MapViewOfFile( hsrc, FILE_MAP_READ, 0, 0, 0 )) == NULL){ dstfh = INVALID_HANDLE_VALUE; DeleteFile( dst ); if( CopyFile( src, dst, TRUE ) == FALSE ) { errcode = GetLastError(); sprintf( errorbuf, "Unable to map source file, and CopyFile failed: error %d", errcode ); return errcode; } nCopyFile++; ulTotalBytesCopied += srcfind->nFileSizeLow; goto DoTime; } totalbytes = 0; while( !bThreadStop && totalbytes < srcfind->nFileSizeLow && ret == TRUE ) { ret = WriteFile( dstfh, psrc + totalbytes, min( 64*1024, srcfind->nFileSizeLow - totalbytes ), &nBytesWritten, NULL ); totalbytes += nBytesWritten; ulTotalBytesCopied += nBytesWritten; } errcode = GetLastError(); UnmapViewOfFile( psrc );
if( bThreadStop == TRUE ) { dstfh = INVALID_HANDLE_VALUE; DeleteFile( dst ); *errorbuf = '\0'; return errcode; }
if( ret == FALSE ) { dstfh = INVALID_HANDLE_VALUE; DeleteFile( dst ); if( CopyFile( src, dst, TRUE ) == FALSE ) { errcode = GetLastError(); sprintf( errorbuf, "%s: CopyFile failed: error %d", dst, errcode); return GetLastError(); } nCopyFile++; ulTotalBytesCopied += srcfind->nFileSizeLow; goto DoTime; }
nMappedCopy++; } }
DoTime: if( dstfh == INVALID_HANDLE_VALUE ) { dstfh = CreateFile( dst, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); }
if( dstfh == INVALID_HANDLE_VALUE ) { errcode = GetLastError(); DeleteFile( dst ); sprintf( errorbuf, "Unable to open destination file to set time, error %d", errcode ); return errcode; }
if( !SetFileTime( dstfh, &srcfind->ftCreationTime, &srcfind->ftLastAccessTime, &srcfind->ftLastWriteTime )) { errcode = GetLastError(); sprintf( errorbuf, "Unable to set destination file times, error %d\n", errcode ); return errcode; }
nFcopy++; return 0; }
/*
* Pick the source having the fewest outstanding operations at the moment. */ int SelectSource() { int index = -1; struct SourceList *psl; struct SourceList *opsl; static min;
EnterCriticalSection( &csSourceList );
//
// Find the first valid source
//
for( opsl = SourceList; opsl < &SourceList[ MaxSources ]; opsl++ ) if( opsl->flags.valid == TRUE ) break;
//
// Now locate the source having the fewest pending operations right now
//
for( psl = opsl+1; psl < &SourceList[ MaxSources ]; psl++ ) { if( psl->flags.valid == TRUE && psl->count < opsl->count ) opsl = psl; }
if( opsl->flags.valid == TRUE ) { opsl->count++; index = (int)(opsl - SourceList); }
LeaveCriticalSection( &csSourceList );
return index; }
/*
* We've completed the operation on source 'index' */ void SourceCopyComplete( int index, BOOL fFile ) { EnterCriticalSection( &csSourceList ); SourceList[ index ].count--; if (fFile) SourceList[ index ].ulTotalFiles++; LeaveCriticalSection( &csSourceList ); }
void DisableSource( int index ) { if( index >= 0 && index < MaxSources ) { EnterCriticalSection( &csSourceList ); if( SourceList[ index ].flags.valid == TRUE ) { errormsg( "Disabling %s\n", SourceList[ index ].name ); SourceList[ index ].flags.valid = FALSE; } LeaveCriticalSection( &csSourceList ); } } BOOL FileTimesEqual( CONST FILETIME *pt1, CONST FILETIME *pt2 ) { SYSTEMTIME s1, s2;
if( !FileTimeToSystemTime( pt1, &s1 ) || !FileTimeToSystemTime( pt2, &s2 ) ) return FALSE;
return s1.wHour == s2.wHour && s1.wMinute == s2.wMinute && s1.wMonth == s2.wMonth && s1.wDay == s2.wDay && s1.wYear == s2.wYear; } void PrintFileTime( char *str, CONST FILETIME *ft ) { SYSTEMTIME st;
if( FileTimeToSystemTime( ft, &st ) == FALSE ) { errormsg( "????\n" ); return; }
msg( "%s %u:%u.%u.%u %u/%u/%u\n", str, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, st.wMonth, st.wDay, st.wYear ); }
DWORD __stdcall ThreadWorker( void *arg ) { struct WorkList *pdl = NULL; HANDLE hdestfind; WIN32_FIND_DATA destfind; int index = 0; char errorbuf[ 100 ]; char pathbuf[ MAX_PATH ]; DWORD errcode;
MaxThreads++;
while( 1 ) { if( pdl != NULL ) { free( pdl->src ); free( pdl->dest ); free( pdl ); pdl = NULL; }
if( bThreadStop == TRUE ) break;
// Poll for new stuff every 2 seconds. If the thread is set to stop,
// then go away.
DWORD dwWait; while( 1 ) { dwWait = WaitForSingleObject( hWorkAvailSem, 1000 ); if( dwWait == WAIT_OBJECT_0 ) { break; }
if( dwWait == WAIT_TIMEOUT ) { if( bThreadStop == TRUE ) break; } else { errormsg( "Thread %p: Semaphore wait failed\n", arg ); break; } } if ( dwWait != WAIT_OBJECT_0 ) { break; }
// pick an item off the head of the work list
EnterCriticalSection( &csWorkList ); pdl = WorkList; if( pdl != NULL ) { WorkList = pdl->next; if (NULL == WorkList) { // just pulled off the tail entry
WorkListTail = NULL; } } LeaveCriticalSection( &csWorkList ); ReleaseSemaphore( hMaxWorkQueueSem, 1, NULL );
if( pdl == NULL ) break;
nFilesOnQueue--;
if( pdl->srcfind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { errormsg( "Logic Error: Directory on work list!\n" ); continue; }
pdl->srcfind.dwFileAttributes &= FILE_ATTRIBUTE_MASK;
hdestfind = FindFirstFile( pdl->dest, &destfind ); if( hdestfind != INVALID_HANDLE_VALUE ) { FindClose( hdestfind ); destfind.dwFileAttributes &= FILE_ATTRIBUTE_MASK;
/*
* Destination file exists. What should we do? */
if( oFlag == FALSE ) { /*
* We should not overwrite the existing file at the dest */ if( vFlag ) msg( "%s [SKIP: exists]\n", pdl->dest ); ulTotalBytesSkipped += destfind.nFileSizeLow; nSkipped++; continue; }
if( iFlag && vFlag ) {
if( destfind.dwFileAttributes !=pdl->srcfind.dwFileAttributes) msg( "%s [ ATTRIBUTES differ ]\n", pdl->dest ); if(!FileTimesEqual( &destfind.ftLastWriteTime, &pdl->srcfind.ftLastWriteTime)) { EnterCriticalSection( &csMsg ); msg( "%s [ TIMES differ ]\n", pdl->dest ); PrintFileTime( "Dest: ", &destfind.ftLastWriteTime ); PrintFileTime( "Src: ", &pdl->srcfind.ftLastWriteTime ); LeaveCriticalSection( &csMsg ); } if( (destfind.nFileSizeHigh != pdl->srcfind.nFileSizeHigh) || (destfind.nFileSizeLow != pdl->srcfind.nFileSizeLow) ) msg( "%s [ SIZES differ ]\n", pdl->dest ); }
if( iFlag && (destfind.dwFileAttributes == pdl->srcfind.dwFileAttributes) && FileTimesEqual( &destfind.ftLastWriteTime, &pdl->srcfind.ftLastWriteTime) && (destfind.nFileSizeHigh == pdl->srcfind.nFileSizeHigh) && (destfind.nFileSizeLow == pdl->srcfind.nFileSizeLow) ) { if( vFlag ) msg("%s [SKIP: same atts, time, size]\n",pdl->dest); ulTotalBytesSkipped += destfind.nFileSizeLow; nSkipped++; continue; }
if( tFlag && CompareFileTime( &destfind.ftLastWriteTime, &pdl->srcfind.ftLastWriteTime) >= 0 ) { if( vFlag ) msg("%s [SKIP: same or newer time]\n", pdl->dest ); ulTotalBytesSkipped += destfind.nFileSizeLow; nSkipped++; continue; }
if( destfind.dwFileAttributes & FILE_ATTRIBUTE_READONLY ) if( rFlag == FALSE && bThreadStop == FALSE ) { if( vFlag ) msg( "%s [SKIP: readonly]\n", pdl->dest ); ulTotalBytesSkipped += destfind.nFileSizeLow; nSkipped++; continue; }
/*
* Delete the destination file */ if( destfind.dwFileAttributes & FILE_ATTRIBUTE_READONLY ) { destfind.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes( pdl->dest, destfind.dwFileAttributes ); }
if( FFlag == FALSE ) (void)DeleteFile( pdl->dest );
}
if( FFlag == FALSE ) { while( bThreadStop == FALSE && (index = SelectSource()) >= 0 ) { strcpy( pathbuf, SourceList[index].name ); strcat( pathbuf, pdl->src );
nInProgress++; errcode = fcopy( pathbuf,&pdl->srcfind,pdl->dest,errorbuf); nInProgress--;
if( errcode == 0 ) { SourceCopyComplete( index, TRUE ); msg("%s -> %s [OK]\n", pathbuf, pdl->dest ); SetFileAttributes(pdl->dest,pdl->srcfind.dwFileAttributes); break; }
if( errcode == ERROR_SWAPERROR ) { errormsg( "%s [ SWAP ERROR, will try again... ]\n",pathbuf); Sleep( 5 * 1000 * 60 ); continue; } if( bThreadStop == FALSE ) errormsg( "%s [FAILED: %s ]\n", pathbuf, errorbuf ); if( AFlag == TRUE ) break; DisableSource( index ); } } else { msg( "%s\n", pdl->dest ); }
if( AFlag == FALSE && index < 0 ) errorexit( "%s [FAILED completely]\n", pdl->dest ); }
if( pdl != NULL ) { free( pdl->src ); free( pdl->dest ); free( pdl ); }
if( MaxThreads-- == 1 ) { if( ExitCode == 0 ) { dwElapsedTime = GetTickCount() - dwElapsedTime; dwElapsedTime /= 1000; BOOL oldqFlag = qFlag; qFlag = FALSE; msg( "%u files copied (%u memory mappped, %u CopyFile )\n", (int)nFcopy, (int)nMappedCopy, (int)nCopyFile ); msg( "%u files skipped\n", (int)nSkipped); msg( "%lu bytes in %u seconds: %lu bits/sec\n", (int)ulTotalBytesCopied, dwElapsedTime, dwElapsedTime ? (LONG)(((LONG)ulTotalBytesCopied*8L)/dwElapsedTime) : 0L );
qFlag = oldqFlag; EnterCriticalSection( &csSourceList ); for (int i = 0; i < MaxSources; i++) { if (TRUE == SourceList[i].flags.valid) { msg( "%s %5lu files\n", SourceList[i].name, SourceList[i].ulTotalFiles); } } LeaveCriticalSection( &csSourceList ); } SetConsoleTitle( OldConsoleTitle ); ExitProcess( ExitCode ); }
ExitThread( ExitCode ); return 0; }
void AddToWorkList( char *src, char *dest, WIN32_FIND_DATA *pfind ) { struct WorkList *pdl;
if( WaitForSingleObject( hMaxWorkQueueSem, INFINITE ) != WAIT_OBJECT_0 ) { errormsg( "Semaphore wait failed, can't add to work list\n" ); return; }
if( bThreadStop == TRUE ) return;
if( (pdl = (struct WorkList *)malloc( sizeof( struct WorkList ) ) ) == NULL ){ errorexit( "Out of Memory!\n" ); return; }
if( (pdl->dest = _strdup( dest )) == NULL ) { errorexit( "Out of memory!\n" ); free( pdl ); return; }
if( (pdl->src = _strdup( src )) == NULL ) { errorexit( "Out of memory!\n" ); free( pdl->dest ); free( pdl ); return; }
pdl->srcfind = *pfind; pdl->next = NULL;
EnterCriticalSection( &csWorkList ); if (NULL == WorkList) { WorkListTail = WorkList = pdl; } else { WorkListTail->next = pdl; // point the tail to the new entry
WorkListTail = pdl; // the new entry becomes the tail
} LeaveCriticalSection( &csWorkList );
nFilesOnQueue++; ReleaseSemaphore( hWorkAvailSem, 1, NULL ); }
void ScanDirectory( char *relpath, char *dest );
void ScanDirectoryHelp( char *relpath, // path relative to the source
char *dest, // resulting destination directory
BOOL fOnlyDirectories, // TRUE if we only want to look for dirs
BOOL fOnlyFiles // TRUE if we only want to look for files
) { int index; int destlen = strlen( dest ); int rellen = strlen( relpath ); WIN32_FIND_DATA fbuf; HANDLE hfind = INVALID_HANDLE_VALUE; char SourceName[ MAX_PATH ];
while( 1 ) { if( (index = SelectSource()) < 0 ) return; /*
* FindFirst/Next is such low overhead on the server that we shouldn't * really count it as a load on the server... */ SourceCopyComplete( index, FALSE );
strcpy( SourceName, SourceList[ index ].name ); if( rellen ) { strcat( SourceName, relpath ); strcat( SourceName, "\\" ); }
strcat( SourceName, fOnlyDirectories ? "*.*" : (pFlag ? szPattern : "*.*" ) );
hfind = FindFirstFile( SourceName, &fbuf ); if( hfind != INVALID_HANDLE_VALUE ) break;
if (pFlag && ERROR_FILE_NOT_FOUND == GetLastError()) { // simply no files that match the pattern in the directory
return; }
errormsg( "Dir scan of %s failed, error %d [DISABLING]\n", SourceName, GetLastError() ); DisableSource( index ); }
do { if( !strcmp( fbuf.cFileName, "." ) || !strcmp( fbuf.cFileName, ".." ) ) continue;
sprintf( &dest[ destlen ], "%s%s", dest[destlen-1] == '\\' ? "" : "\\", fbuf.cFileName );
if((fbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ) { //
// Not a directory
//
if (fOnlyDirectories) { continue; }
/*
* Queue this file into the work queue! */ if( rellen ) { strcpy( SourceName, relpath ); strcat( SourceName, "\\" ); } else SourceName[0] = '\0';
strcat( SourceName, fbuf.cFileName ); ulTotalBytesScanned += fbuf.nFileSizeLow; AddToWorkList( SourceName, dest, &fbuf ); continue; }
/*
* We've found a directory. Descend into it and scan (if we're * not excluding it) */
if (fOnlyFiles) { continue; }
if (zFlag) { // no recurse on source: ignore it!
continue; }
for( int i=0; i < MaxDirectoryExcludes; i++ ) if( !_stricmp( DirectoryExcludeList[i], fbuf.cFileName ) ) break;
if( i != MaxDirectoryExcludes ) { if( vFlag ) msg( "Directory: %s [EXCLUDED]\n", dest ); nSkipped++; continue; }
sprintf( &relpath[ rellen ], "%s%s", rellen ? "\\":"", fbuf.cFileName ); if (yFlag) { // no recurse on target: nuke end of dest (the new dir)
dest[ destlen ] = '\0'; } ScanDirectory( relpath, dest ); relpath[ rellen ] = '\0';
} while( !bThreadStop && FindNextFile( hfind, &fbuf ) == TRUE );
dest[ destlen ] = '\0';
FindClose( hfind ); }
void ScanDirectory( char *relpath, // path relative to the source
char *dest // resulting destination directory
) { DWORD dwattrs;
if( (dwattrs = GetFileAttributes( dest )) == 0xFFFFFFFF ) { msg( "Creating Directory: %s\n", dest ); if( FFlag == FALSE && CreateDirectory( dest, NULL ) == FALSE ) { errorexit( "Can not create directory: %s\n", dest ); return; }
} else if( !(dwattrs & FILE_ATTRIBUTE_DIRECTORY) ) { errorexit( "Not a directory: %s\n", dest ); return; }
if (pFlag) { // two passes: one looking for files, one looking for directories
ScanDirectoryHelp(relpath, dest, FALSE, TRUE); ScanDirectoryHelp(relpath, dest, TRUE, FALSE); } else { ScanDirectoryHelp(relpath, dest, FALSE, FALSE); } }
static void appendslash( char *p ) { if( p[ strlen(p) - 1 ] != '\\' ) strcat( p, "\\" ); }
BOOL rootpath( char *src, char *dst ) { char* FilePart; char *p;
if( src == NULL || *src == '\0' ) return FALSE;
if( GetFullPathName( src, MAX_PATH, dst, &FilePart ) == 0 ) return FALSE;
p = src + strlen(src) - 1; if( *p == '.' ) if( p > src ) { p--; if( *p != '.' && *p != ':' && (*p == '\\' || *p == '/') ) strcat( dst, "." ); }
return TRUE; }
static void Usage( char *s ) { errormsg( "Usage: %s [flags] [-p pattern] [src ...] dest\n", s ); errormsg( "Flags:\n" );
errormsg( "\t-i Skip seemingly identical files (time, attrs, size agree)\n" ); errormsg( "\t-l Execute at lower priority\n" ); errormsg( "\t-o Do not overwrite any files that are already at dest\n" ); errormsg( "\t-p pattern Only files matching the pattern are copied\n" ); errormsg( "\t-q Quiet mode\n" ); errormsg( "\t-r Overwrite read-only files at dest\n" ); errormsg( "\t-t Copy only newer files to dest\n" ); errormsg( "\t-v Verbose\n" ); errormsg( "\t-y Don't recurse on target\n" ); errormsg( "\t-z Don't recurse on source\n" ); errormsg( "\t-A Keep going even if there are errors\n" ); errormsg( "\t-F Don't actually copy files or create directories\n" ); errormsg( "\t~dir Skip any directory named 'dir'\n" ); errormsg( "\nIf environment variable FTC_PARANOID is set, then the meaning of -o is\n" ); errormsg( "reversed: no -o means don't overwrite, -o means go ahead and overwrite.\n" );
errormsg( "\nExamples:\n" ); errormsg( " Copy from two sources, no 'obj' dir: ftc ~obj \\\\foo\\dir \\\\bar\\dir dest\n" ); ExitProcess( 1 ); }
BOOL __stdcall ControlHandlerRoutine( DWORD dwCtrlType ) { msg( "Interrupted!\n" ); bThreadStop = TRUE; ExitCode = 1; return TRUE; }
int __cdecl main(int argc, char *argv[]) { char *p; SECURITY_ATTRIBUTES sa; int i, argno; DWORD IDThread; char dest[ MAX_PATH ]; char relpath[ MAX_PATH ]; BOOL lFlag = FALSE; // low priority?
BOOL fParanoid = FALSE; // is FTC_PARANOID set in the environment?
SYSTEM_INFO si; CHandle CThread;
InitializeCriticalSection( &csMsg ); InitializeCriticalSection( &csWorkList ); InitializeCriticalSection( &csSourceList ); ZeroMemory(&si, sizeof( si )); GetConsoleTitle( OldConsoleTitle, sizeof( OldConsoleTitle ) );
TCHAR szParanoid[100]; DWORD len = GetEnvironmentVariable(TEXT("FTC_PARANOID"), szParanoid, ARRAYLEN(szParanoid)); if (len > 0) { fParanoid = TRUE; }
if (fParanoid) { oFlag = FALSE; } else { oFlag = TRUE; }
for( argno = 1; argno < argc && (argv[argno][0] == '-' || argv[argno][0] == '/' || argv[argno][0] == '~') ; argno++ ) { if( argv[argno][0] == '~' ) { DirectoryExcludeList[ MaxDirectoryExcludes++ ] = &argv[argno][1];
} else for( int j=1; argv[argno][j]; j++ ) { switch( argv[argno][j] ) { case 'l': lFlag = TRUE; break; case 'q': qFlag = TRUE; break; case 'y': yFlag = TRUE; break; case 'z': zFlag = TRUE; break; case 'w': wFlag = TRUE; break; case 'o': if (fParanoid) { oFlag = TRUE; } else { oFlag = FALSE; } break; case 'F': FFlag = TRUE; break; case 'A': AFlag = TRUE; break; case 'p': if( j == 1 ) { if ( strcmp( &argv[argno][j], "p" ) == 0 ) { if (argno + 1 < argc) { pFlag = TRUE; strcpy( szPattern, argv[++argno] ); goto nextarg; // go to next argument
} else Usage( argv[0] ); } else Usage( argv[0] ); } else Usage( argv[0] ); break; case 'v': vFlag = TRUE; break; case 'r': rFlag = TRUE; break; case 'i': iFlag = TRUE; break; case 't': tFlag = TRUE; break; default: case '?': Usage( argv[0] ); break;
} }
nextarg: ;
}
for( ; argno < argc-1; argno++ ) { if( rootpath( argv[argno], dest ) == FALSE ) { errorexit( "invalid source\n" ); ExitProcess(1); } for( p = dest; *p; p++ ) if( *p == '/' ) *p = '\\'; if( (SourceList[ MaxSources ].name = (char *)malloc( strlen( dest ) + 2 )) == NULL ) { errorexit( "Out of memory!\n" ); ExitProcess(1); } strcpy( SourceList[ MaxSources++ ].name, dest ); }
if( MaxSources == 0 ) Usage( argv[0] );
for( i=0; i < MaxDirectoryExcludes; i++ ) msg( "Exclude Directory: %s\n", DirectoryExcludeList[i] );
while( 1 ) { char statusbuffer[ MAX_PATH ];
for( i=0; i < MaxSources; i++ ) { appendslash( SourceList[i].name ); if( vFlag == TRUE || wFlag == FALSE ) msg( "Validating %s....", SourceList[i].name ); sprintf( statusbuffer, "Validating %s", SourceList[i].name ); SetConsoleTitle( statusbuffer ); if( GetFileAttributes( SourceList[i].name ) == 0xFFFFFFFF ) { if( vFlag == TRUE || wFlag == FALSE ) msg( "[DISABLING %s]\n", SourceList[i].name ); SourceList[i].flags.valid = FALSE; } else { SourceList[i].flags.valid = TRUE; if( vFlag == TRUE || wFlag == FALSE ) msg( "[OK]\n" ); } }
for( i=0; i < MaxSources; i++ ) if( SourceList[i].flags.valid == TRUE ) break;
if( i != MaxSources ) break;
if( wFlag == TRUE ) { SetConsoleTitle( "Sleeping awhile..." ); Sleep( 3 * 1000 * 60 ); } else { SetConsoleTitle( OldConsoleTitle ); ExitProcess(1); } }
SetConsoleTitle( "Sources Present" ); LONG cThreads = (MaxSources * 3) + 1;
/*
* hack for ftc -w -678 to exit when the release shares are available */ if( argno == argc && wFlag ) ExitProcess( 0 );
if ( argno != argc - 1 ) { Usage( argv[0] ); } else if (rootpath (argv[argno], dest) == FALSE ) { errorexit( "Invalid destination\n" ); ExitProcess(1); }
for( p = dest; *p; p++ ) if( *p == '/' ) *p = '\\';
for( i=0; i < MaxSources; i++ ) if (!strcmp(SourceList[i].name, dest)) { errorexit("Source == dest == %s", SourceList[i].name ); ExitThread(1); }
/*
* Create the semaphores for the work lists */ sa.nLength = sizeof( sa ); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; if( (hWorkAvailSem = CreateSemaphore( &sa, 0, 100000, NULL)) == NULL ) { errorexit( "Unable to create semaphore (err %u)!\n", GetLastError() ); ExitProcess(1); }
hMaxWorkQueueSem = CreateSemaphore( &sa, MAXQUEUE, MAXQUEUE, NULL ); if( hMaxWorkQueueSem == NULL ) { errorexit( "Unable to create queue length semaphore (err %u)!\n", GetLastError() ); ExitProcess( 1 ); }
/*
* Create the thread pool to do the copies */ for( i=0; i < cThreads - 1; i++ ) { CThread = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0, ThreadWorker, (LPVOID *)IntToPtr(i), 0, &IDThread ); if( CThread == NULL || CThread == INVALID_HANDLE_VALUE ) break; SetThreadPriority( CThread, THREAD_PRIORITY_NORMAL ); CThread = INVALID_HANDLE_VALUE; }
/*
* Create the 'update status' thread */ CThread = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0, StatusWorker,(LPVOID *)0,0,&IDThread ); if( CThread != NULL && CThread != INVALID_HANDLE_VALUE ) { // SetThreadPriority( CThread, THREAD_PRIORITY_BELOW_NORMAL );
CThread = INVALID_HANDLE_VALUE; }
SetConsoleCtrlHandler( ControlHandlerRoutine, TRUE );
/*
* Produce the directory list */ relpath[0] = '\0'; dwElapsedTime = GetTickCount();
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL ); ScanDirectory( relpath, dest );
if( lFlag ) SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS );
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL );
// OK, now hWorkAvailSem has a count for every item to copy. But when they
// finish copying, each thread waits on this semaphore again. At the end,
// everyone will still be waiting! So, add the number of threads to the
// count, so each thread notices, one by one, that everything's done.
ReleaseSemaphore( hWorkAvailSem, (int)cThreads+1, NULL );
if( bThreadStop == FALSE ) ThreadWorker( 0 );
return ExitCode; }
|