windows-nt/Source/XPSP1/NT/base/win32/fusion/tools/mjgcopy/mjgcopy.cpp
2020-09-26 16:20:57 +08:00

1930 lines
54 KiB
C++

#include "stdinc.h" // actually from dll\whistler directory
/*-----------------------------------------------------------------------------
Side X ("by") Side Test
-----------------------------------------------------------------------------*/
#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "windows.h"
#include "fusionlastwin32error.h"
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#pragma warning(push)
#pragma warning(disable: 4511)
#pragma warning(disable: 4512)
#pragma warning(disable: 4663)
#include <yvals.h>
#pragma warning(disable: 4663)
#include <string>
#include <deque>
#include <vector>
#pragma warning(pop)
#include "fusionbuffer.h"
#include "fusion.h"
#include "sxsasmname.h"
#include "util.h"
#include "filestream.cpp"
#include "sxsapi.h"
#include "fusiontrace.h"
#include "cresourcestream.cpp"
#include "cmemorystream.cpp"
#include "wintrust.h"
#include "softpub.h"
#include "perfclocking.h"
#include "strongname.h"
#include "fusionversion.h"
#include "fusionhash.h"
#include "fusiondeque.h"
BOOL
MySetFilePointerEx(
HANDLE File, // handle to file
LARGE_INTEGER DistanceToMove, // bytes to move pointer
PLARGE_INTEGER NewFilePointer, // new file pointer
DWORD MoveMethod // starting point
)
{
LONG DistanceToMoveLow = static_cast<LONG>(DistanceToMove.LowPart);
LONG DistanceToMoveHigh = DistanceToMove.HighPart;
DWORD NewPositionLow = SetFilePointer(File, DistanceToMoveLow, &DistanceToMoveHigh, MoveMethod);
if (NewPositionLow == INVALID_SET_FILE_POINTER)
{
if (GetLastError() != NO_ERROR)
return FALSE;
}
if (NewFilePointer != NULL)
{
NewFilePointer->LowPart = NewPositionLow;
NewFilePointer->HighPart = DistanceToMoveHigh;
}
return TRUE;
}
BOOL FusionpAreWeInOSSetupMode(BOOL* pfIsInSetup) { *pfIsInSetup = FALSE; return TRUE; }
void ReportFailure(const char szFormat[], ...);
PCWSTR g_pszImage = L"mjgcopy";
CRITICAL_SECTION g_cs;
ULONGLONG g_FilesCopied = 0;
ULONGLONG g_BytesCopied = 0;
ULONGLONG g_FilesLinked = 0;
ULONGLONG g_DirectoriesCopied = 0;
ULONGLONG g_CopiesSkipped = 0;
ULONGLONG g_LinksSkipped = 0;
ULONGLONG g_FileCopiesProcessed = 0;
ULONGLONG g_FileLinksProcessed = 0;
ULONGLONG g_DirScansProcessed = 0;
ULONGLONG g_BytesToCopy = 0;
bool g_fAnnounceDirectories = true;
bool g_fAnnounceCopies = false;
bool g_fAnnounceDeletes = true;
bool g_fAnnounceLinks = true;
bool g_fAnnounceSkips = false;
bool g_fSilent = false;
bool g_fShowProgress = true;
HANDLE g_hIoCompletionPort = NULL;
HANDLE g_hWorkItemDoneEvent = INVALID_HANDLE_VALUE;
DWORD g_dwDestinationSectorsPerCluster = 0;
DWORD g_dwDestinationBytesPerSector = 0;
DWORD g_dwDestinationNumberOfFreeClusters = 0;
DWORD g_dwDestinationTotalNumberOfClusters = 0;
ULONG g_nThreads = 3;
HANDLE g_rghThreads[32];
class CFileCopy;
class CFileLink;
class CDir;
// BOOL ScanAndCopyDir(PCWSTR szSource, PCWSTR szDest);
BOOL BuildDirList(const CBaseStringBuffer &rbuffSource, const CBaseStringBuffer &rbuffDestination);
BOOL MakeDirectoryStructure();
BOOL QueueDirScans();
BOOL QueueFileCopies();
BOOL QueueFileLinks();
DWORD WINAPI WorkerThreadProc(LPVOID pvParameter);
void ComputeTimeDeltas(const SYSTEMTIME &rstStart, const SYSTEMTIME &rstEnd, SYSTEMTIME &rstDelta);
BOOL ProcessFileCopy(CFileCopy *pFileCopy, bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer);
BOOL ProcessFileLink(CFileLink *pFileLink, bool &rfReQueue);
BOOL ProcessDirScan(CDir *pDir, bool &rfReQueue);
BOOL ResumeWorkerThreads();
BOOL SuspendWorkerThreads();
BOOL
WaitForWorkersToComplete(
ULONGLONG &rullCounter,
ULONGLONG ullLimit,
PCSTR pszOperationName
);
BOOL
MyCopyFile(
BYTE *pBuffer,
DWORD cbBuffer,
PCWSTR lpExistingFileName,
PCWSTR lpNewFileName,
BOOL fFailIfExists,
HANDLE &rhNewFileHandle
);
class CEntry
{
private:
CEntry(const CEntry&); // deliberately not implemented
void operator=(const CEntry&); // deliberately not implemented
public:
CEntry() : m_cRetries(0) { }
~CEntry() { }
virtual BOOL BaseDoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer)
{
LARGE_INTEGER liStart, liEnd;
::QueryPerformanceCounter(&liStart);
BOOL fResult = this->DoYourThing(rfReQueue, pBuffer, cbBuffer);
if (fResult && rfReQueue)
{
m_cRetries++;
if (m_cRetries > 100)
fResult = FALSE;
}
CSxsPreserveLastError ple;
::QueryPerformanceCounter(&liEnd);
ple.Restore();
m_ullStart = static_cast<ULONGLONG>(liStart.QuadPart);
m_ullEnd = static_cast<ULONGLONG>(liEnd.QuadPart);
return fResult;
}
virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pbBuffer, DWORD cbBuffer) = 0;
ULONGLONG m_ullStart, m_ullEnd;
ULONG m_cRetries;
};
class CDir : public CEntry
{
private:
CDir(const CDir&); // deliberately not implemented
void operator=(const CDir&); // deliberately not implemented
public:
CDir() { };
~CDir() { };
BOOL Initialize(const CBaseStringBuffer &rbuffSource, const CBaseStringBuffer &rbuffDestination)
{
BOOL fSuccess = FALSE;
if (!m_buffSource.Win32Assign(rbuffSource))
goto Exit;
if (!m_buffDestination.Win32Assign(rbuffDestination))
goto Exit;
fSuccess = TRUE;
Exit:
return fSuccess;
}
virtual BOOL DoYourThing(bool &rfReQueue, BYTE *, DWORD) { return ::ProcessDirScan(this, rfReQueue); }
CStringBuffer m_buffSource;
CStringBuffer m_buffDestination;
CDequeLinkage m_linkage;
};
class CFileBase : public CEntry
{
private:
CFileBase(const CFileBase&); // deliberately not implemented
void operator=(const CFileBase&); // deliberately not implemented
public:
CFileBase() : m_fDone(false) { }
~CFileBase() { }
BOOL Initialize(
CDir *pDir,
PCWSTR pszFilename,
FILETIME ftSourceCreationTime,
FILETIME ftSourceLastAccessTime,
FILETIME ftSourceLastWriteTime,
ULONGLONG ullFileIndex,
ULONGLONG cbSize
)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
IFW32FALSE_EXIT(m_buffFilename.Win32Assign(pszFilename, wcslen(pszFilename)));
m_ftSourceCreationTime = ftSourceCreationTime;
m_ftSourceLastAccessTime = ftSourceLastAccessTime;
m_ftSourceLastWriteTime = ftSourceLastWriteTime;
m_ullFileIndex = ullFileIndex;
m_cbSize = cbSize;
m_pDir = pDir;
fSuccess = TRUE;
Exit:
return fSuccess;
}
virtual BOOL GetSource(CBaseStringBuffer &rbuff)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
IFW32FALSE_EXIT(rbuff.Win32Assign(m_pDir->m_buffSource));
IFW32FALSE_EXIT(rbuff.Win32Append(m_buffFilename));
fSuccess = TRUE;
Exit:
return fSuccess;
}
virtual BOOL GetDestination(CBaseStringBuffer &rbuff)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
IFW32FALSE_EXIT(rbuff.Win32Assign(m_pDir->m_buffDestination));
IFW32FALSE_EXIT(rbuff.Win32Append(m_buffFilename));
fSuccess = TRUE;
Exit:
return fSuccess;
}
CStringBuffer m_buffFilename;
FILETIME m_ftSourceCreationTime;
FILETIME m_ftSourceLastAccessTime;
FILETIME m_ftSourceLastWriteTime;
ULONGLONG m_ullFileIndex;
ULONGLONG m_cbSize;
CDir *m_pDir;
bool m_fSkipped; // used to avoid skewed statistics about number of bytes copied/sec etc.
bool m_fDone;
CDequeLinkage m_linkage;
};
class CFileCopy : public CFileBase
{
public:
CFileCopy() { }
~CFileCopy() { }
virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer) { return ::ProcessFileCopy(this, rfReQueue, pBuffer, cbBuffer); }
static int __cdecl QSortBySize(const void *param1, const void *param2)
{
CFileCopy **pp1 = (CFileCopy **) param1;
CFileCopy **pp2 = (CFileCopy **) param2;
CFileCopy *p1 = *pp1;
CFileCopy *p2 = *pp2;
int iRet = 0;
if (p1->m_fSkipped)
{
if (p2->m_fSkipped)
{
if (p1->m_cbSize < p2->m_cbSize)
iRet = 1;
else if (p1->m_cbSize > p2->m_cbSize)
iRet = -1;
else
{
if (p1->m_ullFileIndex < p2->m_ullFileIndex)
iRet = -1;
else
iRet = 1;
}
}
else
iRet = -1;
}
else
{
if (p2->m_fSkipped)
iRet = 1;
else
{
if (p1->m_cbSize < p2->m_cbSize)
iRet = 1;
else if (p1->m_cbSize > p2->m_cbSize)
iRet = -1;
else
{
if (p1->m_ullFileIndex < p2->m_ullFileIndex)
iRet = -1;
else
iRet = 1;
}
}
}
return iRet;
}
private:
CFileCopy(const CFileCopy &r);
void operator =(const CFileCopy &r);
};
class CFileLink : public CFileBase
{
public:
CFileLink() { }
~CFileLink() { }
virtual BOOL DoYourThing(bool &rfReQueue, BYTE *pBuffer, DWORD cbBuffer) { return ::ProcessFileLink(this, rfReQueue); }
BOOL GetSource(CBaseStringBuffer &rbuff);
private:
CFileLink(const CFileLink &r);
void operator =(const CFileLink &r);
};
CDeque<CDir, offsetof(CDir, m_linkage)> *g_pDirs = NULL;
CDir **g_prgpDirs = NULL;
CDeque<CFileCopy, offsetof(CFileCopy, m_linkage)> *g_pFileCopies = NULL;
CFileCopy **g_prgpFileCopies = NULL;
CDeque<CFileLink, offsetof(CFileLink, m_linkage)> *g_pFileLinks = NULL;
CFileLink **g_prgpFileLinks = NULL;
class CFileIdHashHelper : public CHashTableHelper<ULONGLONG, ULONGLONG, PCWSTR, CStringBuffer>
{
private:
CFileIdHashHelper(const CFileIdHashHelper&); // deliberately not implemented
void operator=(const CFileIdHashHelper&); // deliberately not implemented
public:
static BOOL HashKey(ULONGLONG keyin, ULONG &rulPseudoKey) { rulPseudoKey = static_cast<ULONG>(keyin); return TRUE; }
static BOOL CompareKey(ULONGLONG keyin, const ULONGLONG &rtkeystored, bool &rfMatch) { rfMatch = keyin == rtkeystored; return TRUE; }
static VOID PreInitializeKey(ULONGLONG &rtkeystored) { rtkeystored = 0; }
static VOID PreInitializeValue(CFileCopy *&rtvaluestored) { rtvaluestored = NULL; }
static BOOL InitializeKey(ULONGLONG keyin, ULONGLONG &rtkeystored) { rtkeystored = keyin; return TRUE; }
static BOOL InitializeValue(CFileCopy *vin, CFileCopy *&rvstored) { rvstored = vin; return TRUE; }
static BOOL UpdateValue(CFileCopy *vin, CFileCopy *&rvstored) { rvstored = vin; return TRUE; }
static VOID FinalizeKey(ULONGLONG &rtkeystored) { }
static VOID FinalizeValue(CFileCopy *&rvstored) { rvstored = NULL; }
};
class CFileIdHashTable : public CHashTable<ULONGLONG, ULONGLONG, CFileCopy *, CFileCopy *, CFileIdHashHelper>
{
private:
CFileIdHashTable(const CFileIdHashTable&); // deliberately not implemented
void operator=(const CFileIdHashTable&); // deliberately not implemented
public:
CFileIdHashTable() { }
~CFileIdHashTable() { }
};
CFileIdHashTable *g_pFiles = NULL;
FILE *g_pLogFile = NULL;
BOOL
CFileLink::GetSource(CBaseStringBuffer &rbuff)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
CStringBuffer *pbuff = NULL;
CFileCopy **ppFileCopy = NULL;
if (!g_pFiles->Find(m_ullFileIndex, ppFileCopy))
{
::ReportFailure("Finding file index %I64u in the file table failed.\n");
goto Exit;
}
IFW32FALSE_EXIT((*ppFileCopy)->GetDestination(rbuff));
FN_EPILOG
}
extern "C" int __cdecl wmain(int argc, wchar_t** argv)
{
int iReturnStatus = EXIT_FAILURE;
SYSTEMTIME stStart, stAfterScan, stAfterDirCreation, stAfterCopies, stAfterLinks, stEnd;
ULONG i;
CStringBuffer buffSource;
CStringBuffer buffDestination;
int iSource = 0;
int iDestination = 0;
SYSTEMTIME stAfterScanDelta, stAfterDirCreationDelta, stAfterCopiesDelta, stAfterLinksDelta, stEndDelta;
ULONGLONG ullTemp;
int iArg;
// DWORD dwRetVal;
WCHAR rgwchSourceVolumePath[MAX_PATH];
WCHAR rgwchSourceVolumeName[MAX_PATH];
DWORD dwSourceVolumeSerialNumber;
DWORD dwSourceMaximumComponentLength;
DWORD dwSourceFileSystemFlags;
WCHAR rgwchSourceFileSystemNameBuffer[MAX_PATH];
WCHAR rgwchSourceRemoteName[MAX_PATH];
DWORD dwSourceRemoteNameLength = NUMBER_OF(rgwchSourceRemoteName);
UINT uiSourceDriveType;
WCHAR rgwchDestinationVolumePath[MAX_PATH];
WCHAR rgwchDestinationVolumeName[MAX_PATH];
DWORD dwDestinationVolumeSerialNumber;
DWORD dwDestinationMaximumComponentLength;
DWORD dwDestinationFileSystemFlags;
WCHAR rgwchDestinationFileSystemNameBuffer[MAX_PATH];
WCHAR rgwchDestinationRemoteName[MAX_PATH];
DWORD dwDestinationRemoteNameLength = NUMBER_OF(rgwchDestinationRemoteName);
UINT uiDestinationDriveType;
if (!::FusionpInitializeHeap(NULL))
goto Exit;
iArg = 1;
while (iArg < argc)
{
PCWSTR arg = argv[iArg];
// Let's see if we see some switches...
if ((arg[0] == L'-') || (arg[0] == L'/'))
{
arg++;
if ((_wcsicmp(arg, L"threads") == 0) ||
(_wcsicmp(arg, L"t") == 0))
{
PWSTR pszDummy;
iArg++;
if (iArg >= argc)
break;
g_nThreads = wcstol(argv[iArg], &pszDummy, 10);
if (g_nThreads < 1)
g_nThreads = 1;
if (g_nThreads > RTL_NUMBER_OF(g_rghThreads))
g_nThreads = RTL_NUMBER_OF(g_rghThreads);
iArg++;
continue;
}
else if ((_wcsicmp(arg, L"quiet") == 0) ||
(_wcsicmp(arg, L"q") == 0))
{
g_fSilent = true;
iArg++;
continue;
}
else if (_wcsicmp(arg, L"logfile") == 0)
{
iArg++;
if (iArg >= argc)
break;
g_pLogFile = ::_wfopen(argv[iArg], L"w+");
if (g_pLogFile == NULL)
{
::perror("Error opening logfile");
goto Exit;
}
iArg++;
}
}
// This must be it! We hope; there should be two things left
if ((iArg + 2) != argc)
break;
iSource = iArg;
iDestination = iArg + 1;
break;
}
if (iSource == 0)
{
fprintf(stderr,
"%ls: usage:\n"
" %ls [-threads n] [-quiet] <source> <dest>\n",
argv[0], argv[0]);
goto Exit;
}
// Abuse these buffers for debugging purposes...
{
HANDLE h;
if (!::GetLogicalDriveStringsW(NUMBER_OF(rgwchSourceVolumePath), rgwchSourceVolumePath))
{
::ReportFailure("GetLogicalDriveStringsW failed\n");
goto Exit;
}
h = ::FindFirstVolumeW(rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath));
if (h == INVALID_HANDLE_VALUE)
{
::ReportFailure("FindFirstVolumeW failed\n");
goto Exit;
}
for (;;)
{
DWORD cchReturnLength;
if (!::GetVolumePathNamesForVolumeNameW(rgwchSourceVolumePath, rgwchDestinationVolumePath, NUMBER_OF(rgwchDestinationVolumePath), &cchReturnLength))
{
::ReportFailure("GetVolumePathNamesForVolumeNameW failed\n");
goto Exit;
}
if (!::FindNextVolumeW(h, rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath)))
{
const DWORD dwLastError = ::GetLastError();
if (dwLastError != ERROR_NO_MORE_FILES)
{
::ReportFailure("FindNextVolumeW failed\n");
goto Exit;
}
break;
}
}
::FindVolumeClose(h);
}
if (!::GetVolumePathNameW(argv[iSource], rgwchSourceVolumePath, NUMBER_OF(rgwchSourceVolumePath)))
{
::ReportFailure("GetVolumePathName(L\"%ls\", ...) failed\n", argv[iSource]);
goto Exit;
}
uiSourceDriveType = ::GetDriveTypeW(rgwchSourceVolumePath);
if (!::GetVolumePathNameW(argv[iDestination], rgwchDestinationVolumePath, NUMBER_OF(rgwchDestinationVolumePath)))
{
::ReportFailure("GetVolumePathName(L\"%ls\", ...) failed\n", argv[iDestination]);
goto Exit;
}
uiDestinationDriveType = ::GetDriveTypeW(rgwchDestinationVolumePath);
if (!::GetVolumeInformationW(
rgwchSourceVolumePath,
rgwchSourceVolumeName,
NUMBER_OF(rgwchSourceVolumeName),
&dwSourceVolumeSerialNumber,
&dwSourceMaximumComponentLength,
&dwSourceFileSystemFlags,
rgwchSourceFileSystemNameBuffer,
NUMBER_OF(rgwchSourceFileSystemNameBuffer)))
{
::ReportFailure("GetVolumeInformation(L\"%ls\", ...) failed\n", rgwchSourceVolumePath);
goto Exit;
}
if (!::GetVolumeInformationW(
rgwchDestinationVolumePath,
rgwchDestinationVolumeName,
NUMBER_OF(rgwchDestinationVolumeName),
&dwDestinationVolumeSerialNumber,
&dwDestinationMaximumComponentLength,
&dwDestinationFileSystemFlags,
rgwchDestinationFileSystemNameBuffer,
NUMBER_OF(rgwchDestinationFileSystemNameBuffer)))
{
::ReportFailure("GetVolumeInformation(L\"%ls\", ...) failed\n", rgwchDestinationVolumePath);
goto Exit;
}
if (!::GetDiskFreeSpaceW(
rgwchDestinationVolumePath,
&g_dwDestinationSectorsPerCluster,
&g_dwDestinationBytesPerSector,
&g_dwDestinationNumberOfFreeClusters,
&g_dwDestinationTotalNumberOfClusters))
{
::ReportFailure("GetDiskFreeSpaceW(L\"%ls\", ...) failed\n", rgwchDestinationVolumePath);
goto Exit;
}
#if 0
if ((dwRetVal = ::WNetGetConnectionW(rgwchSourceVolumePath, rgwchSourceRemoteName, &dwSourceRemoteNameLength)) != NO_ERROR)
{
::SetLastError(dwRetVal);
::ReportFailure("WNetGetConnection(L\"%ls\", ...) failed\n", rgwchSourceVolumePath);
goto Exit;
}
#endif // 0
InitializeCriticalSection(&g_cs);
g_pFiles = new CFileIdHashTable;
g_pDirs = new CDeque<CDir, offsetof(CDir, m_linkage)>;
g_pFileCopies = new CDeque<CFileCopy, offsetof(CFileCopy, m_linkage)>;
g_pFileLinks = new CDeque<CFileLink, offsetof(CFileLink, m_linkage)>;
g_pszImage = wcsrchr(argv[0], L'\\');
if (g_pszImage == NULL)
g_pszImage = argv[0];
else
g_pszImage++;
g_hIoCompletionPort = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, g_nThreads);
if (g_hIoCompletionPort == NULL)
{
::ReportFailure("Creating I/O Completion Port with %lu concurrent threads failed.\n", g_nThreads);
goto Exit;
}
g_hWorkItemDoneEvent = ::CreateEventW(NULL, FALSE, FALSE, NULL);
if (g_hWorkItemDoneEvent == NULL)
{
::ReportFailure("Creating the file copied event failed.\n");
goto Exit;
}
for (i=0; i<g_nThreads; i++)
{
g_rghThreads[i] = ::CreateThread(NULL, 0, &WorkerThreadProc, NULL, CREATE_SUSPENDED, NULL);
if (g_rghThreads[i] == NULL)
{
::ReportFailure("Creating worker thread number %lu failed.\n", i);
goto Exit;
}
}
::GetSystemTime(&stStart);
if (!buffSource.Win32Assign(argv[iSource], wcslen(argv[iSource])))
goto Exit;
if (!buffDestination.Win32Assign(argv[iDestination], wcslen(argv[iDestination])))
goto Exit;
if (!::BuildDirList(buffSource, buffDestination))
goto Exit;
if (!g_fSilent)
printf("%ls: Found %Iu directories.\n", g_pszImage, g_pDirs->GetEntryCount());
if (!::QueueDirScans())
goto Exit;
if (!::WaitForWorkersToComplete(g_DirScansProcessed, g_pDirs->GetEntryCount(), "Directory scans"))
goto Exit;
::GetSystemTime(&stAfterScan);
if (!g_fSilent)
{
printf("%ls: Copying %Iu files (%I64u bytes)\n", g_pszImage, g_pFileCopies->GetEntryCount(), g_BytesToCopy);
printf("%ls: Linking %Iu files\n", g_pszImage, g_pFileLinks->GetEntryCount());
}
if (!::MakeDirectoryStructure())
goto Exit;
if (!g_fSilent)
printf("%ls: Created %Iu directories.\n", g_pszImage, g_pDirs->GetEntryCount());
::GetSystemTime(&stAfterDirCreation);
if (!::QueueFileCopies())
goto Exit;
if (!::WaitForWorkersToComplete(g_FileCopiesProcessed, g_pFileCopies->GetEntryCount(), "File copies"))
goto Exit;
::GetSystemTime(&stAfterCopies);
::qsort(g_prgpFileCopies, g_pFileCopies->GetEntryCount(), sizeof(CFileCopy *), &CFileCopy::QSortBySize);
if (g_pLogFile != NULL)
{
LARGE_INTEGER liFreq;
ULONGLONG ullFreqDiv100;
CStringBuffer buffDestination;
::QueryPerformanceFrequency(&liFreq);
ullFreqDiv100 = (liFreq.QuadPart / 100);
for (i=0; i<g_pFileCopies->GetEntryCount(); i++)
{
ULONGLONG diff = g_prgpFileCopies[i]->m_ullEnd - g_prgpFileCopies[i]->m_ullStart;
diff = diff / ullFreqDiv100;
if (!g_prgpFileCopies[i]->GetDestination(buffDestination))
goto Exit;
fprintf(g_pLogFile, "%ls,%Iu,%Iu\n", static_cast<PCWSTR>(buffDestination), diff, g_prgpFileCopies[i]->m_cbSize);
}
}
if (!::QueueFileLinks())
goto Exit;
if (!::WaitForWorkersToComplete(g_FileLinksProcessed, g_pFileLinks->GetEntryCount(), "File links"))
goto Exit;
::GetSystemTime(&stAfterLinks);
::GetSystemTime(&stEnd);
::ComputeTimeDeltas(stStart, stAfterScan, stAfterScanDelta);
::ComputeTimeDeltas(stAfterScan, stAfterDirCreation, stAfterDirCreationDelta);
::ComputeTimeDeltas(stAfterDirCreation, stAfterCopies, stAfterCopiesDelta);
::ComputeTimeDeltas(stAfterCopies, stAfterLinks, stAfterLinksDelta);
::ComputeTimeDeltas(stStart, stEnd, stEndDelta);
printf(
"%ls: Statistics:\n"
" Directories Copied: %I64u\n"
" Files Copied: %I64u\n"
" Bytes Copied: %I64u\n"
" Files Linked: %I64u\n"
" Copies Skipped: %I64u\n"
" Links Skipped: %I64u\n",
g_pszImage,
g_DirectoriesCopied,
g_FilesCopied,
g_BytesCopied,
g_FilesLinked,
g_CopiesSkipped,
g_LinksSkipped);
printf(
" Times:\n"
" Scan: %u:%02u:%02u.%03u\n"
" Directory Creation: %u:%02u:%02u.%03u\n"
" Copying Files: %u:%02u:%02u.%03u\n"
" Linking Files: %u:%02u:%02u.%03u\n"
" Total: %u:%02u:%02u.%03u\n",
stAfterScanDelta.wHour, stAfterScanDelta.wMinute, stAfterScanDelta.wSecond, stAfterScanDelta.wMilliseconds,
stAfterDirCreationDelta.wHour, stAfterDirCreationDelta.wMinute, stAfterDirCreationDelta.wSecond, stAfterDirCreationDelta.wMilliseconds,
stAfterCopiesDelta.wHour, stAfterCopiesDelta.wMinute, stAfterCopiesDelta.wSecond, stAfterCopiesDelta.wMilliseconds,
stAfterLinksDelta.wHour, stAfterLinksDelta.wMinute, stAfterLinksDelta.wSecond, stAfterLinksDelta.wMilliseconds,
stEndDelta.wHour, stEndDelta.wMinute, stEndDelta.wSecond, stEndDelta.wMilliseconds
);
ullTemp = (((((stAfterCopiesDelta.wHour * 60) + stAfterCopiesDelta.wMinute) * 60) + stAfterCopiesDelta.wSecond) * 1000) + stAfterCopiesDelta.wMilliseconds;
if (ullTemp != 0)
{
ULONGLONG ullFilesPerMS = ((g_FilesCopied * 1000000ui64) / ullTemp);
ULONGLONG ullBytesPerMS = ((g_BytesCopied * 1000000ui64) / ullTemp);
printf(
" Files copied per second: %I64u.%03u\n"
" Bytes copied per second: %I64u.%03u\n",
static_cast<ULONGLONG>(ullFilesPerMS / 1000ui64), static_cast<ULONG>(ullFilesPerMS % 1000ui64),
static_cast<ULONGLONG>(ullBytesPerMS / 1000ui64), static_cast<ULONG>(ullBytesPerMS % 1000ui64));
#if 0
static_cast<ULONGLONG>((g_FilesCopied / ullTemp) * 1000ui64), static_cast<unsigned int>((g_FilesCopied / ullTemp) % 1000),
static_cast<ULONGLONG>((g_BytesCopied / ullTemp) * 1000ui64), static_cast<unsigned int>((g_BytesCopied / ullTemp) % 1000));
#endif
}
ullTemp = (((stAfterLinksDelta.wHour * 60) + stAfterLinksDelta.wMinute) * 60) + stAfterLinksDelta.wSecond;
if (ullTemp != 0)
{
printf(
" Files linked per second: %I64u\n",
static_cast<ULONGLONG>(g_FilesLinked / ullTemp));
}
ullTemp = (((stEndDelta.wHour * 60) + stEndDelta.wMinute) * 60) + stEndDelta.wSecond;
if (ullTemp != 0)
{
printf(
" Overall files per second: %I64u\n",
static_cast<ULONGLONG>((g_FilesCopied + g_CopiesSkipped + g_FilesLinked + g_LinksSkipped + g_pDirs->GetEntryCount()) / ullTemp));
}
iReturnStatus = EXIT_SUCCESS;
Exit:
// Wake the children; process termination doesn't seem to work if we don't.
::ResumeWorkerThreads();
if (g_pLogFile != NULL)
{
fflush(g_pLogFile);
fclose(g_pLogFile);
}
return iReturnStatus;
}
BOOL
ResumeWorkerThreads()
{
BOOL fSuccess = FALSE;
ULONG i;
for (i=0; i<g_nThreads; i++)
{
if (::ResumeThread(g_rghThreads[i]) == -1)
{
::ReportFailure("Failed to resume worker thread %lu\n", i + 1);
goto Exit;
}
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
SuspendWorkerThreads()
{
BOOL fSuccess = FALSE;
ULONG i;
for (i=0; i<g_nThreads; i++)
{
if (::SuspendThread(g_rghThreads[i]) == -1)
{
::ReportFailure("Failed to suspend worker thread %lu\n", i + 1);
goto Exit;
}
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
WaitForWorkersToComplete(
ULONGLONG &rullCounter,
ULONGLONG ullLimit,
PCSTR pszOperationName
)
{
BOOL fSuccess = FALSE;
ULONGLONG i;
if (!::ResumeWorkerThreads())
goto Exit;
i = 0;
while (rullCounter < ullLimit)
{
// Don't wake more than every tenth of a second...
::Sleep(100);
DWORD dwWFSO = ::WaitForSingleObject(g_hWorkItemDoneEvent, INFINITE);
if (dwWFSO == WAIT_FAILED)
{
::ReportFailure("Waiting for work item done event failed\n");
goto Exit;
}
i++;
if ((i % 50) == 0)
{
if (!g_fSilent)
printf("%ls: %s processed (%I64u total): %I64u\n", g_pszImage, pszOperationName, ullLimit, rullCounter);
}
}
if (!::SuspendWorkerThreads())
goto Exit;
fSuccess = TRUE;
Exit:
return fSuccess;
}
void
ComputeTimeDeltas(
const SYSTEMTIME &rstStart,
const SYSTEMTIME &rstEnd,
SYSTEMTIME &rstDelta
)
{
FILETIME ftStart, ftEnd;
ULARGE_INTEGER uliStart, uliEnd;
ULONGLONG ullDiff, ullTemp;
ULONG ulHours, ulMinutes, ulSeconds, ulMilliseconds;
::SystemTimeToFileTime(&rstStart, &ftStart);
::SystemTimeToFileTime(&rstEnd, &ftEnd);
uliStart.LowPart = ftStart.dwLowDateTime;
uliStart.HighPart = ftStart.dwHighDateTime;
uliEnd.LowPart = ftEnd.dwLowDateTime;
uliEnd.HighPart = ftEnd.dwHighDateTime;
ullDiff = (uliEnd.QuadPart - uliStart.QuadPart);
ulHours = (ULONG) (ullDiff / (10000000ui64 * 60 * 60));
ullTemp = ullDiff - ((ULONGLONG) ulHours) * (10000000ui64 * 60 * 60);
ulMinutes = (ULONG) (ullTemp / (10000000ui64 * 60));
ullTemp -= ((ULONGLONG) ulMinutes) * (10000000ui64 * 60);
ulSeconds = (ULONG) (ullTemp / 10000000ui64);
ullTemp -= ((ULONGLONG) ulSeconds) * 10000000ui64;
ulMilliseconds = (ULONG) (ullTemp / 10000ui64);
rstDelta.wYear = 0;
rstDelta.wMonth = 0;
rstDelta.wDayOfWeek = 0;
rstDelta.wDay = 0;
rstDelta.wHour = (WORD) ulHours;
rstDelta.wMinute = (WORD) ulMinutes;
rstDelta.wSecond = (WORD) ulSeconds;
rstDelta.wMilliseconds = (WORD) ulMilliseconds;
}
BOOL
BuildDirList(
const CBaseStringBuffer &rbuffSource,
const CBaseStringBuffer &rbuffDestination
)
{
PCWSTR szSrc = rbuffSource;
BOOL fSuccess = FALSE;
DWORD dwFileAttributes;
CStringBuffer buffSrc;
CStringBuffer buffSrcWildcard;
CStringBuffer buffDst;
WIN32_FIND_DATAW wfd;
HANDLE hFind = INVALID_HANDLE_VALUE;
HANDLE hFile = INVALID_HANDLE_VALUE;
SIZE_T cchSrc, cchDst;
CDir *pDir = NULL;
dwFileAttributes = ::GetFileAttributesW(rbuffSource);
if (dwFileAttributes == ((DWORD) -1))
{
::ReportFailure("GetFileAttributesW() on the source \"%ls\" failed\n", szSrc);
goto Exit;
}
if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
::SetLastError(ERROR_INVALID_PARAMETER);
::ReportFailure("Source directory \"%ls\" is not a directory!\n");
goto Exit;
}
if (!buffSrc.Win32Assign(rbuffSource))
goto Exit;
if (!buffSrc.Win32EnsureTrailingPathSeparator())
goto Exit;
if (!buffDst.Win32Assign(rbuffDestination))
goto Exit;
if (!buffDst.Win32EnsureTrailingPathSeparator())
goto Exit;
cchSrc = buffSrc.Cch();
cchDst = buffDst.Cch();
pDir = new CDir;
if (pDir == NULL)
{
::SetLastError(ERROR_OUTOFMEMORY);
::ReportFailure("Failed to allocate new CDir object\n");
goto Exit;
}
if (!pDir->Initialize(buffSrc, buffDst))
goto Exit;
g_pDirs->AddToTail(pDir);
pDir = NULL;
if ((g_pDirs->GetEntryCount() % 50) == 0)
{
if (!g_fSilent)
printf("%ls: Found %Iu directories...\n", g_pszImage, g_pDirs->GetEntryCount());
}
if (!buffSrcWildcard.Win32Assign(buffSrc))
goto Exit;
if (!buffSrcWildcard.Win32Append(L"*", 1))
goto Exit;
if ((hFind = ::FindFirstFileExW(
buffSrcWildcard,
FindExInfoStandard,
&wfd,
FindExSearchLimitToDirectories,
NULL,
0)) == INVALID_HANDLE_VALUE)
{
const DWORD dwLastError = ::GetLastError();
if (dwLastError != ERROR_NO_MORE_FILES)
{
::ReportFailure("FindFirstFileW(L\"%ls\", ...) failed.\n", static_cast<PCWSTR>(buffSrcWildcard));
goto Exit;
}
}
for (;;)
{
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(wcscmp(wfd.cFileName, L".") != 0) &&
(wcscmp(wfd.cFileName, L"..") != 0))
{
buffSrc.Left(cchSrc);
buffDst.Left(cchDst);
if (!buffSrc.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
goto Exit;
if (!buffDst.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
goto Exit;
if (!::BuildDirList(buffSrc, buffDst))
goto Exit;
}
if (!::FindNextFileW(hFind, &wfd))
{
if (::GetLastError() == ERROR_NO_MORE_FILES)
break;
::ReportFailure("FindNextFileW() failed\n");
goto Exit;
}
}
fSuccess = TRUE;
Exit:
if (hFind != INVALID_HANDLE_VALUE)
{
CSxsPreserveLastError ple;
::FindClose(hFind);
ple.Restore();
}
if (pDir != NULL)
delete pDir;
return fSuccess;
}
BOOL
BuildDirFileList(
CSxsLockCriticalSection &rlcs,
CDir *pDir
)
{
PCWSTR szSrc = pDir->m_buffSource;
BOOL fSuccess = FALSE;
DWORD dwFileAttributes;
CStringBuffer buffSrc;
CStringBuffer buffSrcWildcard;
CStringBuffer buffDst;
WIN32_FIND_DATAW wfd;
HANDLE hFind = INVALID_HANDLE_VALUE;
HANDLE hFile = INVALID_HANDLE_VALUE;
SIZE_T cchSrc, cchDst;
CFileLink *pFileLink = NULL;
CFileCopy *pFileCopy = NULL;
dwFileAttributes = ::GetFileAttributesW(pDir->m_buffSource);
if (dwFileAttributes == ((DWORD) -1))
{
::ReportFailure("GetFileAttributesW() on the source \"%ls\" failed", szSrc);
goto Exit;
}
if (!(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
::SetLastError(ERROR_INVALID_PARAMETER);
::ReportFailure("Source directory \"%ls\" is not a directory!\n", szSrc);
goto Exit;
}
if (!buffSrc.Win32Assign(pDir->m_buffSource))
goto Exit;
if (!buffSrc.Win32EnsureTrailingPathSeparator())
goto Exit;
if (!buffDst.Win32Assign(pDir->m_buffDestination))
goto Exit;
if (!buffDst.Win32EnsureTrailingPathSeparator())
goto Exit;
cchSrc = buffSrc.Cch();
cchDst = buffDst.Cch();
if (!buffSrcWildcard.Win32Assign(buffSrc))
goto Exit;
if (!buffSrcWildcard.Win32Append(L"*", 1))
goto Exit;
if ((hFind = ::FindFirstFileW(buffSrcWildcard, &wfd)) == INVALID_HANDLE_VALUE)
{
const DWORD dwLastError = ::GetLastError();
if (::GetLastError() != ERROR_NO_MORE_FILES)
{
::ReportFailure("FindFirstFileW(L\"%ls\", ...) failed\n", static_cast<PCWSTR>(buffSrcWildcard));
goto Exit;
}
}
for (;;)
{
if ((wcscmp(wfd.cFileName, L".") != 0) && (wcscmp(wfd.cFileName, L"..") != 0))
{
buffSrc.Left(cchSrc);
buffDst.Left(cchDst);
if (!buffSrc.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
goto Exit;
if (!buffDst.Win32Append(wfd.cFileName, wcslen(wfd.cFileName)))
goto Exit;
dwFileAttributes = ::GetFileAttributesW(buffSrc);
if (dwFileAttributes == ((DWORD) -1))
{
::ReportFailure("GetFileAttribuesW(L\"%ls\") failed\n", static_cast<PCWSTR>(buffSrc));
goto Exit;
}
if ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
BY_HANDLE_FILE_INFORMATION bhfi;
ULONGLONG ullFileIndex;
ULONGLONG ullFileSize;
CFileCopy **ppFileCopy = NULL;
hFile = ::CreateFileW(buffSrc, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
::ReportFailure("CreateFileW(L\"%ls\", ...) failed\n", static_cast<PCWSTR>(buffSrc));
goto Exit;
}
if (!::GetFileInformationByHandle(hFile, &bhfi))
{
::ReportFailure("Failed to GetFileInformationByHandle(%p, ...) on file \"%ls\"\n", hFile, static_cast<PCWSTR>(buffSrc));
goto Exit;
}
::CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
ullFileIndex = (((ULONGLONG) bhfi.nFileIndexHigh) << 32) | ((ULONGLONG) bhfi.nFileIndexLow);
ullFileSize = (((ULONGLONG) bhfi.nFileSizeHigh) << 32) | ((ULONGLONG) bhfi.nFileSizeLow);
g_BytesToCopy += ullFileSize;
if (!rlcs.Lock())
{
::ReportFailure("Failed to lock global critical section");
goto Exit;
}
if (!g_pFiles->Find(ullFileIndex, ppFileCopy))
{
::ReportFailure("Finding file index %I64u in file table failed.\n", ullFileIndex);
goto Exit;
}
if (ppFileCopy != NULL)
{
pFileLink = new CFileLink;
if (pFileLink == NULL)
{
::SetLastError(ERROR_OUTOFMEMORY);
goto Exit;
}
if (!pFileLink->Initialize(
pDir,
wfd.cFileName,
bhfi.ftCreationTime,
bhfi.ftLastAccessTime,
bhfi.ftLastWriteTime,
ullFileIndex,
ullFileSize))
goto Exit;
g_pFileLinks->AddToTail(pFileLink);
pFileLink = NULL;
}
else
{
pFileCopy = new CFileCopy;
if (pFileCopy == NULL)
{
::SetLastError(ERROR_OUTOFMEMORY);
::ReportFailure("Allocating new CFileCopy object failed.\n");
goto Exit;
}
if (!pFileCopy->Initialize(
pDir,
wfd.cFileName,
bhfi.ftCreationTime,
bhfi.ftLastAccessTime,
bhfi.ftLastWriteTime,
ullFileIndex,
ullFileSize))
goto Exit;
if (!g_pFiles->Insert(ullFileIndex, pFileCopy))
goto Exit;
g_pFileCopies->AddToTail(pFileCopy);
pFileCopy = NULL;
}
rlcs.Unlock();
}
}
if (!::FindNextFileW(hFind, &wfd))
{
const DWORD dwLastError = ::GetLastError();
if (dwLastError == ERROR_NO_MORE_FILES)
break;
::ReportFailure("FindNextFileW() call failed\n");
goto Exit;
}
}
fSuccess = TRUE;
Exit:
if (hFind != INVALID_HANDLE_VALUE)
{
CSxsPreserveLastError ple;
::FindClose(hFind);
ple.Restore();
}
if (pFileLink != NULL)
delete pFileLink;
if (pFileCopy != NULL)
delete pFileCopy;
return fSuccess;
}
BOOL
MakeDirectoryStructure()
{
BOOL fSuccess = FALSE;
CDequeIterator<CDir, offsetof(CDir, m_linkage)> dirIter;
dirIter.Rebind(g_pDirs);
for (dirIter.Reset(); dirIter.More(); dirIter.Next())
{
if (!::CreateDirectoryW(dirIter->m_buffDestination, NULL))
{
const DWORD dwLastError = ::GetLastError();
if (dwLastError != ERROR_ALREADY_EXISTS)
{
::ReportFailure("Unable to create directory \"%ls\"", static_cast<PCWSTR>(dirIter->m_buffDestination));
goto Exit;
}
}
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
void
ReportFailure(
const char szFormat[],
...
)
{
const DWORD dwLastError = ::GetLastError();
va_list ap;
char rgchBuffer[4096];
WCHAR rgchWin32Error[4096];
va_start(ap, szFormat);
_vsnprintf(rgchBuffer, sizeof(rgchBuffer) / sizeof(rgchBuffer[0]), szFormat, ap);
va_end(ap);
if (!::FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwLastError,
0,
rgchWin32Error,
NUMBER_OF(rgchWin32Error),
&ap))
{
const DWORD dwLastError2 = ::GetLastError();
_snwprintf(rgchWin32Error, sizeof(rgchWin32Error) / sizeof(rgchWin32Error[0]), L"Error formatting Win32 error %lu\nError from FormatMessage is %lu", dwLastError, dwLastError2);
}
fprintf(stderr, "%ls: %s\n%ls\n", g_pszImage, rgchBuffer, rgchWin32Error);
}
DWORD
WINAPI
WorkerThreadProc(
LPVOID pvParameter
)
{
ULONG nThread = (ULONG)(ULONG_PTR)pvParameter;
DWORD dwReturnValue = 0;
BYTE *pBuffer = NULL;
DWORD cbBuffer;
cbBuffer = 8192 * 32;
pBuffer = (BYTE *) ::VirtualAlloc(NULL, cbBuffer, MEM_COMMIT, PAGE_READWRITE);
if (pBuffer == NULL)
{
ReportFailure("VirtualAlloc() for thread %lu failed.\n", nThread);
goto Exit;
}
for (;;)
{
DWORD nBytes = 0;
ULONG_PTR ulpCompletionKey = 0;
LPOVERLAPPED lpo = NULL;
CEntry *pEntry = NULL;
bool fReQueue = false;
if (!::GetQueuedCompletionStatus(g_hIoCompletionPort, &nBytes, &ulpCompletionKey, &lpo, INFINITE))
{
ReportFailure("Thread %lu failed call to GetQueuedCompletionStatus()\n", nThread);
dwReturnValue = ::GetLastError();
goto Exit;
}
pEntry = (CEntry *) lpo;
DoItAgain:
if (!pEntry->BaseDoYourThing(fReQueue, pBuffer, cbBuffer))
goto Exit;
if (fReQueue)
{
if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, NULL, lpo))
{
ReportFailure("Thread %lu failed to requeue item; retrying directly.\n", nThread);
goto DoItAgain;
}
}
::SetEvent(g_hWorkItemDoneEvent);
}
Exit:
// Heavy handed but what else can we do?
::ExitProcess(dwReturnValue);
return dwReturnValue;
}
BOOL
QueueDirScans()
{
BOOL fSuccess = FALSE;
SIZE_T i;
g_prgpDirs = new (CDir *[g_pDirs->GetEntryCount()]);
CDequeIterator<CDir, offsetof(CDir, m_linkage)> dirIter;
dirIter.Rebind(g_pDirs);
for (dirIter.Reset(), i=0; dirIter.More(); dirIter.Next(), i++)
{
g_prgpDirs[i] = dirIter;
if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) dirIter.Current()))
{
ReportFailure("Failed to queue dir scan\n");
goto Exit;
}
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
QueueFileCopies()
{
BOOL fSuccess = FALSE;
SIZE_T i;
FN_TRACE_WIN32(fSuccess);
CDequeIterator<CFileCopy, offsetof(CFileCopy, m_linkage)> fileIter;
g_prgpFileCopies = new (CFileCopy *[g_pFileCopies->GetEntryCount()]);
fileIter.Rebind(g_pFileCopies);
i=0;
for (fileIter.Reset(); fileIter.More(); fileIter.Next())
{
if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) fileIter.Current()))
{
ReportFailure("Failed to queue file copy\n");
goto Exit;
}
g_prgpFileCopies[i++] = fileIter;
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
QueueFileLinks()
{
BOOL fSuccess = FALSE;
SIZE_T i;
CDequeIterator<CFileLink, offsetof(CFileLink, m_linkage)> fileIter;
g_prgpFileLinks = new (CFileLink *[g_pFileLinks->GetEntryCount()]);
fileIter.Rebind(g_pFileLinks);
i=0;
for (fileIter.Reset(); fileIter.More(); fileIter.Next())
{
if (!::PostQueuedCompletionStatus(g_hIoCompletionPort, 0, 0, (LPOVERLAPPED) fileIter.Current()))
{
ReportFailure("Failed to queue file link\n");
goto Exit;
}
g_prgpFileLinks[i++] = fileIter;
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
ProcessDirScan(
CDir *pDir,
bool &rfReQueue
)
{
BOOL fSuccess = FALSE;
CSxsLockCriticalSection l(g_cs);
rfReQueue = false;
if (!::BuildDirFileList(l, pDir))
goto Exit;
if (!l.Lock())
{
ReportFailure("Failed to lock global critical section");
goto Exit;
}
g_DirScansProcessed++;
l.Unlock();
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
ProcessFileCopy(
CFileCopy *pFile,
bool &rfReQueue,
BYTE *pBuffer,
DWORD cbBuffer
)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
CSxsLockCriticalSection l(g_cs);
bool fDoCopy = true;
DWORD dwFileAttributes;
HANDLE hFile = INVALID_HANDLE_VALUE;
CStringBuffer buffSource, buffDestination;
rfReQueue = false;
// If we're about to copy it, it's worth taking a look at the target to see if it's more-or-less
// out of date.
WIN32_FILE_ATTRIBUTE_DATA wfad;
IFW32FALSE_EXIT(pFile->GetSource(buffSource));
IFW32FALSE_EXIT(pFile->GetDestination(buffDestination));
if (!::GetFileAttributesExW(buffDestination, GetFileExInfoStandard, &wfad))
{
const DWORD dwLastError = ::GetLastError();
if (dwLastError != ERROR_FILE_NOT_FOUND)
{
::ReportFailure("Error opening target file \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
goto Exit;
}
wfad.dwFileAttributes = ((DWORD) -1);
}
else
{
ULONGLONG cbFileSize = (wfad.nFileSizeHigh << 32) | wfad.nFileSizeLow;
ASSERT_NTC(cbFileSize >= wfad.nFileSizeLow);
if ((cbFileSize == pFile->m_cbSize) &&
(wfad.ftCreationTime == pFile->m_ftSourceCreationTime))
{
pFile->m_fSkipped = true;
fDoCopy = false;
}
}
if (fDoCopy)
{
bool fSetTimestamp = true;
bool fClearedReadOnly = false;
// Destructively clear the read-only bit if th destination file exists and is read-only.
dwFileAttributes = wfad.dwFileAttributes;
// We already filtered out all other reasons for failure other than FILE_NOT_FOUND.
if (dwFileAttributes != ((DWORD) -1))
{
if (dwFileAttributes & FILE_ATTRIBUTE_READONLY)
{
// If it's readonly, clear that bit as well as all the others that
// are illegal to set via SetFileAttributes()
dwFileAttributes &=
~(FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_COMPRESSED |
FILE_ATTRIBUTE_DEVICE |
FILE_ATTRIBUTE_DIRECTORY |
FILE_ATTRIBUTE_ENCRYPTED |
FILE_ATTRIBUTE_REPARSE_POINT |
FILE_ATTRIBUTE_SPARSE_FILE);
if (!::SetFileAttributesW(buffDestination, dwFileAttributes))
{
::ReportFailure("Error setting file attributes for target file \"%ls\" to 0x%08lx to allow overwrite\n", static_cast<PCWSTR>(buffDestination), dwFileAttributes);
goto Exit;
}
}
}
if (g_fAnnounceCopies)
printf("Copying \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
// Hey, it's not there. Let's copy it and put the entry in the table.
if (!::MyCopyFile(pBuffer, cbBuffer, buffSource, buffDestination, FALSE, hFile))
{
::ReportFailure("Failed to copy \"%ls\" to \"%ls\".\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
goto Exit;
}
if (fSetTimestamp)
{
if (!::SetFileTime(hFile, &pFile->m_ftSourceCreationTime, &pFile->m_ftSourceLastAccessTime, &pFile->m_ftSourceLastWriteTime))
{
::ReportFailure("Failed call to SetFileTime on file \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
goto Exit;
}
::CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
if (!l.Lock())
{
::ReportFailure("Failed to lock global critical section\n");
goto Exit;
}
g_FilesCopied++;
g_FileCopiesProcessed++;
g_BytesCopied += pFile->m_cbSize;
l.Unlock();
}
else
{
if (g_fAnnounceSkips)
::printf("Skipping copy from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffSource), static_cast<PCWSTR>(buffDestination));
if (!l.Lock())
{
::ReportFailure("Failed to lock global critical section\n");
goto Exit;
}
g_CopiesSkipped++;
g_FileCopiesProcessed++;
l.Unlock();
}
fSuccess = TRUE;
Exit:
if (hFile != INVALID_HANDLE_VALUE)
{
CSxsPreserveLastError ple;
::CloseHandle(hFile);
ple.Restore();
}
if (::GetLastError() == ERROR_TOO_MANY_OPEN_FILES)
{
rfReQueue = true;
fSuccess = TRUE;
}
return fSuccess;
}
BOOL
ProcessFileLink(
CFileLink *pFile,
bool &rfReQueue
)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
CSxsLockCriticalSection l(g_cs);
bool fDoLink = true;
CStringBuffer buffSource, buffDestination;
WIN32_FILE_ATTRIBUTE_DATA wfad;
rfReQueue = false;
IFW32FALSE_EXIT(pFile->GetSource(buffSource));
IFW32FALSE_EXIT(pFile->GetDestination(buffDestination));
// CreateHardLinkW() doesn't deal with replace-existing like copyfile, so we'll see if a
// file by that name is already present and if so, delete it.
if (!::GetFileAttributesExW(buffDestination, GetFileExInfoStandard, &wfad))
{
// It failed. But did it fail for the right reason?
DWORD dwLastError = ::GetLastError();
if (dwLastError != ERROR_FILE_NOT_FOUND)
{
::ReportFailure("Error probing destination for \"%ls\"\n", static_cast<PCWSTR>(buffDestination));
goto Exit;
}
}
else
{
ULONGLONG cbFileSize = (wfad.nFileSizeHigh << 32) | wfad.nFileSizeLow;
ASSERT_NTC(cbFileSize >= wfad.nFileSizeLow);
if ((pFile->m_cbSize == cbFileSize) &&
(pFile->m_ftSourceCreationTime == wfad.ftCreationTime))
{
fDoLink = false;
pFile->m_fSkipped = true;
}
else
{
if (g_fAnnounceDeletes)
::printf("Deleting file \"%ls\" in preparation for hard link creation\n", static_cast<PCWSTR>(buffDestination));
if (!::DeleteFileW(buffDestination))
{
::ReportFailure("Error deleting destination \"%ls\" in preparation of hard link creation\n", static_cast<PCWSTR>(buffDestination));
goto Exit;
}
}
}
if (fDoLink)
{
if (g_fAnnounceLinks)
::printf("Creating hard link from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
// Hey, it's already there. Let's link it.
if (!::CreateHardLinkW(buffDestination, buffSource, NULL))
{
::ReportFailure("Error creating hard link from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
goto Exit;
}
if (!l.Lock())
{
::ReportFailure("Failed to lock global critical section\n");
goto Exit;
}
g_FilesLinked++;
g_FileLinksProcessed++;
l.Unlock();
}
else
{
if (g_fAnnounceSkips)
::printf("Skipping hard link creation from \"%ls\" to \"%ls\"\n", static_cast<PCWSTR>(buffDestination), static_cast<PCWSTR>(buffSource));
if (!l.Lock())
{
::ReportFailure("Failed to lock global critical section\n");
goto Exit;
}
g_LinksSkipped++;
g_FileLinksProcessed++;
l.Unlock();
}
fSuccess = TRUE;
Exit:
if (::GetLastError() == ERROR_TOO_MANY_OPEN_FILES)
{
rfReQueue = true;
fSuccess = TRUE;
}
return fSuccess;
}
BOOL
MyCopyFile(
BYTE *pBuffer,
DWORD cbBuffer,
PCWSTR lpExistingFileName,
PCWSTR lpNewFileName,
BOOL fFailIfExists,
HANDLE &rhFile
)
{
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
LARGE_INTEGER liFileSize;
rhFile = INVALID_HANDLE_VALUE;
HANDLE hIn = INVALID_HANDLE_VALUE, hOut = INVALID_HANDLE_VALUE;
hIn = ::CreateFileW(lpExistingFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hIn == INVALID_HANDLE_VALUE)
{
::ReportFailure("Failed to open input file \"%ls\"\n", lpExistingFileName);
goto Exit;
}
hOut = ::CreateFileW(lpNewFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);
if (hOut == INVALID_HANDLE_VALUE)
{
::ReportFailure("Failed to open output file \"%ls\"\n", lpNewFileName);
goto Exit;
}
liFileSize.QuadPart = 0;
// let the games begin...
for (;;)
{
DWORD dwBytesRead, dwBytesWritten;
DWORD dwBytesToWrite = 0;
if (!::ReadFile(hIn, pBuffer, cbBuffer, &dwBytesRead, NULL))
{
::ReportFailure("Error reading from file \"%ls\"\n", lpExistingFileName);
goto Exit;
}
if (dwBytesRead == 0)
break;
liFileSize.QuadPart += dwBytesRead;
if (dwBytesRead != cbBuffer)
{
// We have to round to the cluster size for the write...
dwBytesToWrite = dwBytesRead + (g_dwDestinationBytesPerSector - 1);
dwBytesToWrite -= (dwBytesToWrite % g_dwDestinationBytesPerSector);
}
else
dwBytesToWrite = dwBytesRead;
if (!::WriteFile(hOut, pBuffer, dwBytesToWrite, &dwBytesWritten, NULL))
{
::ReportFailure("Error writing to file \"%ls\"\n", lpNewFileName);
goto Exit;
}
}
// If the file size wasn't a multiple of the sector size, we need to open the
// file without the unbuffered flag so that we can set its size to the exact
// correct byte count.
if ((liFileSize.QuadPart % g_dwDestinationBytesPerSector) != 0)
{
::CloseHandle(hOut);
hOut = INVALID_HANDLE_VALUE;
hOut = ::CreateFileW(lpNewFileName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hOut == INVALID_HANDLE_VALUE)
{
::ReportFailure("Unable to reopen output file \"%ls\"\n", lpNewFileName);
goto Exit;
}
// Truncate the file appropriately
if (!::MySetFilePointerEx(hOut, liFileSize, NULL, FILE_BEGIN))
{
::ReportFailure("Error setting file pointer on file \"%ls\"\n", lpNewFileName);
goto Exit;
}
if (!::SetEndOfFile(hOut))
{
::ReportFailure("Error setting end of file on file \"%ls\"\n", lpNewFileName);
goto Exit;
}
}
::CloseHandle(hIn);
hIn = INVALID_HANDLE_VALUE;
// Pass the handle back out and set it to INVALID_HANDLE_VALUE so that we don't
// try to close it in the exit path.
rhFile = hOut;
hOut = INVALID_HANDLE_VALUE;
fSuccess = TRUE;
Exit:
if (hIn != INVALID_HANDLE_VALUE)
{
CSxsPreserveLastError ple;
::CloseHandle(hIn);
ple.Restore();
}
if (hOut != INVALID_HANDLE_VALUE)
{
CSxsPreserveLastError ple;
::CloseHandle(hOut);
ple.Restore();
}
return fSuccess;
}