Windows-Server-2003/sdktools/pcopy/pcopy.c

1313 lines
39 KiB
C

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <ole2.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <malloc.h>
// #include <imagehlp.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "metadata.h"
BOOL fCheckDebugData;
int
objcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2,
BOOL fIgnoreRsrcDifferences
);
// Generic routine to write a blob out.
void
SaveTemp(
PVOID pFile,
PCHAR szFile,
DWORD FileSize
)
{
DWORD dwBytesWritten;
HANDLE hFile;
hFile = CreateFile(
szFile,
GENERIC_WRITE,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
CREATE_ALWAYS,
0,
NULL
);
if ( hFile == INVALID_HANDLE_VALUE ) {
printf("Unable to open %s\n", szFile);
return;
}
if (!WriteFile(hFile, pFile, FileSize, &dwBytesWritten, FALSE)) {
printf("Unable to write date to %s\n", szFile);
}
CloseHandle(hFile);
return;
}
// Zero out the timestamps in a PE library.
BOOL
ZeroLibTimeStamps(
PCHAR pFile,
DWORD dwSize
)
{
PIMAGE_ARCHIVE_MEMBER_HEADER pHeader;
DWORD dwOffset;
CHAR MemberSize[sizeof(pHeader->Size) + 1];
PIMAGE_FILE_HEADER pObjHeader;
ZeroMemory(MemberSize, sizeof(MemberSize));
__try {
dwOffset = IMAGE_ARCHIVE_START_SIZE;
while (dwOffset < dwSize) {
pHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)(pFile+dwOffset);
ZeroMemory(pHeader->Date, sizeof(pHeader->Date));
ZeroMemory(pHeader->Mode, sizeof(pHeader->Mode)); // Mode isn't interesting (it indicates whether the member was readonly or r/w)
dwOffset += IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR;
memcpy(MemberSize, pHeader->Size, sizeof(pHeader->Size));
// If it's not one of the special members, it must be an object/file, zero it's timestamp also.
if (memcmp(pHeader->Name, IMAGE_ARCHIVE_LINKER_MEMBER, sizeof(pHeader->Name)) &&
memcmp(pHeader->Name, IMAGE_ARCHIVE_LONGNAMES_MEMBER, sizeof(pHeader->Name)))
{
IMAGE_FILE_HEADER UNALIGNED *pFileHeader = (PIMAGE_FILE_HEADER)(pFile+dwOffset);
if ((pFileHeader->Machine == IMAGE_FILE_MACHINE_UNKNOWN) &&
(pFileHeader->NumberOfSections == IMPORT_OBJECT_HDR_SIG2))
{
// VC6 import descriptor OR ANON object header. Eitherway,
// casting to IMPORT_OBJECT_HEADER will do the trick.
((IMPORT_OBJECT_HEADER UNALIGNED *)pFileHeader)->TimeDateStamp = 0;
} else {
pFileHeader->TimeDateStamp = 0;
}
}
dwOffset += strtoul(MemberSize, NULL, 10);
dwOffset = (dwOffset + 1) & ~1; // align to word
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
}
return TRUE;
}
PBYTE RvaToVa(PIMAGE_NT_HEADERS pNtHeaders,
PBYTE pbBase,
DWORD dwRva)
{
PIMAGE_SECTION_HEADER pNtSection = NULL;
DWORD i;
pNtSection = IMAGE_FIRST_SECTION(pNtHeaders);
for (i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) {
if (dwRva >= pNtSection->VirtualAddress &&
dwRva < pNtSection->VirtualAddress + pNtSection->SizeOfRawData)
break;
pNtSection++;
}
if (i < pNtHeaders->FileHeader.NumberOfSections)
return pbBase +
(dwRva - pNtSection->VirtualAddress) +
pNtSection->PointerToRawData;
else
return NULL;
}
// Follow a resource tree rooted under the version resource and clear all data
// blocks.
void ScrubResDir(PIMAGE_NT_HEADERS pNtHeaders,
PBYTE pbBase,
PBYTE pbResBase,
PIMAGE_RESOURCE_DIRECTORY pResDir)
{
PIMAGE_RESOURCE_DIRECTORY pSubResDir;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry;
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry;
WORD i;
PBYTE pbData;
DWORD cbData;
pResEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDir + 1);
for (i = 0; i < (pResDir->NumberOfNamedEntries + pResDir->NumberOfIdEntries); i++, pResEntry++) {
if (pResEntry->DataIsDirectory) {
// Another sub-directory, recurse into it.
pSubResDir = (PIMAGE_RESOURCE_DIRECTORY)(pbResBase + pResEntry->OffsetToDirectory);
ScrubResDir(pNtHeaders, pbBase, pbResBase, pSubResDir);
} else {
// Found a data block, zap it.
pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(pbResBase + pResEntry->OffsetToData);
pbData = RvaToVa(pNtHeaders, pbBase, pDataEntry->OffsetToData);
cbData = pDataEntry->Size;
if (pbData)
ZeroMemory(pbData, cbData);
}
}
}
typedef union {
PIMAGE_OPTIONAL_HEADER32 hdr32;
PIMAGE_OPTIONAL_HEADER64 hdr64;
} PIMAGE_OPTIONAL_HEADER_BOTH;
void
WhackVersionResource(
PIMAGE_NT_HEADERS pNtHeaders,
PBYTE pFile
)
{
PIMAGE_DATA_DIRECTORY pResDataDir;
PIMAGE_RESOURCE_DIRECTORY pResDir;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pResEntry;
DWORD i;
pResDataDir =
pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC ?
&((PIMAGE_OPTIONAL_HEADER32) pNtHeaders)->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE] :
&((PIMAGE_OPTIONAL_HEADER64) pNtHeaders)->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
pResDir = (PIMAGE_RESOURCE_DIRECTORY)RvaToVa(pNtHeaders, pFile, pResDataDir->VirtualAddress);
if (pResDir) {
pResDir->TimeDateStamp = 0;
// Search for a top level resource ID of 0x0010. This should be the root
// of the version info. If we find it, clear all data blocks below this
// root.
pResEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResDir + 1 + pResDir->NumberOfNamedEntries);
for (i = 0; i < pResDir->NumberOfIdEntries; i++, pResEntry++) {
if (pResEntry->Id != 0x0010)
continue;
ScrubResDir(pNtHeaders, pFile, (PBYTE)pResDir, pResDir);
break;
}
}
}
IMAGE_FILE_HEADER UNALIGNED *
GetNextLibMember(
DWORD *pdwOffset,
void *pFile,
DWORD dwSize,
DWORD *dwMemberSize
)
{
PIMAGE_ARCHIVE_MEMBER_HEADER pHeader;
CHAR MemberSize[sizeof(pHeader->Size) + 1];
ZeroMemory(MemberSize, sizeof(MemberSize));
__try {
while (*pdwOffset < dwSize) {
pHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)((PCHAR)pFile+*pdwOffset);
*pdwOffset += IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR;
memcpy(MemberSize, pHeader->Size, sizeof(pHeader->Size));
// If it's not one of the special members, it must be an object/file, zero it's timestamp also.
if (memcmp(pHeader->Name, IMAGE_ARCHIVE_LINKER_MEMBER, sizeof(pHeader->Name)) &&
memcmp(pHeader->Name, IMAGE_ARCHIVE_LONGNAMES_MEMBER, sizeof(pHeader->Name)))
{
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((PCHAR)pFile+*pdwOffset);
*dwMemberSize = strtoul(MemberSize, NULL, 10);
*pdwOffset += *dwMemberSize;
*pdwOffset = (*pdwOffset + 1) & ~1; // align to word
return pFileHeader;
}
*pdwOffset += strtoul(MemberSize, NULL, 10);
*pdwOffset = (*pdwOffset + 1) & ~1; // align to word
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
}
return NULL;
}
BOOL
CompareLibMembers(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2
)
{
DWORD dwOffset1, dwOffset2, dwMemberSize1, dwMemberSize2;
IMAGE_FILE_HEADER UNALIGNED *pObjHeader1;
IMAGE_FILE_HEADER UNALIGNED *pObjHeader2;
__try {
dwOffset1 = dwOffset2 = IMAGE_ARCHIVE_START_SIZE;
pObjHeader1 = GetNextLibMember(&dwOffset1, pFile1, dwSize1, &dwMemberSize1);
pObjHeader2 = GetNextLibMember(&dwOffset2, pFile2, dwSize2, &dwMemberSize2);
while (pObjHeader1 && pObjHeader2) {
if ((pObjHeader1->Machine != pObjHeader2->Machine) ||
(pObjHeader1->NumberOfSections != pObjHeader2->NumberOfSections))
{
return TRUE; // Machine and/or Section count doesn't match - files differ.
}
if ((pObjHeader1->Machine == IMAGE_FILE_MACHINE_UNKNOWN) &&
(pObjHeader1->NumberOfSections == IMPORT_OBJECT_HDR_SIG2))
{
// VC6 import descriptor OR ANON object header. Check the Version.
if (((IMPORT_OBJECT_HEADER UNALIGNED *)pObjHeader1)->Version !=
((IMPORT_OBJECT_HEADER UNALIGNED *)pObjHeader2)->Version)
{
// Versions don't match, these aren't same members. files differ
return TRUE;
}
if (((IMPORT_OBJECT_HEADER UNALIGNED *)pObjHeader1)->Version) {
// non-zero version indicates ANON_OBJECT_HEADER
if (memcmp(pObjHeader1, pObjHeader2, sizeof(ANON_OBJECT_HEADER) + ((ANON_OBJECT_HEADER UNALIGNED *)pObjHeader1)->SizeOfData)) {
// members don't match, files differ.
return TRUE;
}
} else {
// zero version indicates IMPORT_OBJECT_HEADER
if (memcmp(pObjHeader1, pObjHeader2, sizeof(IMPORT_OBJECT_HEADER) + ((IMPORT_OBJECT_HEADER UNALIGNED *)pObjHeader1)->SizeOfData)) {
// members don't match, files differ.
return TRUE;
}
}
} else {
// It's a real object - compare the non-debug data in the objects to see if they match
// ignore resource data - you can't extract it from a lib anyway.
if (objcomp(pObjHeader1, dwMemberSize1, pObjHeader2, dwMemberSize2, TRUE)) {
return TRUE;
}
}
pObjHeader1 = GetNextLibMember(&dwOffset1, pFile1, dwSize1, &dwMemberSize1);
pObjHeader2 = GetNextLibMember(&dwOffset2, pFile2, dwSize2, &dwMemberSize2);
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
}
if (pObjHeader1 != pObjHeader2) {
// Both s/b null. If they're not, one lib has more members and they differ
return TRUE;
} else {
return FALSE;
}
}
//
// Compare two libraries ignoring info that isn't relevant
// (timestamps for now, debug info later).
//
int
libcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2
)
{
// Normalize the two files and compare the results.
ZeroLibTimeStamps(pFile1, dwSize1);
ZeroLibTimeStamps(pFile2, dwSize2);
if (dwSize1 == dwSize2) {
if (!memcmp(pFile1, pFile2, dwSize1)) {
// Files match, don't copy
return FALSE;
}
}
// OK. Zeroing out timestamps didn't work. Compare the members in each lib.
// If they match, the libs match.
return CompareLibMembers(pFile1, dwSize1, pFile2, dwSize2);
}
//
// Compare two headers. For now, just use memcmp. Later, we'll need to
// handle MIDL generated timestamp differences and check for comment only changes.
//
int
hdrcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2
)
{
if (dwSize1 != dwSize2) {
return 1;
}
return memcmp(pFile1, pFile2, dwSize1);
}
//
// Compare two typelibs. Initially just memcmp. Use DougF's typelib code later.
//
int
tlbcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2
)
{
PIMAGE_NT_HEADERS pNtHeader1, pNtHeader2;
if (dwSize1 != dwSize2) {
return 1;
}
pNtHeader1 = RtlpImageNtHeader(pFile1);
pNtHeader2 = RtlpImageNtHeader(pFile2);
if (!pNtHeader1 || !pNtHeader2) {
// Not both PE images - just do a memcmp
return memcmp(pFile1, pFile2, dwSize1);
}
pNtHeader1->FileHeader.TimeDateStamp = 0;
pNtHeader2->FileHeader.TimeDateStamp = 0;
// Eliminate the version resource from the mix
WhackVersionResource(pNtHeader1, pFile1);
WhackVersionResource(pNtHeader2, pFile2);
return memcmp(pFile1, pFile2, dwSize1);
}
int
objcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2,
BOOL fIgnoreRsrcDifferences
)
{
IMAGE_FILE_HEADER UNALIGNED *pFileHeader1 = (PIMAGE_FILE_HEADER)(pFile1);
IMAGE_FILE_HEADER UNALIGNED *pFileHeader2 = (PIMAGE_FILE_HEADER)(pFile2);
IMAGE_SECTION_HEADER UNALIGNED *pSecHeader1;
IMAGE_SECTION_HEADER UNALIGNED *pSecHeader2;
if (pFileHeader1->Machine != pFileHeader2->Machine) {
// Machines don't match, files differ
return TRUE;
}
if (dwSize1 == dwSize2) {
// See if this is a simple test - same size files, zero out timestamps and compare
pFileHeader1->TimeDateStamp = 0;
pFileHeader2->TimeDateStamp = 0;
if (!memcmp(pFile1, pFile2, dwSize1)) {
// Files match, don't copy
return FALSE;
} else {
if (fCheckDebugData) {
// Sizes match, contents don't (must be debug data) - do the copy
return TRUE;
}
}
}
if (fCheckDebugData) {
// Sizes don't match (must be debug data differences) - do a copy
return TRUE;
}
// Harder. Ignore the debug data in each and compare what's left.
if (pFileHeader1->NumberOfSections != pFileHeader2->NumberOfSections) {
// Different number of sections - files differ
return TRUE;
}
pSecHeader1 = (PIMAGE_SECTION_HEADER)((PCHAR)pFile1+sizeof(IMAGE_FILE_HEADER) + pFileHeader1->SizeOfOptionalHeader);
pSecHeader2 = (PIMAGE_SECTION_HEADER)((PCHAR)pFile2+sizeof(IMAGE_FILE_HEADER) + pFileHeader2->SizeOfOptionalHeader);
while (pFileHeader1->NumberOfSections--) {
if (memcmp(pSecHeader1->Name, pSecHeader2->Name, IMAGE_SIZEOF_SHORT_NAME)) {
// Section names don't match, can't compare - files differ
return TRUE;
}
if (memcmp(pSecHeader1->Name, ".debug$", 7) &&
memcmp(pSecHeader1->Name, ".drectve", 8) &&
!(!memcmp(pSecHeader1->Name, ".rsrc$", 6) && fIgnoreRsrcDifferences) )
{
// Not a debug section and not a linker directive, compare for match.
if (pSecHeader1->SizeOfRawData != pSecHeader2->SizeOfRawData) {
// Section sizes don't match - files differ.
return TRUE;
}
if (pSecHeader1->PointerToRawData || pSecHeader2->PointerToRawData) {
if (memcmp((PCHAR)pFile1+pSecHeader1->PointerToRawData, (PCHAR)pFile2+pSecHeader2->PointerToRawData, pSecHeader1->SizeOfRawData)) {
// Raw data doesn't match - files differ
return TRUE;
}
}
}
pSecHeader1++;
pSecHeader2++;
}
// Compared the sections and they match - files don't differ.
return FALSE;
}
int
IsValidMachineType(USHORT usMachine)
{
if ((usMachine == IMAGE_FILE_MACHINE_I386) ||
(usMachine == IMAGE_FILE_MACHINE_AMD64) ||
(usMachine == IMAGE_FILE_MACHINE_IA64) ||
(usMachine == IMAGE_FILE_MACHINE_ALPHA64) ||
(usMachine == IMAGE_FILE_MACHINE_ALPHA))
{
return TRUE;
} else {
return FALSE;
}
}
// Internal metadata header.
typedef struct
{
ULONG lSignature; // "Magic" signature.
USHORT iMajorVer; // Major file version.
USHORT iMinorVer; // Minor file version.
ULONG iExtraData; // Offset to next structure of information
ULONG iVersionString; // Length of version string
BYTE pVersion[0]; // Version string
} MD_STORAGESIGNATURE, *PMD_STORAGESIGNATURE;
int
normalizeasm(
PBYTE pFile,
DWORD dwSize,
PBYTE *ppbMetadata,
DWORD *pcbMetadata
)
{
PIMAGE_NT_HEADERS pNtHeaders;
BOOL f32Bit;
PIMAGE_OPTIONAL_HEADER_BOTH pOptHeader;
PIMAGE_DATA_DIRECTORY pCorDataDir;
PIMAGE_DATA_DIRECTORY pDebugDataDir;
PIMAGE_DEBUG_DIRECTORY pDebugDir;
PBYTE pbDebugData;
DWORD cbDebugData;
PIMAGE_DATA_DIRECTORY pExportDataDir;
PIMAGE_EXPORT_DIRECTORY pExportDir;
PIMAGE_COR20_HEADER pCorDir;
PMD_STORAGESIGNATURE pMetadata;
DWORD i;
PBYTE pbStrongNameSig;
DWORD cbStrongNameSig;
// Check that this is a standard PE.
pNtHeaders = RtlpImageNtHeader(pFile);
if (pNtHeaders == NULL)
return FALSE;
// Managed assemblies still burn in a machine type (though they should be portable).
if (!IsValidMachineType(pNtHeaders->FileHeader.Machine))
return FALSE;
// Clear the file timestamp.
pNtHeaders->FileHeader.TimeDateStamp = 0;
// Determine whether we're dealing with a 32 or 64 bit PE.
if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
f32Bit = TRUE;
else if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
f32Bit = FALSE;
else
return FALSE;
pOptHeader.hdr32 = (PVOID)&pNtHeaders->OptionalHeader;
// Clear the checksum.
if (f32Bit)
pOptHeader.hdr32->CheckSum = 0;
else
pOptHeader.hdr64->CheckSum = 0;
pCorDataDir = f32Bit ?
&pOptHeader.hdr32->DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] :
&pOptHeader.hdr64->DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
if (pCorDataDir->VirtualAddress == 0)
return FALSE;
// Zero any debug data.
pDebugDataDir = f32Bit ?
&pOptHeader.hdr32->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] :
&pOptHeader.hdr64->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
if (pDebugDataDir->VirtualAddress) {
pDebugDir = (PIMAGE_DEBUG_DIRECTORY)RvaToVa(pNtHeaders, pFile, pDebugDataDir->VirtualAddress);
if (pDebugDir) {
pDebugDir->TimeDateStamp = 0;
pbDebugData = pFile + pDebugDir->PointerToRawData;
cbDebugData = pDebugDir->SizeOfData;
ZeroMemory(pbDebugData, cbDebugData);
}
}
// Zero export data timestamp.
pExportDataDir = f32Bit ?
&pOptHeader.hdr32->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] :
&pOptHeader.hdr64->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (pExportDataDir->VirtualAddress) {
pExportDir = (PIMAGE_EXPORT_DIRECTORY)RvaToVa(pNtHeaders, pFile, pExportDataDir->VirtualAddress);
if (pExportDir)
pExportDir->TimeDateStamp = 0;
}
// Locate metadata blob in image and return the results for comparison.
pCorDir = (PIMAGE_COR20_HEADER)RvaToVa(pNtHeaders, pFile, pCorDataDir->VirtualAddress);
if (pCorDir) {
*ppbMetadata = RvaToVa(pNtHeaders, pFile, pCorDir->MetaData.VirtualAddress);
*pcbMetadata = pCorDir->MetaData.Size;
// Remove metadata version string (contains a build number).
pMetadata = (PMD_STORAGESIGNATURE)*ppbMetadata;
if (pMetadata->lSignature != 0x424A5342)
return FALSE;
for (i = 0; i < pMetadata->iVersionString; i++)
pMetadata->pVersion[i] = 0;
// Clear any strong name signature.
pbStrongNameSig = RvaToVa(pNtHeaders, pFile, pCorDir->StrongNameSignature.VirtualAddress);
cbStrongNameSig = pCorDir->StrongNameSignature.Size;
ZeroMemory(pbStrongNameSig, cbStrongNameSig);
}
WhackVersionResource(pNtHeaders, pFile);
return TRUE;
}
//
// Open metadata scope on memory -- return MVID and zero any file hashes.
//
int
scanmetadata(
IMetaDataDispenser *pDispenser,
PBYTE pbMetadata,
DWORD cbMetadata,
GUID *pGUID
)
{
IMetaDataImport *pImport;
IMetaDataAssemblyImport *pAsmImport;
HCORENUM hEnum = 0;
DWORD dwFiles;
mdFile *pFileTokens;
DWORD i;
PBYTE pbHash;
DWORD cbHash;
// Ask the metadata engine to look at the in-memory metadata blob.
if (FAILED(pDispenser->lpVtbl->OpenScopeOnMemory(pDispenser,
pbMetadata,
cbMetadata,
0x80,
&IID_IMetaDataImport,
(IUnknown **)&pImport)))
return FALSE;
// Retrieve the MVID value.
if (FAILED(pImport->lpVtbl->GetScopeProps(pImport,
NULL,
0,
NULL,
pGUID)))
return FALSE;
// Get an assembly importer interface as well as the straight module importer.
if (FAILED(pImport->lpVtbl->QueryInterface(pImport, &IID_IMetaDataAssemblyImport, (void**)&pAsmImport)))
return FALSE;
// Enumerate the file (external module) entries.
if (FAILED(pAsmImport->lpVtbl->EnumFiles(pAsmImport, &hEnum, NULL, 0, NULL)))
return FALSE;
if (FAILED(pImport->lpVtbl->CountEnum(pImport, hEnum, &dwFiles)))
return FALSE;
pFileTokens = (mdFile*)malloc(dwFiles * sizeof(mdFile));
if (!pFileTokens)
return FALSE;
if (FAILED(pAsmImport->lpVtbl->EnumFiles(pAsmImport,
&hEnum,
pFileTokens,
dwFiles,
&dwFiles)))
{
free(pFileTokens);
return FALSE;
}
// Look at each file reference in turn. Metadata actually gives us back the
// real address of the hash blob so we can zero it directly.
for (i = 0; i < dwFiles; i++) {
if (FAILED(pAsmImport->lpVtbl->GetFileProps(pAsmImport,
pFileTokens[i],
NULL,
0,
NULL,
(const void*)&pbHash,
&cbHash,
NULL)))
{
free(pFileTokens);
return FALSE;
}
ZeroMemory(pbHash, cbHash);
}
pAsmImport->lpVtbl->Release(pAsmImport);
pImport->lpVtbl->Release(pImport);
free(pFileTokens);
return TRUE;
}
//
// Compare two managed assemblies.
//
int
asmcomp(
void *pFile1,
DWORD dwSize1,
void *pFile2,
DWORD dwSize2
)
{
PBYTE pbMetadata1;
DWORD cbMetadata1;
PBYTE pbMetadata2;
DWORD cbMetadata2;
IMetaDataDispenser *pDispenser = NULL;
BYTE mvid1[16];
BYTE mvid2[16];
DWORD i, j;
if (!normalizeasm(pFile1, dwSize1, &pbMetadata1, &cbMetadata1))
return TRUE;
if (!normalizeasm(pFile2, dwSize2, &pbMetadata2, &cbMetadata2))
return TRUE;
if (cbMetadata1 != cbMetadata2)
return TRUE;
// It's hard to normalize metadata blobs. They contain two major types of
// per-build churn: a module MVID (GUID) and zero or more file hashes for
// included modules. These are hard to locate, they're not part of a
// simplistic file format, but are stored in a fully fledged relational
// database with non-trivial schema.
// Instead, we use the metadata engine itself to give us the file hashes (it
// actually gives us the addresses of these in memory so we can zero them
// directly) and the MVID (can't use the same trick here, but we can use the
// MVID value to decide whether to discount metadata deltas).
// We need COM.
if (FAILED(CoInitialize(NULL)))
return TRUE;
// And the root interface into the runtime metadata engine.
if (FAILED(CoCreateInstance(&CLSID_CorMetaDataDispenser,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IMetaDataDispenser,
(void **)&pDispenser)))
return TRUE;
if (!scanmetadata(pDispenser,
pbMetadata1,
cbMetadata1,
(GUID*)mvid1))
return TRUE;
if (!scanmetadata(pDispenser,
pbMetadata2,
cbMetadata2,
(GUID*)mvid2))
return TRUE;
// Locate mvids in the metadata blobs (they should be at the same offset).
for (i = 0; i < cbMetadata1; i++)
if (pbMetadata1[i] == mvid1[0]) {
for (j = 0; j < 16; j++)
if (pbMetadata1[i + j] != mvid1[j] ||
pbMetadata2[i + j] != mvid2[j])
break;
if (j == 16) {
// Found the MVIDs in both assemblies, zero them.
ZeroMemory(&pbMetadata1[i], 16);
ZeroMemory(&pbMetadata2[i], 16);
printf("Zapped MVID\n");
}
}
return memcmp(pFile1, pFile2, dwSize1);
}
#define FILETYPE_ARCHIVE 0x01
#define FILETYPE_TYPELIB 0x02
#define FILETYPE_HEADER 0x03
#define FILETYPE_PE_OBJECT 0x04
#define FILETYPE_MANAGED 0x05
#define FILETYPE_UNKNOWN 0xff
//
// Given a file, attempt to determine what it is. Initial pass will just use file
// extensions except for libs that we can search for the <arch>\n start.
//
int
DetermineFileType(
void *pFile,
DWORD dwSize,
CHAR *szFileName
)
{
char szExt[_MAX_EXT];
// Let's see if it's a library first:
if ((dwSize >= IMAGE_ARCHIVE_START_SIZE) &&
!memcmp(pFile, IMAGE_ARCHIVE_START, IMAGE_ARCHIVE_START_SIZE))
{
return FILETYPE_ARCHIVE;
}
// For now, guess about the headers/tlb based on the extension.
_splitpath(szFileName, NULL, NULL, NULL, szExt);
if (!_stricmp(szExt, ".h") ||
!_stricmp(szExt, ".hxx") ||
!_stricmp(szExt, ".hpp") ||
!_stricmp(szExt, ".w") ||
!_stricmp(szExt, ".inc"))
{
return FILETYPE_HEADER;
}
if (!_stricmp(szExt, ".tlb"))
{
return FILETYPE_TYPELIB;
}
if ((!_stricmp(szExt, ".obj") ||
!_stricmp(szExt, ".lib"))
&&
IsValidMachineType(((PIMAGE_FILE_HEADER)pFile)->Machine))
{
return FILETYPE_PE_OBJECT;
}
if (!_stricmp(szExt, ".dll"))
{
return FILETYPE_MANAGED;
}
return FILETYPE_UNKNOWN;
}
//
// Determine if two files are materially different.
//
BOOL
CheckIfCopyNecessary(
char *szSourceFile,
char *szDestFile,
BOOL *fTimeStampsDiffer
)
{
PVOID pFile1 = NULL, pFile2 = NULL;
DWORD File1Size, File2Size, dwBytesRead, dwErrorCode = ERROR_SUCCESS;
HANDLE hFile1 = INVALID_HANDLE_VALUE;
HANDLE hFile2 = INVALID_HANDLE_VALUE;
BOOL fCopy = FALSE;
int File1Type, File2Type;
FILETIME FileTime1, FileTime2;
hFile1 = CreateFile(
szDestFile,
GENERIC_READ,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_EXISTING,
0,
NULL
);
if ( hFile1 == INVALID_HANDLE_VALUE ) {
fCopy = TRUE; // Dest file doesn't exist. Always do the copy.
goto Exit;
}
// Now get the second file.
hFile2 = CreateFile(
szSourceFile,
GENERIC_READ,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_EXISTING,
0,
NULL
);
if ( hFile2 == INVALID_HANDLE_VALUE ) {
// If the source is missing, always skip the copy (don't want to delete the dest file).
dwErrorCode = ERROR_FILE_NOT_FOUND;
goto Exit;
}
// Get the file times and sizes.
if (!GetFileTime(hFile1, NULL, NULL, &FileTime1)) {
dwErrorCode = GetLastError();
goto Exit;
}
if (!GetFileTime(hFile2, NULL, NULL, &FileTime2)) {
dwErrorCode = GetLastError();
goto Exit;
}
if (!memcmp(&FileTime1, &FileTime2, sizeof(FILETIME))) {
*fTimeStampsDiffer = FALSE;
goto Exit;
}
*fTimeStampsDiffer = TRUE;
// Read file 1 in.
File1Size = GetFileSize(hFile1, NULL);
pFile1 = malloc(File1Size);
if (!pFile1) {
dwErrorCode = ERROR_OUTOFMEMORY;
goto Exit; // Can't compare - don't copy.
}
SetFilePointer(hFile1, 0, 0, FILE_BEGIN);
if (!ReadFile(hFile1, pFile1, File1Size, &dwBytesRead, FALSE)) {
dwErrorCode = GetLastError();
goto Exit; // Can't compare - don't copy
}
// Read file 2 in.
File2Size = GetFileSize(hFile2, NULL);
pFile2 = malloc(File2Size);
if (!pFile2) {
dwErrorCode = ERROR_OUTOFMEMORY;
goto Exit; // Can't compare - don't copy.
}
SetFilePointer(hFile2, 0, 0, FILE_BEGIN);
if (!ReadFile(hFile2, pFile2, File2Size, &dwBytesRead, FALSE)) {
dwErrorCode = GetLastError();
goto Exit; // Can't compare - don't copy
}
// Let's see what we've got.
File1Type = DetermineFileType(pFile1, File1Size, szSourceFile);
File2Type = DetermineFileType(pFile2, File2Size, szDestFile);
if (File1Type == File2Type) {
switch (File1Type) {
case FILETYPE_ARCHIVE:
fCopy = libcomp(pFile1, File1Size, pFile2, File2Size);
break;
case FILETYPE_HEADER:
fCopy = hdrcomp(pFile1, File1Size, pFile2, File2Size);
break;
case FILETYPE_TYPELIB:
fCopy = tlbcomp(pFile1, File1Size, pFile2, File2Size);
break;
case FILETYPE_PE_OBJECT:
fCopy = objcomp(pFile1, File1Size, pFile2, File2Size, FALSE);
break;
case FILETYPE_MANAGED:
fCopy = asmcomp(pFile1, File1Size, pFile2, File2Size);
break;
case FILETYPE_UNKNOWN:
default:
if (File1Size == File2Size) {
fCopy = memcmp(pFile1, pFile2, File1Size);
} else {
fCopy = TRUE;
}
}
} else {
// They don't match according to file extensions - just memcmp them.
if (File1Size == File2Size) {
fCopy = memcmp(pFile1, pFile2, File1Size);
} else {
fCopy = TRUE;
}
}
Exit:
if (pFile1)
free(pFile1);
if (pFile2)
free(pFile2);
if (hFile1 != INVALID_HANDLE_VALUE)
CloseHandle(hFile1);
if (hFile2 != INVALID_HANDLE_VALUE)
CloseHandle(hFile2);
SetLastError(dwErrorCode);
return fCopy;
}
BOOL
UpdateDestTimeStamp(
char *szSourceFile,
char *szDestFile
)
{
HANDLE hFile;
FILETIME LastWriteTime;
DWORD dwAttributes;
BOOL fTweakAttributes;
hFile = CreateFile(
szSourceFile,
GENERIC_READ,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_EXISTING,
0,
NULL
);
if ( hFile == INVALID_HANDLE_VALUE ) {
return FALSE;
}
if (!GetFileTime(hFile, NULL, NULL, &LastWriteTime)) {
CloseHandle(hFile);
return FALSE;
}
CloseHandle(hFile);
dwAttributes = GetFileAttributes(szDestFile);
if ((dwAttributes != (DWORD) -1) && (dwAttributes & FILE_ATTRIBUTE_READONLY))
{
// Make sure it's not readonly
SetFileAttributes(szDestFile, dwAttributes & ~FILE_ATTRIBUTE_READONLY);
fTweakAttributes = TRUE;
} else {
fTweakAttributes = FALSE;
}
hFile = CreateFile(
szDestFile,
GENERIC_WRITE,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_EXISTING,
0,
NULL
);
if ( hFile == INVALID_HANDLE_VALUE ) {
return FALSE;
}
SetFileTime(hFile, NULL, NULL, &LastWriteTime);
CloseHandle(hFile);
if (fTweakAttributes) {
// Put the readonly attribute back on.
if (!SetFileAttributes(szDestFile, dwAttributes)) {
printf("PCOPY: SetFileAttributes(%s, %X) failed - error code: %d\n", szDestFile, dwAttributes, GetLastError());
}
}
return TRUE;
}
//
// Log routine to find out what files were actually copied and why.
//
void
LogCopyFile(
char * szSource,
char * szDest,
BOOL fCopy,
DWORD dwReturnCode
)
{
if (getenv("LOG_PCOPY")) {
FILE *FileHandle = fopen("\\pcopy.log", "a");
if (FileHandle) {
time_t Time;
UCHAR const *szTime = "";
Time = time(NULL);
szTime = ctime(&Time);
fprintf(FileHandle, "%s: %.*s, %s, %s, %d\n", fCopy ? (dwReturnCode ? "ERROR" : "DONE") : "SKIP", strlen(szTime)-1, szTime, szSource, szDest, dwReturnCode);
fclose(FileHandle);
}
}
}
BOOL
MyMakeSureDirectoryPathExists(
char * DirPath
)
{
LPSTR p;
DWORD dw;
char szDir[_MAX_DIR];
char szMakeDir[_MAX_DIR];
_splitpath(DirPath, szMakeDir, szDir, NULL, NULL);
strcat(szMakeDir, szDir);
p = szMakeDir;
dw = GetFileAttributes(szMakeDir);
if ( (dw != (DWORD) -1) && (dw & FILE_ATTRIBUTE_DIRECTORY) ) {
// Directory already exists.
return TRUE;
}
// If the second character in the path is "\", then this is a UNC
// path, and we should skip forward until we reach the 2nd \ in the path.
if ((*p == '\\') && (*(p+1) == '\\')) {
p++; // Skip over the first \ in the name.
p++; // Skip over the second \ in the name.
// Skip until we hit the first "\" (\\Server\).
while (*p && *p != '\\') {
p = p++;
}
// Advance over it.
if (*p) {
p++;
}
// Skip until we hit the second "\" (\\Server\Share\).
while (*p && *p != '\\') {
p = p++;
}
// Advance over it also.
if (*p) {
p++;
}
} else
// Not a UNC. See if it's <drive>:
if (*(p+1) == ':' ) {
p++;
p++;
// If it exists, skip over the root specifier
if (*p && (*p == '\\')) {
p++;
}
}
while( *p ) {
if ( *p == '\\' ) {
*p = '\0';
dw = GetFileAttributes(szMakeDir);
// Nothing exists with this name. Try to make the directory name and error if unable to.
if ( dw == 0xffffffff ) {
if (strlen(szMakeDir)) { // Don't try to md <empty string>
if ( !CreateDirectory(szMakeDir,NULL) ) {
if( GetLastError() != ERROR_ALREADY_EXISTS ) {
return FALSE;
}
}
}
} else {
if ( (dw & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY ) {
// Something exists with this name, but it's not a directory... Error
return FALSE;
}
}
*p = '\\';
}
p = p++;
}
return TRUE;
}
int
__cdecl
main(
int argc,
char *argv[]
)
{
char *szSourceFile, *szDestFile, *szAlternateSourceFile;
BOOL fCopyFile = 0, fDoCopy, fTimeStampsDiffer;
int CopyErrorCode;
if (argc < 3) {
puts("pcopy <-d> <source file> <dest file>\n"
" -d switch: compare debug data\n"
" (by default, debug differences are ignored)\n"
"Returns: -1 if no copy necessary (no material change to the files)\n"
" 0 if a successful copy was made\n"
" otherwise the error code for why the copy was unsuccessful\n");
return ((int)ERROR_INVALID_COMMAND_LINE);
}
if (argv[1][0] == '-' && argv[1][1] == 'd') {
fCheckDebugData = TRUE;
szSourceFile = argv[2];
szDestFile = argv[3];
} else {
szSourceFile = argv[1];
szDestFile = argv[2];
}
if (getenv("PCOPY_COMPARE_DEBUG")) {
fCheckDebugData = TRUE;
}
szAlternateSourceFile = strstr(szSourceFile, "::");
if (szAlternateSourceFile) {
*szAlternateSourceFile = '\0'; // Null terminte szSourceFile
szAlternateSourceFile+=2; // Advance past the ::
}
fDoCopy = CheckIfCopyNecessary(szSourceFile, szDestFile, &fTimeStampsDiffer);
if (fDoCopy) {
DWORD dwAttributes;
CopyAlternate:
dwAttributes = GetFileAttributes(szDestFile);
if (dwAttributes != (DWORD) -1) {
// Make sure it's not readonly
SetFileAttributes(szDestFile, dwAttributes & ~FILE_ATTRIBUTE_READONLY);
}
// Make sure destination directory exists.
MyMakeSureDirectoryPathExists(szDestFile);
fCopyFile = CopyFileA(szSourceFile, szDestFile, FALSE);
if (!fCopyFile) {
CopyErrorCode = (int) GetLastError();
} else {
dwAttributes = GetFileAttributes(szDestFile);
if (dwAttributes != (DWORD) -1) {
// Make sure the dest is read/write
SetFileAttributes(szDestFile, dwAttributes & ~FILE_ATTRIBUTE_READONLY);
}
CopyErrorCode = 0;
}
if (!CopyErrorCode && szAlternateSourceFile) {
CHAR Drive[_MAX_DRIVE];
CHAR Dir[_MAX_DIR];
CHAR FileName[_MAX_FNAME];
CHAR Ext[_MAX_EXT];
CHAR NewDest[_MAX_PATH];
_splitpath(szDestFile, Drive, Dir, NULL, NULL);
_splitpath(szAlternateSourceFile, NULL, NULL, FileName, Ext);
_makepath(NewDest, Drive, Dir, FileName, Ext);
szSourceFile = szAlternateSourceFile;
szAlternateSourceFile=NULL;
szDestFile=NewDest;
goto CopyAlternate;
}
} else {
CopyErrorCode = GetLastError();
if (!CopyErrorCode && fTimeStampsDiffer) {
// No copy necessary. Touch the timestamp on the dest to match the source.
UpdateDestTimeStamp(szSourceFile, szDestFile);
}
}
LogCopyFile(szSourceFile, szDestFile, fDoCopy, CopyErrorCode);
if (fDoCopy) {
return CopyErrorCode;
} else {
return CopyErrorCode ? CopyErrorCode : -1; // No copy necessary.
}
}