mirror of https://github.com/lianthony/NT4.0
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.
1135 lines
32 KiB
1135 lines
32 KiB
/*++
|
|
|
|
Copyright (c) 1993 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
copy.c
|
|
|
|
Abstract:
|
|
|
|
High-level file copy/installation functions
|
|
|
|
Author:
|
|
|
|
Ted Miller (tedm) 14-Feb-1995
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "setupntp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
//
|
|
// Mask for all copy flags that will require us to determine
|
|
// version information.
|
|
//
|
|
#define SP_COPY_MASK_NEEDVERINFO (SP_COPY_NEWER | SP_COPY_FORCE_NEWER | SP_COPY_LANGUAGEAWARE)
|
|
|
|
|
|
BOOL
|
|
DoMove(
|
|
IN PCTSTR CurrentName,
|
|
IN PCTSTR NewName
|
|
)
|
|
{
|
|
BOOL b;
|
|
|
|
//
|
|
// Try to be as efficient as possible on Windows NT.
|
|
//
|
|
if(OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) {
|
|
b = MoveFileEx(CurrentName,NewName,MOVEFILE_REPLACE_EXISTING);
|
|
} else {
|
|
DeleteFile(NewName);
|
|
b = MoveFile(CurrentName,NewName);
|
|
}
|
|
|
|
return(b);
|
|
}
|
|
|
|
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 PVOID CopyMsgHandler, OPTIONAL
|
|
IN PVOID Context, OPTIONAL
|
|
OUT PBOOL FileWasInUse,
|
|
IN BOOL IsMsgHandlerNativeCharWidth
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Actual implementation of SetupInstallFileEx. Handles either ANSI or
|
|
Unicode callback routine.
|
|
|
|
Arguments:
|
|
|
|
Same as SetupInstallFileEx().
|
|
|
|
IsMsgHandlerNativeCharWidth - supplies a flag indicating whether CopyMsgHandler
|
|
expects native char widths args (or ansi ones, in the unicode build
|
|
of the dll).
|
|
|
|
Return Value:
|
|
|
|
Same as SetupInstallFileEx().
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
BOOL Ok;
|
|
DWORD rc;
|
|
UINT SourceId;
|
|
TCHAR Buffer1[MAX_PATH];
|
|
TCHAR Buffer2[MAX_PATH];
|
|
PCTSTR FullSourceFilename;
|
|
PCTSTR FullTargetFilename;
|
|
PTCHAR SourceFilenamePart;
|
|
PTSTR ActualSourceFilename;
|
|
PTSTR TemporaryTargetFile;
|
|
UINT CompressionType;
|
|
DWORD SourceFileSize;
|
|
DWORD TargetFileSize;
|
|
PTSTR p;
|
|
BOOL TargetExists;
|
|
DWORDLONG SourceVersion, TargetVersion;
|
|
LANGID SourceLanguage;
|
|
LANGID TargetLanguage;
|
|
WIN32_FIND_DATA SourceFindData;
|
|
WIN32_FIND_DATA TargetFindData;
|
|
UINT NotifyFlags;
|
|
PSECURITY_DESCRIPTOR SecurityInfo;
|
|
FILEPATHS FilePaths;
|
|
UINT param;
|
|
FILETIME sFileTime,tFileTime;
|
|
WORD sDosTime,sDosDate,tDosTime,tDosDate;
|
|
BOOL Moved;
|
|
|
|
//
|
|
// Assume failure.
|
|
//
|
|
Ok = FALSE;
|
|
SecurityInfo = NULL;
|
|
Moved = FALSE;
|
|
try {
|
|
*FileWasInUse = FALSE;
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return(FALSE);
|
|
}
|
|
|
|
if(InfContext) {
|
|
if(!InfHandle || (InfHandle == INVALID_HANDLE_VALUE)) {
|
|
rc = ERROR_INVALID_PARAMETER;
|
|
goto clean0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Determine the full source path and filename of the file.
|
|
//
|
|
if(CopyStyle & SP_COPY_SOURCE_ABSOLUTE) {
|
|
FullSourceFilename = DuplicateString(SourceFile);
|
|
} else {
|
|
|
|
//
|
|
// Get the relative path for this file if necessary.
|
|
//
|
|
if(CopyStyle & SP_COPY_SOURCEPATH_ABSOLUTE) {
|
|
Buffer2[0] = 0;
|
|
b = TRUE;
|
|
} else {
|
|
b = SetupGetSourceFileLocation(
|
|
InfHandle,
|
|
InfContext,
|
|
SourceFile,
|
|
&SourceId,
|
|
Buffer2,
|
|
MAX_PATH,
|
|
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;
|
|
}
|
|
|
|
lstrcpyn(Buffer1,SourcePathRoot,MAX_PATH);
|
|
|
|
if(!ConcatenatePaths(Buffer1,Buffer2,MAX_PATH,NULL)
|
|
|| !ConcatenatePaths(Buffer1,SourceFile,MAX_PATH,NULL)) {
|
|
rc = ERROR_FILENAME_EXCED_RANGE;
|
|
goto clean0;
|
|
}
|
|
|
|
FullSourceFilename = DuplicateString(Buffer1);
|
|
}
|
|
|
|
if(!FullSourceFilename) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean0;
|
|
}
|
|
|
|
if(SourceFilenamePart = _tcsrchr(FullSourceFilename,TEXT('\\'))) {
|
|
SourceFilenamePart++;
|
|
} else {
|
|
SourceFilenamePart = (PTCHAR)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 = ConcatenatePaths(
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Make sure the target path exists.
|
|
//
|
|
rc = pSetupMakeSurePathExists(FullTargetFilename);
|
|
if(rc != NO_ERROR) {
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// Determine if the source file is compressed and get compression type
|
|
// if so.
|
|
//
|
|
rc = SetupInternalGetFileCompressionInfo(
|
|
FullSourceFilename,
|
|
&ActualSourceFilename,
|
|
&SourceFindData,
|
|
&TargetFileSize,
|
|
&CompressionType
|
|
);
|
|
|
|
if(rc != NO_ERROR) {
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// Got the actual source file name now.
|
|
//
|
|
MyFree(FullSourceFilename);
|
|
FullSourceFilename = ActualSourceFilename;
|
|
if(SourceFilenamePart = _tcsrchr(FullSourceFilename,TEXT('\\'))) {
|
|
SourceFilenamePart++;
|
|
} else {
|
|
SourceFilenamePart = (PTCHAR)FullSourceFilename;
|
|
}
|
|
|
|
//
|
|
// If the no-decomp 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.
|
|
//
|
|
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.
|
|
//
|
|
if(p = _tcsrchr(FullTargetFilename,TEXT('\\'))) {
|
|
*p = 0;
|
|
}
|
|
|
|
//
|
|
// Concatenate the source filename onto the target pathname.
|
|
//
|
|
lstrcpyn(Buffer1,FullTargetFilename,MAX_PATH);
|
|
if(!ConcatenatePaths(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. If not, and the replace only flag is set,
|
|
// we're done. Otherwise get version info from the target if we're going to
|
|
// need it.
|
|
//
|
|
if(TargetExists = FileExists(FullTargetFilename,&TargetFindData)) {
|
|
if(CopyStyle & SP_COPY_MASK_NEEDVERINFO) {
|
|
if(!GetVersionInfoFromImage(FullTargetFilename,&TargetVersion,&TargetLanguage)) {
|
|
TargetVersion = 0;
|
|
TargetLanguage = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the target file exists we'll want to preserve security info on it.
|
|
//
|
|
if(RetreiveFileSecurity(FullTargetFilename,&SecurityInfo) != NO_ERROR) {
|
|
SecurityInfo = NULL;
|
|
}
|
|
} else {
|
|
if(CopyStyle & SP_COPY_REPLACEONLY) {
|
|
rc = NO_ERROR;
|
|
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);
|
|
if(p = _tcsrchr(Buffer1,TEXT('\\'))) {
|
|
*p = 0;
|
|
}
|
|
|
|
if(!GetTempFileName(Buffer1,TEXT("SETP"),0,Buffer2)) {
|
|
rc = GetLastError();
|
|
goto clean2;
|
|
}
|
|
|
|
TemporaryTargetFile = DuplicateString(Buffer2);
|
|
if(!TemporaryTargetFile) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean2;
|
|
}
|
|
|
|
//
|
|
// 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 are2 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;
|
|
}
|
|
|
|
//
|
|
// 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(TargetExists) {
|
|
|
|
if(CopyStyle & SP_COPY_FORCE_NOOVERWRITE) {
|
|
//
|
|
// No overwrite and no notification of callback either.
|
|
//
|
|
rc = NO_ERROR;
|
|
goto clean4;
|
|
}
|
|
|
|
param = 0;
|
|
|
|
//
|
|
// If we're not supposed to overwrite existing files,
|
|
// then the overwrite check fails.
|
|
//
|
|
if(CopyStyle & SP_COPY_NOOVERWRITE) {
|
|
NotifyFlags |= SPFILENOTIFY_TARGETEXISTS;
|
|
}
|
|
|
|
if(CopyStyle & SP_COPY_MASK_NEEDVERINFO) {
|
|
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.
|
|
//
|
|
if((CopyStyle & SP_COPY_LANGUAGEAWARE)
|
|
&& SourceLanguage
|
|
&& TargetLanguage
|
|
&& (SourceLanguage != TargetLanguage)) {
|
|
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.
|
|
//
|
|
if(CopyStyle & (SP_COPY_NEWER | SP_COPY_FORCE_NEWER)) {
|
|
|
|
if(SourceVersion && TargetVersion) {
|
|
b = (TargetVersion > SourceVersion);
|
|
//
|
|
// Check special case where a flag is set indicating that
|
|
// we should just silently not copy the newer file.
|
|
//
|
|
if(b && (CopyStyle & SP_COPY_FORCE_NEWER)) {
|
|
//
|
|
// Target is newer; don't copy the file.
|
|
//
|
|
rc = NO_ERROR;
|
|
goto clean4;
|
|
}
|
|
} 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
|
|
}
|
|
|
|
if(b) {
|
|
NotifyFlags |= SPFILENOTIFY_TARGETNEWER;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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) {
|
|
|
|
FilePaths.Source = FullSourceFilename;
|
|
FilePaths.Target = FullTargetFilename;
|
|
FilePaths.Win32Error = NO_ERROR;
|
|
|
|
if(!CopyMsgHandler
|
|
|| !pSetupCallMsgHandler(
|
|
CopyMsgHandler,
|
|
IsMsgHandlerNativeCharWidth,
|
|
Context,
|
|
NotifyFlags,
|
|
(UINT)&FilePaths,
|
|
param))
|
|
{
|
|
|
|
rc = NO_ERROR;
|
|
goto clean4;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move the target file into its final location.
|
|
//
|
|
if(TargetExists) {
|
|
SetFileAttributes(FullTargetFilename,FILE_ATTRIBUTE_NORMAL);
|
|
}
|
|
|
|
//
|
|
// 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(!(TargetExists && (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) {
|
|
StampFileSecurity(FullTargetFilename,SecurityInfo);
|
|
}
|
|
} else {
|
|
//
|
|
// If this fails, assume the file is in use and mark it for copy on next boot.
|
|
//
|
|
b = TRUE;
|
|
try {
|
|
*FileWasInUse = TRUE;
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
b = FALSE;
|
|
}
|
|
if(b) {
|
|
|
|
b = DelayedMove(
|
|
TemporaryTargetFile,
|
|
FullTargetFilename
|
|
);
|
|
|
|
if(b && SecurityInfo) {
|
|
//
|
|
// Couldn't set security info on the actual target, so at least
|
|
// set it on the temp file that will become the target.
|
|
//
|
|
StampFileSecurity(TemporaryTargetFile,SecurityInfo);
|
|
|
|
//
|
|
// 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(
|
|
CopyMsgHandler,
|
|
IsMsgHandlerNativeCharWidth,
|
|
Context,
|
|
SPFILENOTIFY_FILEOPDELAYED,
|
|
(UINT)&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);
|
|
clean2:
|
|
MyFree(FullTargetFilename);
|
|
clean1:
|
|
MyFree(FullSourceFilename);
|
|
clean0:
|
|
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;
|
|
DWORD rc;
|
|
|
|
sourceFile = NULL;
|
|
sourcePathRoot = NULL;
|
|
destinationName = NULL;
|
|
rc = NO_ERROR;
|
|
|
|
if(SourceFile) {
|
|
rc = CaptureAndConvertAnsiArg(SourceFile,&sourceFile);
|
|
}
|
|
if((rc == NO_ERROR) && SourcePathRoot) {
|
|
rc = CaptureAndConvertAnsiArg(SourcePathRoot,&sourcePathRoot);
|
|
}
|
|
if((rc == NO_ERROR) && DestinationName) {
|
|
rc = CaptureAndConvertAnsiArg(DestinationName,&destinationName);
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
|
|
b = _SetupInstallFileEx(
|
|
InfHandle,
|
|
InfContext,
|
|
sourceFile,
|
|
sourcePathRoot,
|
|
destinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
FileWasInUse,
|
|
FALSE
|
|
);
|
|
|
|
rc = b ? NO_ERROR : GetLastError();
|
|
}
|
|
|
|
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;
|
|
PCTSTR sourceFile,sourcePathRoot,destinationName;
|
|
PCTSTR p;
|
|
DWORD rc;
|
|
|
|
//
|
|
// Capture args.
|
|
//
|
|
if(SourceFile) {
|
|
rc = CaptureStringArg(SourceFile,&p);
|
|
if(rc != NO_ERROR) {
|
|
return(rc);
|
|
}
|
|
sourceFile = p;
|
|
} else {
|
|
sourceFile = NULL;
|
|
}
|
|
|
|
if(SourcePathRoot) {
|
|
rc = CaptureStringArg(SourcePathRoot,&p);
|
|
if(rc != NO_ERROR) {
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
return(rc);
|
|
}
|
|
sourcePathRoot = p;
|
|
} else {
|
|
sourcePathRoot = NULL;
|
|
}
|
|
|
|
if(DestinationName) {
|
|
rc = CaptureStringArg(DestinationName,&p);
|
|
if(rc != NO_ERROR) {
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
if(sourcePathRoot) {
|
|
MyFree(sourcePathRoot);
|
|
}
|
|
return(rc);
|
|
}
|
|
destinationName = p;
|
|
} else {
|
|
destinationName = NULL;
|
|
}
|
|
|
|
b = _SetupInstallFileEx(
|
|
InfHandle,
|
|
InfContext,
|
|
sourceFile,
|
|
sourcePathRoot,
|
|
destinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
FileWasInUse,
|
|
TRUE
|
|
);
|
|
|
|
if(sourceFile) {
|
|
MyFree(sourceFile);
|
|
}
|
|
if(sourcePathRoot) {
|
|
MyFree(sourcePathRoot);
|
|
}
|
|
if(destinationName) {
|
|
MyFree(destinationName);
|
|
}
|
|
|
|
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_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 callback function returned FALSE.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
BOOL InUse;
|
|
|
|
b = SetupInstallFileEx(
|
|
InfHandle,
|
|
InfContext,
|
|
SourceFile,
|
|
SourcePathRoot,
|
|
DestinationName,
|
|
CopyStyle,
|
|
CopyMsgHandler,
|
|
Context,
|
|
&InUse
|
|
);
|
|
|
|
return(b);
|
|
}
|
|
|
|
|
|
DWORD
|
|
pSetupMakeSurePathExists(
|
|
IN PCTSTR FullFilespec
|
|
)
|
|
{
|
|
TCHAR Buffer[MAX_PATH];
|
|
PTCHAR p,q;
|
|
BOOL Done;
|
|
DWORD d;
|
|
WIN32_FIND_DATA FindData;
|
|
|
|
//
|
|
// BUGBUG ignore UNC paths for now.
|
|
//
|
|
if((FullFilespec[0] == TEXT('\\')) && (FullFilespec[1] == TEXT('\\'))) {
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
//
|
|
// Locate and strip off the final component, which we assume is
|
|
// the filename.
|
|
//
|
|
lstrcpyn(Buffer,FullFilespec,MAX_PATH);
|
|
if(p = _tcsrchr(Buffer,TEXT('\\'))) {
|
|
*p = 0;
|
|
//
|
|
// If it's a drive root, nothing to do.
|
|
//
|
|
if(Buffer[0] && (Buffer[1] == TEXT(':')) && !Buffer[2]) {
|
|
return(NO_ERROR);
|
|
}
|
|
} else {
|
|
//
|
|
// Just a relative filename, nothing to do.
|
|
//
|
|
return(NO_ERROR);
|
|
}
|
|
|
|
//
|
|
// If it already exists do nothing.
|
|
//
|
|
if(FileExists(Buffer,&FindData)) {
|
|
return((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? NO_ERROR : ERROR_DIRECTORY);
|
|
}
|
|
|
|
p = Buffer;
|
|
|
|
//
|
|
// Compensate for drive spec.
|
|
//
|
|
if(p[0] && (p[1] == TEXT(':'))) {
|
|
p += 2;
|
|
}
|
|
|
|
Done = FALSE;
|
|
do {
|
|
//
|
|
// Skip path sep char.
|
|
//
|
|
while(*p == TEXT('\\')) {
|
|
p++;
|
|
}
|
|
|
|
//
|
|
// Locate next path sep char or terminating nul.
|
|
//
|
|
if(q = _tcschr(p,TEXT('\\'))) {
|
|
*q = 0;
|
|
} else {
|
|
q = p + lstrlen(p);
|
|
Done = TRUE;
|
|
}
|
|
|
|
//
|
|
// Create this portion of the path.
|
|
//
|
|
if(!CreateDirectory(Buffer,NULL)) {
|
|
d = GetLastError();
|
|
if(d != ERROR_ALREADY_EXISTS) {
|
|
return(d);
|
|
}
|
|
}
|
|
|
|
if(!Done) {
|
|
*q = TEXT('\\');
|
|
p = q+1;
|
|
}
|
|
|
|
} while(!Done);
|
|
|
|
return(NO_ERROR);
|
|
}
|