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.
1267 lines
39 KiB
1267 lines
39 KiB
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
}
|