mirror of https://github.com/tongzx/nt5src
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.
2619 lines
88 KiB
2619 lines
88 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
copy.c
|
|
|
|
Abstract:
|
|
|
|
High-level file copy/installation functions
|
|
|
|
Author:
|
|
|
|
Ted Miller (tedm) 14-Feb-1995
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#pragma hdrstop
|
|
|
|
#include <winioctl.h>
|
|
|
|
#define FILE_COMPARE_BLOCK_SIZE (0x1000000)
|
|
|
|
ULONG
|
|
_cdecl
|
|
DbgPrint(
|
|
PCH Format,
|
|
...
|
|
);
|
|
|
|
|
|
//
|
|
// Mask for all copy flags that will require us to determine
|
|
// version information.
|
|
//
|
|
#define SP_COPY_MASK_NEEDVERINFO (SP_COPY_NEWER_OR_SAME | SP_COPY_NEWER_ONLY | SP_COPY_FORCE_NEWER | SP_COPY_LANGUAGEAWARE)
|
|
|
|
|
|
VOID
|
|
pGetVersionText(
|
|
OUT PTSTR VersionText,
|
|
IN DWORDLONG Version
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert a 64-bit version number into either
|
|
n.n.n.n or "0"
|
|
|
|
Arguments:
|
|
|
|
VersionText - buffer, big enough to hold 4x16 bit numbers
|
|
Version - 64-bit version, or 0 if no version
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
if (Version == 0) {
|
|
lstrcpy(VersionText,TEXT("0"));
|
|
} else {
|
|
int m1 = (int)((Version >> 48) & 0xffff);
|
|
int m2 = (int)((Version >> 32) & 0xffff);
|
|
int m3 = (int)((Version >> 16) & 0xffff);
|
|
int m4 = (int)(Version & 0xffff);
|
|
|
|
wsprintf(VersionText,TEXT("%d.%d.%d.%d"),m1,m2,m3,m4);
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
CreateTargetAsLinkToMaster(
|
|
IN PSP_FILE_QUEUE Queue,
|
|
IN PCTSTR FullSourceFilename,
|
|
IN PCTSTR FullTargetFilename,
|
|
IN PVOID CopyMsgHandler OPTIONAL,
|
|
IN PVOID Context OPTIONAL,
|
|
IN BOOL IsMsgHandlerNativeCharWidth
|
|
)
|
|
{
|
|
#ifdef ANSI_SETUPAPI
|
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
|
#else
|
|
|
|
PTSTR p;
|
|
TCHAR c;
|
|
DWORD bytesReturned;
|
|
DWORD error;
|
|
BOOL ok;
|
|
DWORD sourceLength;
|
|
DWORD targetLength;
|
|
DWORD sourceDosDevLength;
|
|
DWORD targetDosDevLength;
|
|
DWORD copyFileSize;
|
|
PSI_COPYFILE copyFile;
|
|
PCHAR s;
|
|
HANDLE targetHandle;
|
|
|
|
//
|
|
// Get the name of the source directory.
|
|
//
|
|
p = _tcsrchr( FullSourceFilename, TEXT('\\') );
|
|
if ( (p == NULL) || (p == FullSourceFilename) ) {
|
|
return ERROR_FILE_NOT_FOUND; // copy by usual means
|
|
}
|
|
if ( *(p-1) == TEXT(':') ) {
|
|
p++;
|
|
}
|
|
c = *p;
|
|
*p = 0;
|
|
|
|
//
|
|
// If this is the same as the previous source directory, then we already
|
|
// have a handle to the directory; otherwise, close the old handle and
|
|
// open a handle to this directory.
|
|
//
|
|
if ( (Queue->SisSourceDirectory == NULL) ||
|
|
(_tcsicmp(FullSourceFilename, Queue->SisSourceDirectory) != 0) ) {
|
|
|
|
if ( Queue->SisSourceHandle != INVALID_HANDLE_VALUE ) {
|
|
CloseHandle( Queue->SisSourceHandle );
|
|
}
|
|
Queue->SisSourceHandle = CreateFile(
|
|
FullSourceFilename,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL
|
|
);
|
|
if ( Queue->SisSourceHandle == INVALID_HANDLE_VALUE ) {
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
if ( Queue->SisSourceDirectory != NULL ) {
|
|
MyFree( Queue->SisSourceDirectory );
|
|
}
|
|
Queue->SisSourceDirectory = DuplicateString( FullSourceFilename );
|
|
|
|
//
|
|
// If the DuplicateString fails, we press on. Because SisSourceDirectory
|
|
// is NULL, we'll reopen the source directory next time.
|
|
//
|
|
}
|
|
|
|
*p = c;
|
|
|
|
//
|
|
// Build the FSCTL command buffer.
|
|
//
|
|
|
|
sourceLength = (_tcslen(FullSourceFilename) + 1) * sizeof(TCHAR);
|
|
if ( *FullSourceFilename != TEXT('\\') ) {
|
|
sourceDosDevLength = _tcslen(TEXT("\\??\\")) * sizeof(TCHAR);
|
|
} else {
|
|
sourceDosDevLength = 0;
|
|
}
|
|
targetLength = (_tcslen(FullTargetFilename) + 1) * sizeof(TCHAR);
|
|
if ( *FullTargetFilename != TEXT('\\') ) {
|
|
targetDosDevLength = _tcslen(TEXT("\\??\\")) * sizeof(TCHAR);
|
|
} else {
|
|
targetDosDevLength = 0;
|
|
}
|
|
|
|
copyFileSize = FIELD_OFFSET(SI_COPYFILE, FileNameBuffer) +
|
|
sourceDosDevLength + sourceLength +
|
|
targetDosDevLength + targetLength;
|
|
|
|
copyFile = MyMalloc( copyFileSize );
|
|
if ( copyFile == NULL ) {
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
copyFile->SourceFileNameLength = sourceDosDevLength + sourceLength;
|
|
copyFile->DestinationFileNameLength = targetDosDevLength + targetLength;
|
|
copyFile->Flags = COPYFILE_SIS_REPLACE;
|
|
|
|
s = (PCHAR)copyFile->FileNameBuffer;
|
|
if ( sourceDosDevLength != 0 ) {
|
|
RtlCopyMemory(
|
|
s,
|
|
TEXT("\\??\\"),
|
|
sourceDosDevLength
|
|
);
|
|
s += sourceDosDevLength;
|
|
}
|
|
RtlCopyMemory(
|
|
s,
|
|
FullSourceFilename,
|
|
sourceLength
|
|
);
|
|
s += sourceLength;
|
|
|
|
if ( targetDosDevLength != 0 ) {
|
|
RtlCopyMemory(
|
|
s,
|
|
TEXT("\\??\\"),
|
|
targetDosDevLength
|
|
);
|
|
s += targetDosDevLength;
|
|
}
|
|
RtlCopyMemory(
|
|
s,
|
|
FullTargetFilename,
|
|
targetLength
|
|
);
|
|
|
|
//
|
|
// Invoke the SIS CopyFile FsCtrl.
|
|
//
|
|
|
|
ok = DeviceIoControl(
|
|
Queue->SisSourceHandle,
|
|
FSCTL_SIS_COPYFILE,
|
|
copyFile, // Input buffer
|
|
copyFileSize, // Input buffer length
|
|
NULL, // Output buffer
|
|
0, // Output buffer length
|
|
&bytesReturned,
|
|
NULL
|
|
);
|
|
error = GetLastError( );
|
|
|
|
MyFree( copyFile );
|
|
|
|
if ( ok ) {
|
|
|
|
//DbgPrint( "\n\nCreateTargetAsLinkToMaster: SIS copy %ws->%ws succeeded\n\n\n", FullSourceFilename, FullTargetFilename );
|
|
|
|
//
|
|
// Open the target file so that CSC knows about it and pins it,
|
|
// if necessary.
|
|
//
|
|
|
|
targetHandle = CreateFile(
|
|
FullTargetFilename,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL
|
|
);
|
|
if ( targetHandle == INVALID_HANDLE_VALUE ) {
|
|
error = GetLastError();
|
|
DbgPrint( "\n\nCreateTargetAsLinkToMaster: SIS copy %ws->%ws succeeded, but open failed: %d\n\n\n", FullSourceFilename, FullTargetFilename, error );
|
|
} else {
|
|
CloseHandle( targetHandle );
|
|
}
|
|
|
|
error = NO_ERROR;
|
|
|
|
} else {
|
|
|
|
//DbgPrint( "\n\nCreateTargetAsLinkToMaster: SIS copy %ws->%ws failed: %d\n\n\n", FullSourceFilename, FullTargetFilename, error );
|
|
|
|
//
|
|
// If it looks like SIS isn't active on the remote file system, close
|
|
// the SIS root handle so that we can avoid repeatedly getting this
|
|
// error.
|
|
//
|
|
// Note: NTFS returns STATUS_INVALID_PARAMETER (ERROR_INVALID_PARAMETER).
|
|
// FAT returns STATUS_INVALID_DEVICE_REQUEST (ERROR_INVALID_FUNCTION).
|
|
//
|
|
|
|
if ( (error == ERROR_INVALID_PARAMETER) ||
|
|
(error == ERROR_INVALID_FUNCTION) ) {
|
|
CloseHandle( Queue->SisSourceHandle );
|
|
Queue->SisSourceHandle = INVALID_HANDLE_VALUE;
|
|
if ( Queue->SisSourceDirectory != NULL ) {
|
|
MyFree( Queue->SisSourceDirectory );
|
|
Queue->SisSourceDirectory = NULL;
|
|
}
|
|
Queue->Flags &= ~FQF_TRY_SIS_COPY;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
|
|
#endif
|
|
}
|
|
|
|
BOOL
|
|
pCompareFilesExact(
|
|
IN PCTSTR File1,
|
|
IN PCTSTR File2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if File1 and File2 are byte-for-byte exactly the same. If they are, we don't need to do anything.
|
|
|
|
Arguments:
|
|
|
|
File1 - the two files to compare. The order does not matter
|
|
File2
|
|
|
|
Return Value:
|
|
|
|
TRUE if the files are exactly the same
|
|
Note that we have to allow for potentially huge files.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hFile1,hFile2;
|
|
HANDLE hMap1,hMap2;
|
|
LPVOID View1,View2;
|
|
ULARGE_INTEGER Size1,Size2,Offset;
|
|
SIZE_T BlockSize;
|
|
SIZE_T ChunkSize;
|
|
BOOL match;
|
|
|
|
match = FALSE;
|
|
hFile1=hFile2=INVALID_HANDLE_VALUE;
|
|
hMap1=hMap2=NULL;
|
|
View1=View2=NULL;
|
|
|
|
try {
|
|
hFile1 = CreateFile(File1,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL);
|
|
if (hFile1 == INVALID_HANDLE_VALUE) {
|
|
leave;
|
|
}
|
|
hFile2 = CreateFile(File2,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL);
|
|
if (hFile2 == INVALID_HANDLE_VALUE) {
|
|
leave;
|
|
}
|
|
|
|
Size1.LowPart = GetFileSize(hFile1,&Size1.HighPart);
|
|
if(Size1.LowPart == (DWORD)(-1) && GetLastError()) {
|
|
//
|
|
// get file size failed
|
|
//
|
|
leave;
|
|
}
|
|
Size2.LowPart = GetFileSize(hFile2,&Size2.HighPart);
|
|
if(Size2.LowPart == (DWORD)(-1) && GetLastError()) {
|
|
//
|
|
// get file size failed
|
|
//
|
|
leave;
|
|
}
|
|
if (Size1.QuadPart != Size2.QuadPart) {
|
|
leave;
|
|
}
|
|
|
|
if (Size1.QuadPart == 0) {
|
|
//
|
|
// both files are zero length, nothing to do
|
|
//
|
|
match = TRUE;
|
|
leave;
|
|
}
|
|
|
|
//
|
|
// basic checks done, we'll mark both files as mappable to do the byte-checks
|
|
//
|
|
hMap1 = CreateFileMapping(hFile1,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
Size1.HighPart,
|
|
Size1.LowPart,
|
|
NULL);
|
|
if (hMap1 == NULL) {
|
|
//
|
|
// mapping failed
|
|
//
|
|
leave;
|
|
}
|
|
|
|
hMap2 = CreateFileMapping(hFile2,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
Size1.HighPart,
|
|
Size1.LowPart,
|
|
NULL);
|
|
if (hMap2 == NULL) {
|
|
//
|
|
// mapping failed
|
|
//
|
|
leave;
|
|
}
|
|
|
|
if (Size1.QuadPart > FILE_COMPARE_BLOCK_SIZE) {
|
|
BlockSize = FILE_COMPARE_BLOCK_SIZE;
|
|
} else {
|
|
BlockSize = (SIZE_T)Size1.QuadPart;
|
|
MYASSERT(BlockSize);
|
|
}
|
|
|
|
//
|
|
// now proceed in BlockSize chunks comparing the two files
|
|
//
|
|
Offset.QuadPart = 0;
|
|
|
|
do {
|
|
if ((Size1.QuadPart - Offset.QuadPart) < BlockSize) {
|
|
ChunkSize = (SIZE_T)(Size1.QuadPart - Offset.QuadPart);
|
|
MYASSERT(ChunkSize);
|
|
} else {
|
|
ChunkSize = BlockSize;
|
|
}
|
|
|
|
//
|
|
// map and compare the two views
|
|
// for most files, we will only need to do this once
|
|
// for big files, we'll do this approx (Size1+BlockSize-1)/BlockSize times
|
|
//
|
|
View1 = MapViewOfFile(hMap1,
|
|
FILE_MAP_READ,
|
|
Offset.HighPart,
|
|
Offset.LowPart,
|
|
ChunkSize);
|
|
if (View1 == NULL) {
|
|
//
|
|
// get view failed
|
|
//
|
|
leave;
|
|
}
|
|
View2 = MapViewOfFile(hMap2,
|
|
FILE_MAP_READ,
|
|
Offset.HighPart,
|
|
Offset.LowPart,
|
|
ChunkSize);
|
|
if (View2 == NULL) {
|
|
//
|
|
// get view failed
|
|
//
|
|
leave;
|
|
}
|
|
if(memcmp(View1,View2,ChunkSize) != 0) {
|
|
//
|
|
// file chunks mismatch
|
|
//
|
|
leave;
|
|
}
|
|
UnmapViewOfFile(View1);
|
|
UnmapViewOfFile(View2);
|
|
View1 = NULL;
|
|
View2 = NULL;
|
|
|
|
Offset.QuadPart += ChunkSize;
|
|
|
|
} while (Offset.QuadPart<Size1.QuadPart);
|
|
|
|
//
|
|
// if we get here, we have a 100% match
|
|
//
|
|
match = TRUE;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
}
|
|
|
|
if (View1 != NULL) {
|
|
UnmapViewOfFile(View1);
|
|
}
|
|
if (View2 != NULL) {
|
|
UnmapViewOfFile(View2);
|
|
}
|
|
if (hMap1 != NULL) {
|
|
CloseHandle(hMap1);
|
|
}
|
|
if (hMap2 != NULL) {
|
|
CloseHandle(hMap2);
|
|
}
|
|
if (hFile1 != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFile1);
|
|
}
|
|
if (hFile2 != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFile2);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
BOOL
|
|
_SetupInstallFileEx(
|
|
IN PSP_FILE_QUEUE Queue, OPTIONAL
|
|
IN PSP_FILE_QUEUE_NODE QueueNode, OPTIONAL
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCTSTR SourceFile, OPTIONAL
|
|
IN PCTSTR SourcePathRoot, OPTIONAL
|
|
IN PCTSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PVOID CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context, OPTIONAL
|
|
OUT PBOOL FileWasInUse,
|
|
IN BOOL IsMsgHandlerNativeCharWidth,
|
|
OUT PBOOL SignatureVerifyFailed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Actual implementation of SetupInstallFileEx. Handles either ANSI or
|
|
Unicode callback routine.
|
|
|
|
Arguments:
|
|
|
|
Same as SetupInstallFileEx().
|
|
|
|
QueueNode - must be specified if Queue is supplied. This parameter gives
|
|
us the queue node for this operation so we can get at the pertinent
|
|
catalog info for driver signing.
|
|
|
|
IsMsgHandlerNativeCharWidth - supplies a flag indicating whether
|
|
CopyMsgHandler expects native char widths args (or ansi ones, in the
|
|
unicode build of the dll).
|
|
|
|
SignatureVerifyFailed - supplies the address of a boolean variable that is
|
|
set to indicate whether or not digital signature verification failed for
|
|
the source file. This will be set to FALSE if some other failure caused
|
|
us to abort prior to attempting the signature verification. This is
|
|
used by the queue commit routines to determine whether or not the queue
|
|
callback routine should be given a chance to handle a copy failure
|
|
(skip, retry, etc.). Digital signature verification failures are
|
|
handled within this routine (including user prompting, if policy
|
|
requires it), and queue callback routines _are not_ allowed to override
|
|
the behavior.
|
|
|
|
Return Value:
|
|
|
|
Same as SetupInstallFileEx().
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
BOOL Ok;
|
|
DWORD rc = NO_ERROR;
|
|
DWORD SigVerifRc;
|
|
UINT SourceId;
|
|
TCHAR Buffer1[MAX_PATH];
|
|
TCHAR Buffer2[MAX_PATH];
|
|
PCTSTR FullSourceFilename;
|
|
PCTSTR FullTargetFilename;
|
|
PCTSTR SourceFilenamePart;
|
|
PTSTR ActualSourceFilename;
|
|
PTSTR TemporaryTargetFile;
|
|
UINT CompressionType;
|
|
DWORD SourceFileSize;
|
|
DWORD TargetFileSize;
|
|
PTSTR p;
|
|
DWORDLONG SourceVersion, TargetVersion;
|
|
TCHAR SourceVersionText[50], TargetVersionText[50];
|
|
LANGID SourceLanguage;
|
|
LANGID TargetLanguage;
|
|
WIN32_FIND_DATA SourceFindData;
|
|
UINT NotifyFlags;
|
|
PSECURITY_DESCRIPTOR SecurityInfo;
|
|
FILEPATHS FilePaths;
|
|
UINT param;
|
|
FILETIME sFileTime,tFileTime;
|
|
WORD sDosTime,sDosDate,tDosTime,tDosDate;
|
|
BOOL Moved;
|
|
SetupapiVerifyProblem Problem;
|
|
BOOL ExistingTargetFileWasSigned;
|
|
PSETUP_LOG_CONTEXT lc = NULL;
|
|
DWORD slot_fileop = 0;
|
|
SP_TARGET_ENT TargetInfo;
|
|
PCTSTR ExistingFile = NULL;
|
|
PCTSTR CompareFile = NULL;
|
|
PCTSTR BackupFileName = NULL;
|
|
BOOL CompareSameFilename = FALSE;
|
|
BOOL FileUnchanged = FALSE;
|
|
PLOADED_INF LoadedInf = NULL;
|
|
DWORD ExemptCopyFlags = 0;
|
|
BOOL DoingDeviceInstall;
|
|
DWORD DriverSigningPolicy;
|
|
PSP_ALTPLATFORM_INFO_V2 ValidationPlatform = NULL;
|
|
PTSTR DeviceDesc = NULL;
|
|
|
|
if (Queue) {
|
|
lc = Queue->LogContext;
|
|
} else if (InfHandle && InfHandle != INVALID_HANDLE_VALUE) {
|
|
//
|
|
// Lock INF for the duration of this routine.
|
|
//
|
|
try {
|
|
if(!LockInf((PLOADED_INF)InfHandle)) {
|
|
rc = ERROR_INVALID_HANDLE;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
//
|
|
// Assume InfHandle was bad pointer
|
|
//
|
|
rc = ERROR_INVALID_HANDLE;
|
|
}
|
|
if(rc != NO_ERROR) {
|
|
SetLastError(rc);
|
|
return FALSE;
|
|
}
|
|
|
|
LoadedInf = (PLOADED_INF)InfHandle;
|
|
|
|
lc = LoadedInf->LogContext;
|
|
}
|
|
|
|
//
|
|
// If Queue is specified, then so must QueueNode (and vice versa).
|
|
//
|
|
MYASSERT((Queue && QueueNode) || !(Queue || QueueNode));
|
|
|
|
*SignatureVerifyFailed = FALSE;
|
|
SigVerifRc = NO_ERROR;
|
|
|
|
//
|
|
// Assume failure.
|
|
//
|
|
Ok = FALSE;
|
|
SecurityInfo = NULL;
|
|
Moved = FALSE;
|
|
try {
|
|
*FileWasInUse = FALSE;
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
rc = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if((rc == NO_ERROR) && InfContext) {
|
|
if(!InfHandle || (InfHandle == INVALID_HANDLE_VALUE)) {
|
|
rc = ERROR_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if(rc != NO_ERROR) {
|
|
goto clean0;
|
|
}
|
|
|
|
//
|
|
// Determine the full source path and filename of the file.
|
|
//
|
|
if(CopyStyle & SP_COPY_SOURCE_ABSOLUTE) {
|
|
if (!SourceFile) {
|
|
rc = ERROR_INVALID_PARAMETER;
|
|
goto clean0;
|
|
}
|
|
FullSourceFilename = DuplicateString(SourceFile);
|
|
} else {
|
|
|
|
//
|
|
// Get the relative path for this file if necessary.
|
|
//
|
|
if(CopyStyle & SP_COPY_SOURCEPATH_ABSOLUTE) {
|
|
Buffer2[0] = TEXT('\0');
|
|
b = TRUE;
|
|
} else {
|
|
b = _SetupGetSourceFileLocation(
|
|
InfHandle,
|
|
InfContext,
|
|
SourceFile,
|
|
(Queue && (Queue->Flags & FQF_USE_ALT_PLATFORM))
|
|
? &(Queue->AltPlatformInfo)
|
|
: NULL,
|
|
&SourceId,
|
|
Buffer2,
|
|
MAX_PATH,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
//
|
|
// Concatenate the relative path and the filename to the source root.
|
|
//
|
|
if(!b) {
|
|
rc = (GetLastError() == ERROR_INSUFFICIENT_BUFFER
|
|
? ERROR_FILENAME_EXCED_RANGE : GetLastError());
|
|
goto clean0;
|
|
}
|
|
|
|
if (SourcePathRoot) {
|
|
lstrcpyn(Buffer1,SourcePathRoot,MAX_PATH);
|
|
} else {
|
|
Buffer1[0] = TEXT('\0');
|
|
}
|
|
|
|
if(!pSetupConcatenatePaths(Buffer1,Buffer2,MAX_PATH,NULL)
|
|
|| !pSetupConcatenatePaths(Buffer1,SourceFile,MAX_PATH,NULL)) {
|
|
rc = ERROR_FILENAME_EXCED_RANGE;
|
|
goto clean0;
|
|
}
|
|
|
|
FullSourceFilename = DuplicateString(Buffer1);
|
|
}
|
|
|
|
if(!FullSourceFilename) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean0;
|
|
}
|
|
|
|
SourceFilenamePart = pSetupGetFileTitle(FullSourceFilename);
|
|
|
|
//
|
|
// Determine the full target path and filename of the file.
|
|
// For now ignore the issues regarding compressed vs. uncompressed names.
|
|
//
|
|
if(InfContext) {
|
|
//
|
|
// DestinationName is the filename only (no path) of the target.
|
|
// We'll need to fetch the target path information for the section
|
|
// that InfContext references.
|
|
//
|
|
b = SetupGetTargetPath(
|
|
InfHandle,
|
|
InfContext,
|
|
NULL,
|
|
Buffer1,
|
|
MAX_PATH,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
rc = (GetLastError() == ERROR_INSUFFICIENT_BUFFER
|
|
? ERROR_FILENAME_EXCED_RANGE : GetLastError());
|
|
goto clean1;
|
|
}
|
|
|
|
lstrcpyn(Buffer2,Buffer1,MAX_PATH);
|
|
|
|
b = pSetupConcatenatePaths(
|
|
Buffer2,
|
|
DestinationName ? DestinationName : SourceFilenamePart,
|
|
MAX_PATH,
|
|
NULL
|
|
);
|
|
|
|
if(!b) {
|
|
rc = ERROR_FILENAME_EXCED_RANGE;
|
|
goto clean1;
|
|
}
|
|
|
|
FullTargetFilename = DuplicateString(Buffer2);
|
|
} else {
|
|
//
|
|
// DestinationName is the full path and filename of the target file.
|
|
//
|
|
FullTargetFilename = DuplicateString(DestinationName);
|
|
}
|
|
|
|
if(!FullTargetFilename) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean1;
|
|
}
|
|
|
|
//
|
|
// Log the file copy - only if we log something else
|
|
// note that once we've determined temporary name, we'll change this message
|
|
//
|
|
slot_fileop = AllocLogInfoSlot(lc,FALSE); // for conditional display of extra logging info
|
|
WriteLogEntry(
|
|
lc,
|
|
slot_fileop,
|
|
MSG_LOG_COPYING_FILE,
|
|
NULL,
|
|
FullSourceFilename,
|
|
FullTargetFilename);
|
|
|
|
//
|
|
// Make sure the target path exists.
|
|
//
|
|
rc = pSetupMakeSurePathExists(FullTargetFilename);
|
|
if(rc != NO_ERROR) {
|
|
rc = ERROR_INVALID_TARGET;
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// Determine if the source file is compressed and get compression type
|
|
// if so.
|
|
//
|
|
rc = SetupInternalGetFileCompressionInfo(
|
|
FullSourceFilename,
|
|
&ActualSourceFilename,
|
|
&SourceFindData,
|
|
&TargetFileSize,
|
|
&CompressionType
|
|
);
|
|
|
|
//
|
|
// If the source doesn't exist but the target does, and we don't want to
|
|
// overwrite it, then there is no error and we're finished.
|
|
//
|
|
// When doing a driver uninstall (i.e., re-installing the
|
|
// previous driver from the backup directory), it's possible that not all
|
|
// source files will be present in that directory (i.e., only those files
|
|
// that were modified got backed up). In that case, we want to consider a
|
|
// source file-not-found error here to be OK, even if the force-nooverwrite
|
|
// flag isn't set.
|
|
//
|
|
// Note that driver signing isn't relevant here, because if an INF was signed
|
|
// with the force-nooverwrite flag, then the signer (i.e., WHQL) must've been
|
|
// satisfied that the file in question was not crucial to the package's
|
|
// integrity/operation (a default INI file would be an example of this).
|
|
//
|
|
if(rc == ERROR_FILE_NOT_FOUND &&
|
|
CopyStyle & SP_COPY_FORCE_NOOVERWRITE &&
|
|
FileExists(FullTargetFilename,NULL)
|
|
) {
|
|
|
|
rc = NO_ERROR;
|
|
goto clean2;
|
|
|
|
} else if(rc != NO_ERROR) {
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// Got the actual source file name now.
|
|
//
|
|
MyFree(FullSourceFilename);
|
|
FullSourceFilename = ActualSourceFilename;
|
|
SourceFilenamePart = pSetupGetFileTitle(FullSourceFilename);
|
|
|
|
//
|
|
// If the file to be copied is a .CAB and the source and destination
|
|
// filenames are the same, then we don't want to attempt to decompress it
|
|
// (because if we do, we'd just be pulling the first file out of the cab
|
|
// and renaming it to the destination filename, which is never the desired
|
|
// behavior.
|
|
//
|
|
if(!lstrcmpi(SourceFilenamePart, pSetupGetFileTitle(FullTargetFilename))) {
|
|
p = _tcsrchr(SourceFilenamePart, TEXT('.'));
|
|
if(p && !lstrcmpi(p, TEXT(".CAB"))) {
|
|
CopyStyle |= SP_COPY_NODECOMP;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If SP_COPY_NODECOMP flag is set, adjust the target filename so that
|
|
// the filename part is the same as the actual name of the source.
|
|
// We do this regardless of whether the source file is compressed.
|
|
//
|
|
// Note: For driver signing, the fact that this file is installed in its
|
|
// compressed form means we must have an entry for the compressed file in
|
|
// the catalog. However, if at some point in the future this file is going
|
|
// to be expanded (as is typically the case), then we need to have the
|
|
// expanded file's signature in the catalog as well, so that sigverif
|
|
// doesn't consider this expanded file to be from a non-certified package.
|
|
//
|
|
if(CopyStyle & SP_COPY_ALREADYDECOMP) {
|
|
//
|
|
// this flag indicates we've decompressed it as far as we want
|
|
// (used when restoring backup)
|
|
//
|
|
CompressionType = FILE_COMPRESSION_NONE;
|
|
|
|
} else if(CopyStyle & SP_COPY_NODECOMP) {
|
|
//
|
|
// Strip out version-related bits and ensure that we treat the file
|
|
// as uncompressed.
|
|
//
|
|
CopyStyle &= ~SP_COPY_MASK_NEEDVERINFO;
|
|
CompressionType = FILE_COMPRESSION_NONE;
|
|
|
|
//
|
|
// Isolate the path part of the target filename.
|
|
//
|
|
lstrcpyn(Buffer1, FullTargetFilename, MAX_PATH);
|
|
*((PTSTR)pSetupGetFileTitle(Buffer1)) = TEXT('\0');
|
|
|
|
//
|
|
// Concatenate the source filename onto the target pathname.
|
|
//
|
|
if(!pSetupConcatenatePaths(Buffer1,SourceFilenamePart,MAX_PATH,NULL)) {
|
|
rc = ERROR_FILENAME_EXCED_RANGE;
|
|
goto clean2;
|
|
}
|
|
|
|
p = DuplicateString(Buffer1);
|
|
if(!p) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean2;
|
|
}
|
|
|
|
MyFree(FullTargetFilename);
|
|
FullTargetFilename = p;
|
|
}
|
|
|
|
//
|
|
// See if the target file exists, either as a renamed file (i.e., because
|
|
// we're replacing a boot file), or as a file presently existing at the
|
|
// target location.
|
|
//
|
|
if(Queue && (CopyStyle & SP_COPY_REPLACE_BOOT_FILE)) {
|
|
//
|
|
// First, we need to find the corresponding target info node so
|
|
// we can find out what temporary name our file was renamed to.
|
|
//
|
|
rc = pSetupBackupGetTargetByPath((HSPFILEQ)Queue,
|
|
NULL, // use Queue's string table
|
|
FullTargetFilename,
|
|
QueueNode->TargetDirectory,
|
|
-1,
|
|
QueueNode->TargetFilename,
|
|
NULL,
|
|
&TargetInfo
|
|
);
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// Has the file previously been renamed (and not yet restored)?
|
|
//
|
|
if((TargetInfo.InternalFlags & (SP_TEFLG_MOVED | SP_TEFLG_RESTORED)) == SP_TEFLG_MOVED) {
|
|
|
|
CompareFile = ExistingFile =
|
|
pSetupStringTableStringFromId(
|
|
Queue->StringTable,
|
|
TargetInfo.NewTargetFilename
|
|
);
|
|
MYASSERT(ExistingFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!ExistingFile && FileExists(FullTargetFilename, NULL)) {
|
|
CompareFile = ExistingFile = FullTargetFilename;
|
|
CompareSameFilename = TRUE; // allows optimization later
|
|
}
|
|
|
|
if(ExistingFile) {
|
|
|
|
if(CopyStyle & SP_COPY_FORCE_NOOVERWRITE) {
|
|
//
|
|
// No overwrite and no callback notification either
|
|
//
|
|
// Note that driver signing isn't relevant here, because if an INF
|
|
// was signed with the force-nooverwrite flag, then the signer
|
|
// (i.e., WHQL) must've been satisfied that the file in question was
|
|
// not crucial to the package's integrity/operation (a default INI
|
|
// file would be an example of this).
|
|
//
|
|
rc = NO_ERROR;
|
|
goto clean2;
|
|
}
|
|
|
|
if(CopyStyle & SP_COPY_MASK_NEEDVERINFO) {
|
|
if(!GetVersionInfoFromImage(ExistingFile, &TargetVersion, &TargetLanguage)) {
|
|
TargetVersion = 0;
|
|
TargetLanguage = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the target file exists we'll want to preserve security info on it.
|
|
//
|
|
if(RetreiveFileSecurity(ExistingFile, &SecurityInfo) != NO_ERROR) {
|
|
SecurityInfo = NULL;
|
|
}
|
|
|
|
} else {
|
|
|
|
if(CopyStyle & SP_COPY_REPLACEONLY) {
|
|
//
|
|
// Target file doesn't exist, so there's nothing to do.
|
|
//
|
|
rc = NO_ERROR;
|
|
goto clean2;
|
|
}
|
|
if(Queue && ((Queue->Flags & FQF_FILES_MODIFIED)==0)) {
|
|
//
|
|
// maybe the file was renamed/deleted first
|
|
// so we might still want to compare against backup
|
|
// to determine if it was "modified"
|
|
//
|
|
rc = pSetupBackupGetTargetByPath((HSPFILEQ)Queue,
|
|
NULL, // use Queue's string table
|
|
FullTargetFilename,
|
|
QueueNode->TargetDirectory,
|
|
-1,
|
|
QueueNode->TargetFilename,
|
|
NULL,
|
|
&TargetInfo
|
|
);
|
|
if((rc == NO_ERROR) &&
|
|
((TargetInfo.InternalFlags & (SP_TEFLG_MOVED | SP_TEFLG_SAVED)) != 0)) {
|
|
//
|
|
// get filename of copy of original file, if there is one
|
|
//
|
|
CompareFile = BackupFileName =
|
|
pSetupFormFullPath(Queue->StringTable,
|
|
TargetInfo.TargetRoot,
|
|
TargetInfo.TargetSubDir,
|
|
TargetInfo.TargetFilename
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the source is not compressed (LZ or cabinet), and SIS is (or might be)
|
|
// present, create the target as an SIS link to the master instead of copying it.
|
|
//
|
|
// If the target exists, and NOOVERWRITE was specified, don't try to create
|
|
// an SIS link. Instead, fall through to the normal copy code. The overwrite
|
|
// semantics are wrong if the file already exists.
|
|
//
|
|
if((CompressionType == FILE_COMPRESSION_NONE) &&
|
|
(!ExistingFile || ((CopyStyle & SP_COPY_NOOVERWRITE) == 0)) &&
|
|
(Queue != NULL) &&
|
|
((Queue->Flags & FQF_TRY_SIS_COPY) != 0)) {
|
|
|
|
//
|
|
// First, verify that the sourcefile is signed. If it is not, but the
|
|
// user elects to proceed with the copy (or if the policy is 'ignore')
|
|
// then we'll go ahead and attempt to setup the SIS link.
|
|
//
|
|
ValidationPlatform = (Queue->Flags & FQF_USE_ALT_PLATFORM)
|
|
? &(Queue->AltPlatformInfo)
|
|
: Queue->ValidationPlatform;
|
|
|
|
rc = VerifySourceFile(lc,
|
|
Queue,
|
|
QueueNode,
|
|
pSetupGetFileTitle(FullTargetFilename),
|
|
FullSourceFilename,
|
|
NULL,
|
|
ValidationPlatform,
|
|
VERIFY_FILE_USE_OEM_CATALOGS | VERIFY_FILE_FAIL_COPIED_INFS,
|
|
&Problem,
|
|
Buffer1,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
|
|
*SignatureVerifyFailed = TRUE;
|
|
SigVerifRc = rc;
|
|
|
|
if(Queue->Flags & FQF_QUEUE_FORCE_BLOCK_POLICY) {
|
|
goto clean2;
|
|
}
|
|
|
|
if (Problem != SetupapiVerifyDriverBlocked) {
|
|
//
|
|
// If this is a device installation and the policy is "Ignore",
|
|
// then crank it up to "Warn" if the file is under SFP's
|
|
// protection. This will allow the user to update a driver that
|
|
// ships in our box for which no WHQL certification program
|
|
// exists.
|
|
//
|
|
if((Queue->Flags & FQF_DEVICE_INSTALL) &&
|
|
(Queue->DriverSigningPolicy == DRIVERSIGN_NONE) &&
|
|
IsFileProtected(FullTargetFilename, lc, NULL)) {
|
|
|
|
Queue->DriverSigningPolicy = DRIVERSIGN_WARNING;
|
|
|
|
//
|
|
// Log the fact that policy was elevated.
|
|
//
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR,
|
|
MSG_LOG_POLICY_ELEVATED_FOR_SFC,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
|
|
if(!pSetupHandleFailedVerification(
|
|
Queue->hWndDriverSigningUi,
|
|
Problem,
|
|
(Problem == SetupapiVerifyDriverBlocked)
|
|
? FullSourceFilename : Buffer1,
|
|
((Queue->DeviceDescStringId == -1)
|
|
? NULL
|
|
: pStringTableStringFromId(Queue->StringTable, Queue->DeviceDescStringId)),
|
|
Queue->DriverSigningPolicy,
|
|
(Queue->Flags & FQF_DIGSIG_ERRORS_NOUI),
|
|
rc,
|
|
lc,
|
|
(((Queue->Flags & FQF_ABORT_IF_UNSIGNED) &&
|
|
(Problem != SetupapiVerifyDriverBlocked))
|
|
? NULL : &ExemptCopyFlags),
|
|
(((Queue->Flags & FQF_ABORT_IF_UNSIGNED) &&
|
|
(Problem != SetupapiVerifyDriverBlocked))
|
|
? NULL : FullTargetFilename)))
|
|
{
|
|
//
|
|
// User elected not to install the unsigned file (or was blocked
|
|
// by policy from doing so).
|
|
//
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// The user wants to proceed with the unsigned installation (or
|
|
// policy is ignore, so they weren't even informed). If the
|
|
// caller wants a chance to set a system restore point prior to
|
|
// doing any unsigned installations, then we abort now with a
|
|
// "special" error code that tells them what to do...
|
|
//
|
|
if(Queue->Flags & FQF_ABORT_IF_UNSIGNED) {
|
|
//
|
|
// We don't want the user to see the driver signing UI again
|
|
// when the queue is re-committed...
|
|
//
|
|
if(Queue->DriverSigningPolicy != DRIVERSIGN_NONE) {
|
|
Queue->Flags |= FQF_DIGSIG_ERRORS_NOUI;
|
|
}
|
|
|
|
rc = ERROR_SET_SYSTEM_RESTORE_POINT;
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// Set a flag in the queue that indicates the user has been informed
|
|
// of a signature problem with this queue, and has elected to go
|
|
// ahead and install anyway. Don't set this flag if the queue's
|
|
// policy is "Ignore", on the chance that the policy might be
|
|
// altered later, and we'd want the user to get informed on any
|
|
// subsequent errors.
|
|
//
|
|
if(Queue->DriverSigningPolicy != DRIVERSIGN_NONE) {
|
|
Queue->Flags |= FQF_DIGSIG_ERRORS_NOUI;
|
|
}
|
|
|
|
if (QueueNode) {
|
|
QueueNode->InternalFlags |= ExemptCopyFlags;
|
|
}
|
|
|
|
//
|
|
// Reset rc to NO_ERROR and carry on.
|
|
//
|
|
rc = NO_ERROR;
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
|
|
rc = CreateTargetAsLinkToMaster(
|
|
Queue,
|
|
FullSourceFilename,
|
|
FullTargetFilename,
|
|
CopyMsgHandler,
|
|
Context,
|
|
IsMsgHandlerNativeCharWidth
|
|
);
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
//
|
|
// ISSUE JamieHun-2001/03/20 Is this best thing to do for SIS link?
|
|
//
|
|
Queue->Flags |= FQF_FILES_MODIFIED;
|
|
|
|
//
|
|
// We're done!
|
|
//
|
|
Ok = TRUE;
|
|
goto clean2;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We will copy the file to a temporary location. This makes version checks
|
|
// possible in all cases (even when the source is compressed) and simplifies
|
|
// the logic below. Start by forming the name of the temporary file.
|
|
//
|
|
lstrcpyn(Buffer1, FullTargetFilename, MAX_PATH);
|
|
*((PTSTR)pSetupGetFileTitle(Buffer1)) = TEXT('\0');
|
|
|
|
if(!GetTempFileName(Buffer1, TEXT("SETP"), 0, Buffer2)) {
|
|
rc = ERROR_INVALID_TARGET;
|
|
goto clean2;
|
|
}
|
|
|
|
TemporaryTargetFile = DuplicateString(Buffer2);
|
|
if(!TemporaryTargetFile) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean2;
|
|
}
|
|
//
|
|
// Log the file copy - only if we log something else unless at Info level
|
|
//
|
|
ReleaseLogInfoSlot(lc,slot_fileop);
|
|
slot_fileop = AllocLogInfoSlotOrLevel(lc,SETUP_LOG_INFO,FALSE); // for conditional display of extra logging info
|
|
WriteLogEntry(
|
|
lc,
|
|
slot_fileop,
|
|
MSG_LOG_COPYING_FILE_VIA,
|
|
NULL,
|
|
FullSourceFilename,
|
|
FullTargetFilename,
|
|
TemporaryTargetFile);
|
|
|
|
//
|
|
// Perform the actual file copy. This creates the temporary target file.
|
|
// Move is allowed as an optimization if we're deleting the source file.
|
|
// The call we make below will not use move if the file is compressed
|
|
// and we are supposed to decompress it, so the right thing will happen
|
|
// in all cases.
|
|
//
|
|
// There are 2 potential issues:
|
|
//
|
|
// 1) When we call the callback function below for a version check,
|
|
// the source file won't exist any more if the file was moved. Oh well.
|
|
//
|
|
// 2) If the MoveFileEx below fails, the source will have still been 'deleted'.
|
|
// This is different from the non-move case, where the source remains
|
|
// intact unless this function is successful.
|
|
//
|
|
// Otherwise this is a non-issue since any compressed file will be decompressed
|
|
// by this call, so version gathering, etc, will all work properly.
|
|
//
|
|
rc = pSetupDecompressOrCopyFile(
|
|
FullSourceFilename,
|
|
TemporaryTargetFile,
|
|
&CompressionType,
|
|
((CopyStyle & SP_COPY_DELETESOURCE) != 0),
|
|
&Moved
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
goto clean3;
|
|
}
|
|
|
|
//
|
|
// Do digital signature check on source file, now that it exists in its
|
|
// final form under a temp name. Note that for signed files, we ignore
|
|
// version checking since they're an inherently unreliable mechanism for
|
|
// comparing files provided from two different vendors (who use different
|
|
// versioning schemes, etc.)
|
|
//
|
|
// To see why we ignore version numbers, consider the decision tree we'd
|
|
// use if we were paying attention to version numbers as well as digital
|
|
// signatures. In the discussion that follows, NewFile is the (signed) file
|
|
// we're going to copy, and OldFile is the file that will be overwritten if
|
|
// the copy goes through...
|
|
//
|
|
// if NewFile is versioned {
|
|
// if OldFile is signed {
|
|
// if OldFile is versioned {
|
|
// //
|
|
// // Both NewFile and OldFile are signed and versioned.
|
|
// //
|
|
// if OldFile is a newer version {
|
|
// //
|
|
// // This is the controversial case. Since these two incarnations could've come from different vendors
|
|
// // with different versioning schemes, we really can't use versioning as a very accurate method of determining
|
|
// // which file is 'better'. So there are two options:
|
|
// // 1. Leave OldFile alone, and if the package being installed can't work with OldFile, then the user must 'undo'
|
|
// // the installation, and then call their vendor to gripe--there'll be no way for them to get the new package to
|
|
// // work, even though WHQL certified it.
|
|
// // 2. Overwrite OldFile. Since we're then guaranteeing that every file signed as part of the package will be
|
|
// // present, then we can have a much higher degree of certainty that our WHQL certification will hold true
|
|
// // for every user's machine. If replacing OldFile breaks someone else (e.g., the previously-installed package
|
|
// // that uses it, then the user can 'undo' the installation. This scenario is better because even though the old
|
|
// // and new packages can't be used simultaneously, at least one or the other can be made to work
|
|
// // independently.
|
|
// //
|
|
// overwrite OldFile
|
|
// } else { // NewFile is a newer version
|
|
// overwrite OldFile
|
|
// }
|
|
// } else { // OldFile isn't versioned--NewFile wins
|
|
// overwrite OldFile
|
|
// }
|
|
// } else { // OldFile isn't signed--we don't care what its version is
|
|
// overwrite OldFile
|
|
// }
|
|
// } else { // NewFile isn't versioned
|
|
// if OldFile is versioned {
|
|
// if OldFile is signed {
|
|
// //
|
|
// // (See discussion above where both OldFile and NewFile are signed and versioned, and OldFile is newer. Note
|
|
// // that something funny is going on if we've been asked to replace a versionable file with an unversionable one!)
|
|
// //
|
|
// overwrite OldFile
|
|
// } else { // OldFile isn't signed
|
|
// overwrite OldFile
|
|
// }
|
|
// } else { // OldFile isn't versioned either
|
|
// overwrite OldFile
|
|
// }
|
|
// }
|
|
//
|
|
|
|
//
|
|
// Check to see if the source file is signed. (Note--we may have already
|
|
// checked this previously in determining whether an SIS link could be
|
|
// created. If we failed to verify the file's digital signature then,
|
|
// there's no use in re-verifying here.)
|
|
//
|
|
if(*SignatureVerifyFailed) {
|
|
//
|
|
// We saved the signature verification failure error when we previously
|
|
// attempted to verify this file. Restore that code to rc now, because
|
|
// the code below relies on the value of rc.
|
|
//
|
|
MYASSERT(SigVerifRc != NO_ERROR);
|
|
rc = SigVerifRc;
|
|
|
|
} else {
|
|
|
|
if(Queue) {
|
|
|
|
ValidationPlatform = (Queue->Flags & FQF_USE_ALT_PLATFORM)
|
|
? &(Queue->AltPlatformInfo)
|
|
: Queue->ValidationPlatform;
|
|
} else {
|
|
//
|
|
// Since we aren't dealing with a queue, we need to retrieve the
|
|
// appropriate validation platform information, if any, for our INF.
|
|
//
|
|
DoingDeviceInstall = IsInfForDeviceInstall(lc,
|
|
NULL,
|
|
LoadedInf,
|
|
&DeviceDesc,
|
|
&ValidationPlatform,
|
|
&DriverSigningPolicy,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
rc = VerifySourceFile(lc,
|
|
Queue,
|
|
QueueNode,
|
|
pSetupGetFileTitle(FullTargetFilename),
|
|
TemporaryTargetFile,
|
|
FullSourceFilename,
|
|
ValidationPlatform,
|
|
VERIFY_FILE_USE_OEM_CATALOGS | VERIFY_FILE_FAIL_COPIED_INFS,
|
|
&Problem,
|
|
Buffer1,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
*SignatureVerifyFailed = (rc != NO_ERROR);
|
|
|
|
}
|
|
|
|
//
|
|
// Don't muck with the value of rc below unless you're setting it
|
|
// immediately before jumping to clean4. The return value from
|
|
// VerifySourceFile needs to be preserved until we finish with the
|
|
// pSetupHandleFailedVerification stuff.
|
|
//
|
|
|
|
//
|
|
// If we are going to perform version checks, fetch the version data
|
|
// of the source (which is now the temporary target file).
|
|
//
|
|
NotifyFlags = 0;
|
|
if(ExistingFile) {
|
|
|
|
param = 0;
|
|
|
|
//
|
|
// If we're not supposed to overwrite existing files,
|
|
// then the overwrite check fails.
|
|
//
|
|
if(CopyStyle & SP_COPY_NOOVERWRITE) {
|
|
NotifyFlags |= SPFILENOTIFY_TARGETEXISTS;
|
|
}
|
|
|
|
//
|
|
// Even if the source file has a verified digital signature, we still
|
|
// want to retrieve version information for the source and target. We
|
|
// do this so that we can detect when we've overwritten a newer-
|
|
// versioned file with an older-versioned one. If we discover that
|
|
// this is the case, then we generate an exception log entry that will
|
|
// help PSS troubleshoot any problems that result.
|
|
//
|
|
if(!GetVersionInfoFromImage(TemporaryTargetFile, &SourceVersion, &SourceLanguage)) {
|
|
SourceVersion = 0;
|
|
SourceLanguage = 0;
|
|
}
|
|
|
|
//
|
|
// If we're not supposed to overwrite files in a different language
|
|
// and the languages differ, then the language check fails.
|
|
// If either file doesn't have language data, then don't do a language
|
|
// check.
|
|
//
|
|
//
|
|
// Special case:
|
|
//
|
|
// If
|
|
// a) the source version is greater than the target version
|
|
// b) the source doesn't have a lang id
|
|
// c) the target does have a lang id
|
|
// Then
|
|
// we will do a language check, and we will consider this language check
|
|
// as passed since we are replacing an older language specific file with
|
|
// a language neutral file, which is an OK thing.
|
|
//
|
|
//
|
|
if(CopyStyle & SP_COPY_LANGUAGEAWARE) {
|
|
if ( SourceLanguage
|
|
&& TargetLanguage
|
|
&& (SourceLanguage != TargetLanguage) ) {
|
|
|
|
NotifyFlags |= SPFILENOTIFY_LANGMISMATCH;
|
|
param = (UINT)MAKELONG(SourceLanguage, TargetLanguage);
|
|
|
|
} else if ( !SourceLanguage
|
|
&& TargetLanguage
|
|
&& (TargetVersion >= SourceVersion) ) {
|
|
|
|
NotifyFlags |= SPFILENOTIFY_LANGMISMATCH;
|
|
param = (UINT)MAKELONG(SourceLanguage, TargetLanguage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// If we're not supposed to overwrite newer versions and the target is
|
|
// newer than the source, then the version check fails. If either file
|
|
// doesn't have version info, fall back to timestamp comparison.
|
|
//
|
|
// If the files are the same version/timestamp, assume that either
|
|
// replacing the existing one is a benevolent operation, or that
|
|
// we are upgrading an existing file whose version info is unimportant.
|
|
// In this case we just go ahead and copy the file (unless the
|
|
// SP_COPY_NEWER_ONLY flag is set).
|
|
//
|
|
// Note that the version checks below are made without regard to presence
|
|
// or absence of digital signatures on either the source or target files.
|
|
// That will be handled later. We want to see what would've happened
|
|
// without driver signing, so we can generate a PSS exception log when
|
|
// something weird happens.
|
|
//
|
|
if(SourceVersion || TargetVersion) {
|
|
|
|
b = (CopyStyle & SP_COPY_NEWER_ONLY)
|
|
? (TargetVersion >= SourceVersion)
|
|
: (TargetVersion > SourceVersion);
|
|
|
|
} else {
|
|
#if 0
|
|
//
|
|
// (tedm) removed timestamp-based checking. It's just not a reliable
|
|
// way of doing things.
|
|
//
|
|
|
|
//
|
|
// File time on FAT is only guaranteed accurate to within 2 seconds.
|
|
// Round the filetimes before comparison by converting to DOS time
|
|
// and back. If the conversions fail then just use the original values.
|
|
//
|
|
if(!FileTimeToDosDateTime(&SourceFindData.ftLastWriteTime,&sDosDate,&sDosTime)
|
|
|| !FileTimeToDosDateTime(&TargetFindData.ftLastWriteTime,&tDosDate,&tDosTime)
|
|
|| !DosDateTimeToFileTime(sDosDate,sDosTime,&sFileTime)
|
|
|| !DosDateTimeToFileTime(tDosDate,tDosTime,&tFileTime)) {
|
|
|
|
sFileTime = SourceFindData.ftLastWriteTime;
|
|
tFileTime = TargetFindData.ftLastWriteTime;
|
|
}
|
|
|
|
//
|
|
// If the FORCE_NEWER flag is set then don't use timestamps
|
|
// for versioning. This more closely matches Win95's setupx behavior.
|
|
//
|
|
b = (CopyStyle & SP_COPY_FORCE_NEWER)
|
|
? FALSE
|
|
: (CompareFileTime(&tFileTime,&sFileTime) > 0);
|
|
#else
|
|
b = FALSE;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// At this point, if b is TRUE then the target file has a later (newer)
|
|
// version than the sourcefile. If we've been asked to pay attention to
|
|
// that, then set the NotifyFlags to indicate this problem.
|
|
// Note that this may get reset later if the source file is signed. We
|
|
// still wanted to get this information so we could put it in our PSS
|
|
// logfile.
|
|
//
|
|
if(b &&
|
|
(CopyStyle & (SP_COPY_NEWER_OR_SAME | SP_COPY_NEWER_ONLY | SP_COPY_FORCE_NEWER))) {
|
|
|
|
NotifyFlags |= SPFILENOTIFY_TARGETNEWER;
|
|
}
|
|
}
|
|
|
|
if(NotifyFlags & SPFILENOTIFY_TARGETNEWER) {
|
|
|
|
if(!*SignatureVerifyFailed) {
|
|
//
|
|
// Source file is signed, but the target file is newer. We know
|
|
// that we still want to replace the existing targetfile with the
|
|
// sourcefile, regardless of version numbers. However, we need to
|
|
// make note of that in our PSS logfile.
|
|
//
|
|
NotifyFlags &= ~SPFILENOTIFY_TARGETNEWER;
|
|
|
|
//
|
|
// Check to see if the target file is signed, in order to include
|
|
// this information in our PSS logfile.
|
|
//
|
|
ExistingTargetFileWasSigned = (NO_ERROR == _VerifyFile(
|
|
lc,
|
|
(Queue ? &(Queue->hCatAdmin) : NULL),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
pSetupGetFileTitle(FullTargetFilename),
|
|
ExistingFile,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
ValidationPlatform,
|
|
(VERIFY_FILE_USE_OEM_CATALOGS | VERIFY_FILE_NO_DRIVERBLOCKED_CHECK),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
));
|
|
|
|
//
|
|
// SPLOG -- report that newer target was overwritten by older (signed)
|
|
// source, whether target was signed, both files' versions, etc.
|
|
// Also probably want to throw in the fact that the SP_COPY_FORCE_NEWER
|
|
// flag was ignored, if it's set.
|
|
//
|
|
pGetVersionText(SourceVersionText,SourceVersion);
|
|
pGetVersionText(TargetVersionText,TargetVersion);
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_NEWER_FILE_OVERWRITTEN,
|
|
NULL,
|
|
FullTargetFilename,
|
|
SourceVersionText,
|
|
TargetVersionText);
|
|
|
|
if (CopyStyle & SP_COPY_FORCE_NEWER) {
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_FLAG_FORCE_NEWER_IGNORED,
|
|
NULL);
|
|
}
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
(ExistingTargetFileWasSigned ?
|
|
MSG_LOG_TARGET_WAS_SIGNED :
|
|
MSG_LOG_TARGET_WAS_NOT_SIGNED),
|
|
NULL);
|
|
//
|
|
// flush the log buffer
|
|
//
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING,
|
|
0,
|
|
TEXT("\n"));
|
|
|
|
} else {
|
|
//
|
|
// Source file isn't signed, so we'll let the old behavior apply.
|
|
// If version dialogs are allowed, for example, the user will be
|
|
// prompted that they're attempting to overwrite a newer file with an
|
|
// older one. If they say "go ahead and copy over the older version",
|
|
// _then_ they'll get a prompt about the file not having a valid
|
|
// signature.
|
|
//
|
|
// Check special case where a flag is set indicating that we should
|
|
// just silently not copy the newer file.
|
|
//
|
|
if(CopyStyle & SP_COPY_FORCE_NEWER) {
|
|
//
|
|
// Target is newer; don't copy the file.
|
|
//
|
|
rc = NO_ERROR;
|
|
goto clean4;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have any reason to notify the caller via the callback,
|
|
// do that here. If there is no callback, then don't copy the file,
|
|
// because one of the conditions has not been met.
|
|
//
|
|
if((NotifyFlags & SPFILENOTIFY_LANGMISMATCH) && ! *SignatureVerifyFailed) {
|
|
//
|
|
//
|
|
//
|
|
// if the source was signed, we will ignore a language mismatch
|
|
// as this is a package deal
|
|
// NTRAID9#498046-2001/11/20-JamieHun handle LANGMISMATCH better
|
|
//
|
|
NotifyFlags &=~SPFILENOTIFY_LANGMISMATCH;
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING,
|
|
MSG_LOG_NOTIFY_LANGMISMATCH,
|
|
NULL);
|
|
}
|
|
if(NotifyFlags) {
|
|
|
|
FilePaths.Source = FullSourceFilename;
|
|
FilePaths.Target = FullTargetFilename;
|
|
FilePaths.Win32Error = NO_ERROR;
|
|
|
|
if(!CopyMsgHandler
|
|
|| !pSetupCallMsgHandler(
|
|
lc,
|
|
CopyMsgHandler,
|
|
IsMsgHandlerNativeCharWidth,
|
|
Context,
|
|
NotifyFlags,
|
|
(UINT_PTR)&FilePaths,
|
|
param))
|
|
{
|
|
if(ExistingFile) {
|
|
//
|
|
// Check to see if the target file is signed, in order to include
|
|
// this information in our PSS logfile.
|
|
//
|
|
ExistingTargetFileWasSigned = (NO_ERROR == _VerifyFile(
|
|
lc,
|
|
(Queue ? &(Queue->hCatAdmin) : NULL),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
pSetupGetFileTitle(FullTargetFilename),
|
|
ExistingFile,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
ValidationPlatform,
|
|
(VERIFY_FILE_USE_OEM_CATALOGS | VERIFY_FILE_NO_DRIVERBLOCKED_CHECK),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
));
|
|
}
|
|
|
|
//
|
|
// SPLOG -- error that occurred, info on whether source and target
|
|
// files were signed, what their versions were, etc.
|
|
//
|
|
|
|
pGetVersionText(SourceVersionText,SourceVersion);
|
|
pGetVersionText(TargetVersionText,TargetVersion);
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_FILE_NOT_OVERWRITTEN,
|
|
NULL,
|
|
SourceVersionText,
|
|
TargetVersionText);
|
|
if (NotifyFlags & SPFILENOTIFY_TARGETEXISTS) {
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_NOTIFY_TARGETEXISTS,
|
|
NULL);
|
|
}
|
|
if (NotifyFlags & SPFILENOTIFY_LANGMISMATCH) {
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_NOTIFY_LANGMISMATCH,
|
|
NULL);
|
|
}
|
|
if (NotifyFlags & SPFILENOTIFY_TARGETNEWER) {
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
MSG_LOG_NOTIFY_TARGETNEWER,
|
|
NULL);
|
|
}
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
(ExistingTargetFileWasSigned ?
|
|
MSG_LOG_TARGET_WAS_SIGNED :
|
|
MSG_LOG_TARGET_WAS_NOT_SIGNED),
|
|
NULL);
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_WARNING | SETUP_LOG_BUFFER,
|
|
(*SignatureVerifyFailed ?
|
|
MSG_LOG_SOURCE_WAS_NOT_SIGNED :
|
|
MSG_LOG_SOURCE_WAS_SIGNED),
|
|
NULL);
|
|
WriteLogError(
|
|
lc,
|
|
SETUP_LOG_WARNING,
|
|
rc);
|
|
|
|
rc = NO_ERROR;
|
|
goto clean4;
|
|
}
|
|
}
|
|
|
|
//
|
|
// OK, now that all non-codesigning stuff is done, tell the user about
|
|
// any digital signature problems we found with the source file.
|
|
//
|
|
// NOTE: If SigVerifRc is set to something other than NO_ERROR, then we know
|
|
// that the pSetupHandleFailedVerification routine has already been called, thus
|
|
// we don't want to call it again or otherwise the user may get multiple
|
|
// prompts about a bad signature for the same file.
|
|
//
|
|
if(*SignatureVerifyFailed) {
|
|
|
|
if(SigVerifRc == NO_ERROR) {
|
|
|
|
MYASSERT(ExemptCopyFlags == 0);
|
|
|
|
//
|
|
// Save away the verification error in SigVerifRc, so we can use
|
|
// this later to determine whether we're dealing with an unsigned
|
|
// file.
|
|
//
|
|
MYASSERT(rc != NO_ERROR);
|
|
SigVerifRc = rc;
|
|
|
|
//
|
|
// If we're using a file queue, the retrieve information from the
|
|
// queue regarding policy, whether it's a device install (and if
|
|
// so, what description to use), etc. There's no need to do this
|
|
// in the non-queue case, because we already did this previously.
|
|
//
|
|
if(Queue) {
|
|
//
|
|
// if we're set to block on a per queue basis, then bail out
|
|
// here
|
|
//
|
|
if(Queue->Flags & FQF_QUEUE_FORCE_BLOCK_POLICY) {
|
|
goto clean4;
|
|
}
|
|
if(Queue->DeviceDescStringId != -1) {
|
|
DeviceDesc = pStringTableStringFromId(
|
|
Queue->StringTable,
|
|
Queue->DeviceDescStringId
|
|
);
|
|
MYASSERT(DeviceDesc);
|
|
} else {
|
|
DeviceDesc = NULL;
|
|
}
|
|
|
|
DoingDeviceInstall = Queue->Flags & FQF_DEVICE_INSTALL;
|
|
DriverSigningPolicy = Queue->DriverSigningPolicy;
|
|
}
|
|
|
|
if (Problem != SetupapiVerifyDriverBlocked) {
|
|
//
|
|
// If this is a device installation and the policy is "Ignore",
|
|
// then crank it up to "Warn" if the file is under SFP's
|
|
// protection. This will allow the user to update a driver that
|
|
// ships in our box for which no WHQL certification program
|
|
// exists.
|
|
//
|
|
if(DoingDeviceInstall && (DriverSigningPolicy == DRIVERSIGN_NONE) &&
|
|
IsFileProtected(FullTargetFilename, lc, NULL)) {
|
|
|
|
DriverSigningPolicy = DRIVERSIGN_WARNING;
|
|
|
|
//
|
|
// If we have a queue, update the queue's policy as well.
|
|
//
|
|
if(Queue) {
|
|
Queue->DriverSigningPolicy = DRIVERSIGN_WARNING;
|
|
}
|
|
|
|
//
|
|
// Log the fact that policy was elevated.
|
|
//
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR,
|
|
MSG_LOG_POLICY_ELEVATED_FOR_SFC,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
|
|
if(!pSetupHandleFailedVerification(
|
|
(Queue ? Queue->hWndDriverSigningUi : NULL),
|
|
Problem,
|
|
(Problem == SetupapiVerifyDriverBlocked)
|
|
? FullSourceFilename : Buffer1,
|
|
DeviceDesc,
|
|
DriverSigningPolicy,
|
|
(Queue ? (Queue->Flags & FQF_DIGSIG_ERRORS_NOUI) : FALSE),
|
|
rc,
|
|
lc,
|
|
(((Queue && (Queue->Flags & FQF_ABORT_IF_UNSIGNED)) &&
|
|
(Problem != SetupapiVerifyDriverBlocked))
|
|
? NULL : &ExemptCopyFlags),
|
|
(((Queue && (Queue->Flags & FQF_ABORT_IF_UNSIGNED)) &&
|
|
(Problem != SetupapiVerifyDriverBlocked))
|
|
? NULL : FullTargetFilename)))
|
|
{
|
|
//
|
|
// User elected not to install the unsigned file (or was blocked
|
|
// by policy from doing so).
|
|
//
|
|
goto clean4;
|
|
}
|
|
}
|
|
|
|
if(Queue) {
|
|
//
|
|
// If the caller wants a chance to set a system restore point prior
|
|
// to doing any unsigned installations, then we abort now with a
|
|
// "special" error code that tells them what to do...
|
|
//
|
|
if(Queue->Flags & FQF_ABORT_IF_UNSIGNED) {
|
|
//
|
|
// We don't want the user to see the driver signing UI again
|
|
// when the queue is re-committed...
|
|
//
|
|
if(Queue->DriverSigningPolicy != DRIVERSIGN_NONE) {
|
|
Queue->Flags |= FQF_DIGSIG_ERRORS_NOUI;
|
|
}
|
|
|
|
rc = ERROR_SET_SYSTEM_RESTORE_POINT;
|
|
goto clean4;
|
|
}
|
|
|
|
//
|
|
// Set a flag in the queue that indicates the user has been informed
|
|
// of a signature problem with this queue, and has elected to go
|
|
// ahead and install anyway. Don't set this flag if the queue's
|
|
// policy is "Ignore", on the chance that the policy might be
|
|
// altered later, and we'd want the user to get informed on any
|
|
// subsequent errors.
|
|
//
|
|
if(Queue->DriverSigningPolicy != DRIVERSIGN_NONE) {
|
|
Queue->Flags |= FQF_DIGSIG_ERRORS_NOUI;
|
|
}
|
|
|
|
if (QueueNode) {
|
|
QueueNode->InternalFlags |= ExemptCopyFlags;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reset rc to NO_ERROR and carry on.
|
|
//
|
|
rc = NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Move the target file into its final location.
|
|
//
|
|
SetFileAttributes(FullTargetFilename, FILE_ATTRIBUTE_NORMAL);
|
|
|
|
if(Queue && ((Queue->Flags & FQF_FILES_MODIFIED)==0)) {
|
|
//
|
|
// so far we haven't flagged that any files are modified
|
|
//
|
|
if(CompareFile) {
|
|
//
|
|
// we have an "original" file
|
|
//
|
|
if(pCompareFilesExact(TemporaryTargetFile,CompareFile)) {
|
|
//
|
|
// new file of this name is same as original file of same name
|
|
//
|
|
if(CompareSameFilename) {
|
|
//
|
|
// original is already in place
|
|
// don't need to do a delayed-rename
|
|
//
|
|
FileUnchanged = TRUE;
|
|
}
|
|
} else {
|
|
//
|
|
// file appears to have been modified
|
|
//
|
|
Queue->Flags |= FQF_FILES_MODIFIED;
|
|
}
|
|
} else {
|
|
//
|
|
// be safe, copying over nothing = modified
|
|
//
|
|
Queue->Flags |= FQF_FILES_MODIFIED;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If the target exists and the force-in-use flag is set, then don't try to
|
|
// move the file into place now -- automatically drop into in-use behavior.
|
|
//
|
|
// Want to use MoveFileEx but it didn't exist in Win95. Ugh.
|
|
//
|
|
if(!(ExistingFile && (CopyStyle & SP_COPY_FORCE_IN_USE))
|
|
&& (b = DoMove(TemporaryTargetFile, FullTargetFilename))) {
|
|
//
|
|
// Place security information on the target file if necessary.
|
|
// Ignore errors. The theory here is that the file is already on
|
|
// the target, so if this fails the worst case is that the file is
|
|
// not secure. But the user can still use the system.
|
|
//
|
|
if(SecurityInfo) {
|
|
DWORD err = StampFileSecurity(FullTargetFilename, SecurityInfo);
|
|
if(err != NO_ERROR) {
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR | SETUP_LOG_BUFFER,
|
|
MSG_LOG_FILE_SECURITY_FAILED,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
WriteLogError(lc,SETUP_LOG_ERROR,err);
|
|
} else {
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_VERBOSE,
|
|
MSG_LOG_SET_FILE_SECURITY,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// If this fails, assume the file is in use and mark it for copy on next
|
|
// boot (except in the case where we're copying a boot file, in which
|
|
// case this is a catastrophic failure).
|
|
//
|
|
if(ExistingFile != FullTargetFilename) {
|
|
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR,
|
|
MSG_LOG_REPLACE_BOOT_FILE_FAILED,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
|
|
b = FALSE;
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
} else if (((CopyStyle & SP_COPY_FORCE_IN_USE) == 0) &&
|
|
(FileUnchanged || pCompareFilesExact(TemporaryTargetFile,FullTargetFilename))) {
|
|
//
|
|
// It turns out that new file and old file are exactly the same
|
|
// we can optimize out the delayed move and delete the temporary file
|
|
//
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_INFO,
|
|
MSG_LOG_COPY_IDENTICAL,
|
|
NULL,
|
|
FullTargetFilename,
|
|
TemporaryTargetFile
|
|
);
|
|
|
|
if(SecurityInfo) {
|
|
//
|
|
// we still need to adjust security on it though
|
|
//
|
|
DWORD err = StampFileSecurity(FullTargetFilename, SecurityInfo);
|
|
if(err != NO_ERROR) {
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR | SETUP_LOG_BUFFER,
|
|
MSG_LOG_FILE_SECURITY_FAILED,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
WriteLogError(lc,SETUP_LOG_ERROR,err);
|
|
} else {
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_VERBOSE,
|
|
MSG_LOG_SET_FILE_SECURITY,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
}
|
|
}
|
|
|
|
SetFileAttributes(TemporaryTargetFile, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile(TemporaryTargetFile);
|
|
|
|
b = TRUE;
|
|
|
|
} else {
|
|
b = TRUE;
|
|
try {
|
|
*FileWasInUse = TRUE;
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
b = FALSE;
|
|
}
|
|
|
|
if(b) {
|
|
//
|
|
// If we're trying to do a delayed move to replace a protected
|
|
// system file (using an unsigned one), and we've not been
|
|
// granted an exception to do so, then we should skip the
|
|
// operation altogether (and make a log entry about it)
|
|
//
|
|
if((SigVerifRc != NO_ERROR) &&
|
|
((ExemptCopyFlags & (IQF_TARGET_PROTECTED | IQF_ALLOW_UNSIGNED)) == IQF_TARGET_PROTECTED)) {
|
|
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_ERROR,
|
|
MSG_LOG_DELAYED_MOVE_SKIPPED_FOR_SFC,
|
|
NULL,
|
|
FullTargetFilename
|
|
);
|
|
|
|
//
|
|
// Delete the source file.
|
|
//
|
|
|
|
SetFileAttributes(TemporaryTargetFile, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile(TemporaryTargetFile);
|
|
|
|
} else {
|
|
|
|
BOOL TargetIsProtected = IsFileProtected(FullTargetFilename, lc, NULL);
|
|
|
|
if(Queue == NULL) {
|
|
|
|
b = DelayedMove(
|
|
TemporaryTargetFile,
|
|
FullTargetFilename
|
|
);
|
|
|
|
if(b && TargetIsProtected) {
|
|
//
|
|
// we have to explicitly tell the session manager it's ok
|
|
// to replace the changed files on reboot.
|
|
//
|
|
// in the case that we're using a queue, we post the
|
|
// delayed move and set this flag only if all the delayed
|
|
// move operations succeed
|
|
//
|
|
pSetupProtectedRenamesFlag(TRUE);
|
|
}
|
|
} else {
|
|
b = PostDelayedMove(
|
|
Queue,
|
|
TemporaryTargetFile,
|
|
FullTargetFilename,
|
|
QueueNode->SecurityDesc,
|
|
TargetIsProtected
|
|
);
|
|
}
|
|
|
|
if(b) {
|
|
//
|
|
// Couldn't set security info on the actual target, so at least
|
|
// set it on the temp file that will become the target.
|
|
//
|
|
if(SecurityInfo) {
|
|
StampFileSecurity(TemporaryTargetFile,SecurityInfo);
|
|
}
|
|
|
|
if (lc) {
|
|
WriteLogEntry(lc,
|
|
SETUP_LOG_WARNING,
|
|
MSG_LOG_COPY_DELAYED,
|
|
NULL,
|
|
FullTargetFilename,
|
|
TemporaryTargetFile
|
|
);
|
|
}
|
|
|
|
//
|
|
// Tell the callback that we queued this file for delayed copy.
|
|
//
|
|
if(CopyMsgHandler) {
|
|
|
|
FilePaths.Source = TemporaryTargetFile;
|
|
FilePaths.Target = FullTargetFilename;
|
|
FilePaths.Win32Error = NO_ERROR;
|
|
FilePaths.Flags = FILEOP_COPY;
|
|
|
|
pSetupCallMsgHandler(
|
|
lc,
|
|
CopyMsgHandler,
|
|
IsMsgHandlerNativeCharWidth,
|
|
Context,
|
|
SPFILENOTIFY_FILEOPDELAYED,
|
|
(UINT_PTR)&FilePaths,
|
|
0
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// FileWasInUse pointer went bad
|
|
//
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!b) {
|
|
rc = GetLastError();
|
|
goto clean4;
|
|
}
|
|
|
|
//
|
|
// We're done. Delete the source if necessary and return.
|
|
//
|
|
if((CopyStyle & SP_COPY_DELETESOURCE) && !Moved) {
|
|
DeleteFile(FullSourceFilename);
|
|
}
|
|
|
|
rc = NO_ERROR;
|
|
Ok = TRUE;
|
|
goto clean3;
|
|
|
|
clean4:
|
|
//
|
|
// Remove temporary target file.
|
|
// In case pSetupDecompressOrCopyFile MoveFile'd the source,
|
|
// we really need to try to move it back, so the source file isn't
|
|
// blown away when this routine fails.
|
|
//
|
|
if(Moved) {
|
|
MoveFile(TemporaryTargetFile,FullSourceFilename);
|
|
} else {
|
|
SetFileAttributes(TemporaryTargetFile,FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile(TemporaryTargetFile);
|
|
}
|
|
|
|
clean3:
|
|
MyFree(TemporaryTargetFile);
|
|
//
|
|
// If we didn't have a file queue, then we may have allocated a device
|
|
// description and validation platform structure when we called
|
|
// IsInfForDeviceInstall. Clean those up now.
|
|
//
|
|
if(!Queue) {
|
|
if(DeviceDesc) {
|
|
MyFree(DeviceDesc);
|
|
}
|
|
if(ValidationPlatform) {
|
|
MyFree(ValidationPlatform);
|
|
}
|
|
}
|
|
|
|
clean2:
|
|
if (BackupFileName) {
|
|
MyFree(BackupFileName);
|
|
}
|
|
MyFree(FullTargetFilename);
|
|
|
|
clean1:
|
|
MyFree(FullSourceFilename);
|
|
|
|
clean0:
|
|
if(SecurityInfo) {
|
|
MyFree(SecurityInfo);
|
|
}
|
|
//
|
|
// if there was an error of some sort, log it
|
|
//
|
|
if (rc != NO_ERROR) {
|
|
//
|
|
// maybe we ought to embelish this a bit.
|
|
//
|
|
WriteLogEntry(
|
|
lc,
|
|
SETUP_LOG_ERROR,
|
|
rc,
|
|
NULL);
|
|
}
|
|
|
|
if(slot_fileop) {
|
|
ReleaseLogInfoSlot(lc, slot_fileop);
|
|
}
|
|
|
|
if(LoadedInf) {
|
|
UnlockInf(LoadedInf);
|
|
}
|
|
|
|
SetLastError(rc);
|
|
return(Ok);
|
|
}
|
|
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupInstallFileExA(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCSTR SourceFile, OPTIONAL
|
|
IN PCSTR SourcePathRoot, OPTIONAL
|
|
IN PCSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK_A CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context, OPTIONAL
|
|
OUT PBOOL FileWasInUse
|
|
)
|
|
{
|
|
PCWSTR sourceFile,sourcePathRoot,destinationName;
|
|
BOOL b, DontCare;
|
|
DWORD rc;
|
|
|
|
sourceFile = NULL;
|
|
sourcePathRoot = NULL;
|
|
destinationName = NULL;
|
|
rc = NO_ERROR;
|
|
|
|
if(SourceFile) {
|
|
rc = pSetupCaptureAndConvertAnsiArg(SourceFile,&sourceFile);
|
|
}
|
|
if((rc == NO_ERROR) && SourcePathRoot) {
|
|
rc = pSetupCaptureAndConvertAnsiArg(SourcePathRoot,&sourcePathRoot);
|
|
}
|
|
if((rc == NO_ERROR) && DestinationName) {
|
|
rc = pSetupCaptureAndConvertAnsiArg(DestinationName,&destinationName);
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
|
|
b = _SetupInstallFileEx(
|
|
NULL,
|
|
NULL,
|
|
InfHandle,
|
|
InfContext,
|
|
sourceFile,
|
|
sourcePathRoot,
|
|
destinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
FileWasInUse,
|
|
FALSE,
|
|
&DontCare
|
|
);
|
|
|
|
rc = b ? NO_ERROR : GetLastError();
|
|
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
if(sourcePathRoot) {
|
|
MyFree(sourcePathRoot);
|
|
}
|
|
if(destinationName) {
|
|
MyFree(destinationName);
|
|
}
|
|
SetLastError(rc);
|
|
return b;
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupInstallFileExW(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCWSTR SourceFile, OPTIONAL
|
|
IN PCWSTR SourcePathRoot, OPTIONAL
|
|
IN PCWSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK_W CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context, OPTIONAL
|
|
OUT PBOOL FileWasInUse
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(InfHandle);
|
|
UNREFERENCED_PARAMETER(InfContext);
|
|
UNREFERENCED_PARAMETER(SourceFile);
|
|
UNREFERENCED_PARAMETER(SourcePathRoot);
|
|
UNREFERENCED_PARAMETER(DestinationName);
|
|
UNREFERENCED_PARAMETER(CopyStyle);
|
|
UNREFERENCED_PARAMETER(CopyMsgHandler);
|
|
UNREFERENCED_PARAMETER(Context);
|
|
UNREFERENCED_PARAMETER(FileWasInUse);
|
|
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOL
|
|
SetupInstallFileEx(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCTSTR SourceFile, OPTIONAL
|
|
IN PCTSTR SourcePathRoot, OPTIONAL
|
|
IN PCTSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context, OPTIONAL
|
|
OUT PBOOL FileWasInUse
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Same as SetupInstallFile().
|
|
|
|
Arguments:
|
|
|
|
Same as SetupInstallFile().
|
|
|
|
FileWasInUse - receives flag indicating whether the file was in use.
|
|
|
|
Return Value:
|
|
|
|
Same as SetupInstallFile().
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b, DontCare;
|
|
PCTSTR sourceFile,sourcePathRoot,destinationName;
|
|
PCTSTR p;
|
|
DWORD rc;
|
|
|
|
//
|
|
// Capture args.
|
|
//
|
|
if(SourceFile) {
|
|
rc = CaptureStringArg(SourceFile,&p);
|
|
if(rc != NO_ERROR) {
|
|
SetLastError(rc);
|
|
return FALSE;
|
|
}
|
|
sourceFile = p;
|
|
} else {
|
|
sourceFile = NULL;
|
|
}
|
|
|
|
if(SourcePathRoot) {
|
|
rc = CaptureStringArg(SourcePathRoot,&p);
|
|
if(rc != NO_ERROR) {
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
SetLastError(rc);
|
|
return FALSE;
|
|
}
|
|
sourcePathRoot = p;
|
|
} else {
|
|
sourcePathRoot = NULL;
|
|
}
|
|
|
|
if(DestinationName) {
|
|
rc = CaptureStringArg(DestinationName,&p);
|
|
if(rc != NO_ERROR) {
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
if(sourcePathRoot) {
|
|
MyFree(sourcePathRoot);
|
|
}
|
|
SetLastError(rc);
|
|
return FALSE;
|
|
}
|
|
destinationName = p;
|
|
} else {
|
|
destinationName = NULL;
|
|
}
|
|
|
|
b = _SetupInstallFileEx(
|
|
NULL,
|
|
NULL,
|
|
InfHandle,
|
|
InfContext,
|
|
sourceFile,
|
|
sourcePathRoot,
|
|
destinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
FileWasInUse,
|
|
TRUE,
|
|
&DontCare
|
|
);
|
|
|
|
//
|
|
// We GetLastError and then set it back again before returning, so that
|
|
// the memory frees we do below can't blow away the error code.
|
|
//
|
|
rc = b ? NO_ERROR : GetLastError();
|
|
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
if(sourcePathRoot) {
|
|
MyFree(sourcePathRoot);
|
|
}
|
|
if(destinationName) {
|
|
MyFree(destinationName);
|
|
}
|
|
|
|
SetLastError(rc);
|
|
return b;
|
|
}
|
|
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupInstallFileA(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCSTR SourceFile, OPTIONAL
|
|
IN PCSTR SourcePathRoot, OPTIONAL
|
|
IN PCSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK_A CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context OPTIONAL
|
|
)
|
|
{
|
|
BOOL b;
|
|
BOOL InUse;
|
|
|
|
b = SetupInstallFileExA(
|
|
InfHandle,
|
|
InfContext,
|
|
SourceFile,
|
|
SourcePathRoot,
|
|
DestinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
&InUse
|
|
);
|
|
|
|
return(b);
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupInstallFileW(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCWSTR SourceFile, OPTIONAL
|
|
IN PCWSTR SourcePathRoot, OPTIONAL
|
|
IN PCWSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK_W CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context OPTIONAL
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(InfHandle);
|
|
UNREFERENCED_PARAMETER(InfContext);
|
|
UNREFERENCED_PARAMETER(SourceFile);
|
|
UNREFERENCED_PARAMETER(SourcePathRoot);
|
|
UNREFERENCED_PARAMETER(DestinationName);
|
|
UNREFERENCED_PARAMETER(CopyStyle);
|
|
UNREFERENCED_PARAMETER(CopyMsgHandler);
|
|
UNREFERENCED_PARAMETER(Context);
|
|
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOL
|
|
SetupInstallFile(
|
|
IN HINF InfHandle, OPTIONAL
|
|
IN PINFCONTEXT InfContext, OPTIONAL
|
|
IN PCTSTR SourceFile, OPTIONAL
|
|
IN PCTSTR SourcePathRoot, OPTIONAL
|
|
IN PCTSTR DestinationName, OPTIONAL
|
|
IN DWORD CopyStyle,
|
|
IN PSP_FILE_CALLBACK CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Note: no disk prompting is performed by this routine. The caller must
|
|
ensure that the source specified in SourcePathRoot or SourceFile
|
|
(see below) is accessible.
|
|
|
|
Arguments:
|
|
|
|
InfHandle - handle of inf file containing [SourceDisksNames]
|
|
and [SourceDisksFiles] sections. If InfContext is not specified
|
|
and CopyFlags includes SP_COPY_SOURCE_ABSOLUTE or
|
|
SP_COPY_SOURCEPATH_ABSOLUTE, then InfHandle is ignored.
|
|
|
|
InfContext - if specified, supplies context for a line in a copy file
|
|
section in an inf file. The routine looks this file up in the
|
|
[SourceDisksFiles] section of InfHandle to get file copy info.
|
|
If not specified, SourceFile must be. If this parameter is specified,
|
|
then InfHandle must also be specified.
|
|
|
|
SourceFile - if specified, supplies the file name (no path) of the file
|
|
to be copied. The file is looked up in [SourceDisksFiles].
|
|
Must be specified if InfContext is not; ignored if InfContext
|
|
is specified.
|
|
|
|
SourcePathRoot - Supplies the root path for the source (for example,
|
|
a:\ or f:\). Paths in [SourceDisksNames] are appended to this path.
|
|
Ignored if CopyStyle includes SP_COPY_SOURCE_ABSOLUTE.
|
|
|
|
DestinationName - if InfContext is specified, supplies the filename only
|
|
(no path) of the target file. Can be NULL to indicate that the
|
|
target file is to have the same name as the source file. If InfContext is
|
|
not specified, supplies the full target path and filename for the target
|
|
file.
|
|
|
|
CopyStyle - supplies flags that control the behavior of the copy operation.
|
|
|
|
SP_COPY_DELETESOURCE - Delete the source file upon successful copy.
|
|
The caller receives no notification if the delete fails.
|
|
|
|
SP_COPY_REPLACEONLY - Copy the file only if doing so would overwrite
|
|
a file at the destination path.
|
|
|
|
SP_COPY_NEWER - Examine each file being copied to see if its version resources
|
|
(or timestamps for non-image files) indicate that it it is not newer than
|
|
an existing copy on the target. If so, and a CopyMsgHandler is specified,
|
|
the caller is notified and may veto the copy. If CopyMsgHandler is not
|
|
specified, the file is not copied.
|
|
|
|
SP_COPY_NOOVERWRITE - Check whether the target file exists, and, if so,
|
|
notify the caller who may veto the copy. If no CopyMsgHandler is specified,
|
|
the file is not overwritten.
|
|
|
|
SP_COPY_NODECOMP - Do not decompress the file. When this option is given,
|
|
the target file is not given the uncompressed form of the source name
|
|
(if appropriate). For example, copying f:\mips\cmd.ex_ to \\foo\bar
|
|
will result a target file \\foo\bar\cmd.ex_. (If this flag wasn't specified
|
|
the file would be decompressed and the target would be called
|
|
\\foo\bar\cmd.exe). The filename part of the target file name
|
|
is stripped and replaced with the filename of the soruce. When this option
|
|
is given, SP_COPY_LANGUAGEAWARE and SP_COPY_NEWER are ignored.
|
|
|
|
SP_COPY_ALREADYDECOMP - assume file to be decompressed but may have
|
|
compressed source name. In this case, rename the file on copy and
|
|
check SP_COPY_LANGUAGEAWARE/SP_COPY_NEWER, but don't attempt to
|
|
decompress the file any further.
|
|
|
|
SP_COPY_LANGUAGEAWARE - Examine each file being copied to see if its language
|
|
differs from the language of any existing file already on the target.
|
|
If so, and a CopyMsgHandler is specified, the caller is notified and
|
|
may veto the copy. If CopyMsgHandler is not specified, the file is not copied.
|
|
|
|
SP_COPY_SOURCE_ABSOLUTE - SourceFile is a full source path.
|
|
Do not attempt to look it up in [SourceDisksNames].
|
|
|
|
SP_COPY_SOURCEPATH_ABSOLUTE - SourcePathRoot is the full path part of the
|
|
source file. Ignore the relative source specified in the [SourceDisksNames]
|
|
section of the inf file for the source media where the file is located.
|
|
Ignored if SP_COPY_SOURCE_ABSOLUTE is specified.
|
|
|
|
SP_COPY_FORCE_IN_USE - if the target exists, behave as if it is in use and
|
|
queue the file for copy on next reboot.
|
|
|
|
CopyMsgHandler - if specified, supplies a callback function to be notified of
|
|
various conditions that may arise during the file copy.
|
|
|
|
Context - supplies a caller-defined value to be passed as the first
|
|
parameter to CopyMsgHandler.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a file was copied. FALSE if not. Use GetLastError for extended
|
|
error information. If GetLastError returns NO_ERROR, then the file copy was
|
|
aborted because (a) it wasn't needed or (b) a callback function returned
|
|
FALSE.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
BOOL InUse;
|
|
|
|
b = SetupInstallFileEx(
|
|
InfHandle,
|
|
InfContext,
|
|
SourceFile,
|
|
SourcePathRoot,
|
|
DestinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
&InUse
|
|
);
|
|
|
|
return(b);
|
|
}
|
|
|
|
|