667 lines
17 KiB
C
667 lines
17 KiB
C
#include <windows.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>
|
|
|
|
|
|
// 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));
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// 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
|
|
)
|
|
{
|
|
if (dwSize1 != dwSize2) {
|
|
return 1;
|
|
}
|
|
|
|
// Normalize the two files and compare the results.
|
|
|
|
ZeroLibTimeStamps(pFile1, dwSize1);
|
|
ZeroLibTimeStamps(pFile2, dwSize2);
|
|
|
|
return memcmp(pFile1, pFile2, dwSize1);
|
|
}
|
|
|
|
//
|
|
// 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
|
|
)
|
|
{
|
|
if (dwSize1 != dwSize2) {
|
|
return 1;
|
|
}
|
|
|
|
return memcmp(pFile1, pFile2, dwSize1);
|
|
}
|
|
|
|
int
|
|
objcomp(
|
|
void *pFile1,
|
|
DWORD dwSize1,
|
|
void *pFile2,
|
|
DWORD dwSize2
|
|
)
|
|
{
|
|
PIMAGE_FILE_HEADER pFileHeader1 = (PIMAGE_FILE_HEADER)(pFile1);
|
|
PIMAGE_FILE_HEADER pFileHeader2 = (PIMAGE_FILE_HEADER)(pFile2);
|
|
|
|
if ((dwSize1 != dwSize2) || (pFileHeader1->Machine != pFileHeader2->Machine))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
pFileHeader1->TimeDateStamp = 0;
|
|
pFileHeader2->TimeDateStamp = 0;
|
|
return memcmp(pFile1, pFile2, dwSize1);
|
|
}
|
|
|
|
int
|
|
IsValidMachineType(USHORT usMachine)
|
|
{
|
|
if ((usMachine == IMAGE_FILE_MACHINE_I386) ||
|
|
(usMachine == IMAGE_FILE_MACHINE_IA64) ||
|
|
(usMachine == IMAGE_FILE_MACHINE_ALPHA64) ||
|
|
(usMachine == IMAGE_FILE_MACHINE_ALPHA))
|
|
{
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#define FILETYPE_ARCHIVE 0x01
|
|
#define FILETYPE_TYPELIB 0x02
|
|
#define FILETYPE_HEADER 0x03
|
|
#define FILETYPE_PE_OBJECT 0x04
|
|
#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") && IsValidMachineType(((PIMAGE_FILE_HEADER)pFile)->Machine))
|
|
{
|
|
return FILETYPE_PE_OBJECT;
|
|
}
|
|
|
|
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 time's. If they match, assume the files match.
|
|
|
|
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
|
|
}
|
|
|
|
CloseHandle(hFile1);
|
|
hFile1 = INVALID_HANDLE_VALUE;
|
|
|
|
// 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
|
|
}
|
|
|
|
CloseHandle(hFile2);
|
|
hFile2 = INVALID_HANDLE_VALUE;
|
|
|
|
// 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);
|
|
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)) {
|
|
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 ( !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;
|
|
BOOL fCopyFile = 0, fDoCopy, fTimeStampsDiffer;
|
|
int CopyErrorCode;
|
|
|
|
if (argc < 3) {
|
|
puts("pcopy <source file> <dest file>\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);
|
|
}
|
|
|
|
szSourceFile = argv[1];
|
|
szDestFile = argv[2];
|
|
|
|
fDoCopy = CheckIfCopyNecessary(szSourceFile, szDestFile, &fTimeStampsDiffer);
|
|
|
|
if (fDoCopy) {
|
|
DWORD 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;
|
|
}
|
|
} 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.
|
|
}
|
|
}
|