/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: backup.c Abstract: Routines to control backup during install process And restore of an old install process Author: Jamie Hunter (jamiehun) 13-Jan-1997 Revision History: --*/ #include "precomp.h" #pragma hdrstop VOID pSetupExemptFileFromProtection( IN PCTSTR FileName, IN DWORD FileChangeFlags, IN PSETUP_LOG_CONTEXT LogContext, OPTIONAL OUT PDWORD QueueNodeFlags OPTIONAL ); // // ========================================================== // DWORD pSetupQueueBackupCopy( IN HSPFILEQ QueueHandle, IN LONG TargetRootPath, IN LONG TargetSubDir, OPTIONAL IN LONG TargetFilename, IN LONG BackupRootPath, IN LONG BackupSubDir, OPTIONAL IN LONG BackupFilename ) /*++ Routine Description: Place a backup copy operation on a setup file queue. Target is to be backed up at Backup location Arguments: QueueHandle - supplies a handle to a setup file queue, as returned by SetupOpenFileQueue. TargetRootPath - Supplies the source directory, eg C:\WINNT\ TargetSubDir - Supplies the optional sub-directory (eg WINNT if RootPath = c:\ ) TargetFilename - supplies the filename part of the file to be copied. BackupRootPath - supplies the directory where the file is to be copied. BackupSubDir - supplies the optional sub-directory BackupFilename - supplies the name of the target file. Return Value: same value as GetLastError() indicating error, or NO_ERROR --*/ { PSP_FILE_QUEUE Queue; PSP_FILE_QUEUE_NODE QueueNode,TempNode; int Size; DWORD Err; PVOID StringTable; PTSTR FullRootName; Queue = (PSP_FILE_QUEUE)QueueHandle; Err = NO_ERROR; try { StringTable = Queue->StringTable; // used for strings in source queue } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean0; } // // Allocate a queue structure. // QueueNode = MyMalloc(sizeof(SP_FILE_QUEUE_NODE)); if (!QueueNode) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // Operation is backup. // QueueNode->Operation = FILEOP_BACKUP; QueueNode->InternalFlags = 0; QueueNode->SourceRootPath = BackupRootPath; QueueNode->SourcePath = BackupSubDir; QueueNode->SourceFilename = BackupFilename; // if target has a sub-dir, we have to combine root and subdir into one string if (TargetSubDir != -1) { FullRootName = pSetupFormFullPath( StringTable, TargetRootPath, TargetSubDir, -1); if (!FullRootName) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean1; } TargetRootPath = pSetupStringTableAddString(StringTable, FullRootName, STRTAB_CASE_SENSITIVE ); MyFree(FullRootName); if (TargetRootPath == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean1; } // now combined into TargetRootPath TargetSubDir = -1; } QueueNode->TargetDirectory = TargetRootPath; QueueNode->TargetFilename = TargetFilename; QueueNode->Next = NULL; // // Link the node onto the end of the backup queue // if (Queue->BackupQueue) { for (TempNode = Queue->BackupQueue; TempNode->Next; TempNode=TempNode->Next) /* blank */ ; TempNode->Next = QueueNode; } else { Queue->BackupQueue = QueueNode; } Queue->BackupNodeCount++; Err = NO_ERROR; goto clean0; clean1: MyFree(QueueNode); clean0: SetLastError(Err); return Err; } // // ========================================================== // BOOL pSetupGetFullBackupPath( OUT PTSTR FullPath, IN PCTSTR Path, OPTIONAL IN UINT TargetBufferSize, OUT PUINT RequiredSize OPTIONAL ) /*++ Routine Description: This routine takes a potentially relative path and concatenates it to the base path Arguments: FullPath - Destination for full path Path - Relative source path to backup directory if specified. If NULL, generates a temporary path TargetBufferSize - Size of buffer (characters) RequiredSize - Filled in with size required to contain full path Return Value: If the function succeeds, return TRUE If there was an error, return FALSE --*/ { UINT PathLen; LPCTSTR Base = WindowsBackupDirectory; if(!Path) { // // temporary location // Path = SP_BACKUP_OLDFILES; Base = WindowsDirectory; } // // Backup directory is stored in "WindowsBackupDirectory" for permanent backups // and WindowsDirectory\SP_BACKUP_OLDFILES for temporary backups // PathLen = lstrlen(Base); if ( FullPath == NULL || TargetBufferSize <= PathLen ) { // just calculate required path len FullPath = (PTSTR) Base; TargetBufferSize = 0; } else { // calculate and copy lstrcpy(FullPath, Base); } return pSetupConcatenatePaths(FullPath, Path, TargetBufferSize, RequiredSize); } // // ========================================================== // DWORD pSetupBackupCopyString( IN PVOID DestStringTable, OUT PLONG DestStringID, IN PVOID SrcStringTable, IN LONG SrcStringID ) /*++ Routine Description: Gets a string from source string table, adds it to destination string table with new ID. Arguments: DestStringTable - Where string has to go DestStringID - pointer, set to string ID in respect to DestStringTable SrcStringTable - Where string is coming from StringID - string ID in respect to SrcStringTable Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { DWORD Err = NO_ERROR; LONG DestID; PTSTR String; if (DestStringID == NULL) { Err = ERROR_INVALID_HANDLE; goto clean0; } if (SrcStringID == -1) { // "not supplied" DestID = -1; } else { // actually need to copy String = pSetupStringTableStringFromId( SrcStringTable, SrcStringID ); if (String == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } DestID = pSetupStringTableAddString( DestStringTable, String, STRTAB_CASE_SENSITIVE ); if (DestID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } *DestStringID = DestID; } Err = NO_ERROR; clean0: SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupGetTargetByPath( IN HSPFILEQ QueueHandle, IN PVOID PathStringTable, OPTIONAL IN PCTSTR TargetPath, OPTIONAL IN LONG TargetRoot, IN LONG TargetSubDir, OPTIONAL IN LONG TargetFilename, OUT PLONG TableID, OPTIONAL OUT PSP_TARGET_ENT TargetInfo ) /*++ Routine Description: Given a pathname, obtains/creates target info Arguments: QueueHandle - Queue we're looking at PathStringTable - String table used for the Target Root/SubDir/Filename strings, NULL if same as QueueHandle's TargetPath - if given, is the full path, previously generated TargetRoot - root portion, eg c:\winnt TargetSubDir - optional sub-directory portion, -1 if not provided TargetFilename - filename , eg readme.txt TableID - filled with ID for future use in pSetupBackupGetTargetByID or pSetupBackupSetTargetByID TargetInfo - Filled with information about target Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { LONG PathID; TCHAR PathBuffer[MAX_PATH]; PTSTR TmpPtr; PVOID LookupTable = NULL; PVOID QueueStringTable = NULL; PTSTR FullTargetPath = NULL; DWORD Err = NO_ERROR; PSP_FILE_QUEUE Queue; DWORD RequiredSize; Queue = (PSP_FILE_QUEUE)QueueHandle; try { LookupTable = Queue->TargetLookupTable; // used for path lookup in source queue QueueStringTable = Queue->StringTable; // used for strings in source queue } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean0; } if (PathStringTable == NULL) { // default string table is that of queue's PathStringTable = QueueStringTable; } if (TargetPath == NULL) { // obtain the complete target path and filename (Duplicated String) FullTargetPath = pSetupFormFullPath( PathStringTable, TargetRoot, TargetSubDir, TargetFilename); if (!FullTargetPath) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } TargetPath = FullTargetPath; } // // normalize path // RequiredSize = GetFullPathName(TargetPath, SIZECHARS(PathBuffer), PathBuffer, &TmpPtr ); // // This call should always succeed. // MYASSERT((RequiredSize > 0) && (RequiredSize < SIZECHARS(PathBuffer)) // RequiredSize doesn't include terminating NULL char ); // // Even though we asserted that this should not be the case above, // we should handle failure in case asserts are turned off. // if(!RequiredSize) { Err = GetLastError(); goto clean0; } else if(RequiredSize >= SIZECHARS(PathBuffer)) { Err = ERROR_BUFFER_OVERFLOW; goto clean0; } PathID = pSetupStringTableLookUpStringEx(LookupTable, PathBuffer, 0, TargetInfo, sizeof(SP_TARGET_ENT)); if (PathID == -1) { ZeroMemory(TargetInfo, sizeof(SP_TARGET_ENT)); if (PathStringTable != QueueStringTable) { // need to add entries to Queue's string table if we're using another Err = pSetupBackupCopyString(QueueStringTable, &TargetRoot, PathStringTable, TargetRoot); if (Err != NO_ERROR) { goto clean0; } Err = pSetupBackupCopyString(QueueStringTable, &TargetSubDir, PathStringTable, TargetSubDir); if (Err != NO_ERROR) { goto clean0; } Err = pSetupBackupCopyString(QueueStringTable, &TargetFilename, PathStringTable, TargetFilename); if (Err != NO_ERROR) { goto clean0; } PathStringTable = QueueStringTable; } TargetInfo->TargetRoot = TargetRoot; TargetInfo->TargetSubDir = TargetSubDir; TargetInfo->TargetFilename = TargetFilename; TargetInfo->BackupRoot = -1; TargetInfo->BackupSubDir = -1; TargetInfo->BackupFilename = -1; TargetInfo->NewTargetFilename = -1; TargetInfo->InternalFlags = 0; PathID = pSetupStringTableAddStringEx(LookupTable, PathBuffer, 0, TargetInfo, sizeof(SP_TARGET_ENT)); if (PathID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } if (TableID != NULL) { *TableID = PathID; } Err = NO_ERROR; clean0: if (FullTargetPath != NULL) { MyFree(FullTargetPath); } SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupGetTargetByID( IN HSPFILEQ QueueHandle, IN LONG TableID, OUT PSP_TARGET_ENT TargetInfo ) /*++ Routine Description: Given an entry in the LookupTable, gets info Arguments: QueueHandle - Queue we're looking at TableID - ID relating to string entry we've found (via pSetupBackupGetTargetByPath) TargetInfo - Filled with information about target Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { PVOID LookupTable = NULL; DWORD Err = NO_ERROR; PSP_FILE_QUEUE Queue; Queue = (PSP_FILE_QUEUE)QueueHandle; try { LookupTable = Queue->TargetLookupTable; // used for strings in source queue } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean0; } if (pSetupStringTableGetExtraData(LookupTable, TableID, TargetInfo, sizeof(SP_TARGET_ENT)) == FALSE) { Err = ERROR_INVALID_HANDLE; goto clean0; } Err = NO_ERROR; clean0: SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupSetTargetByID( IN HSPFILEQ QueueHandle, IN LONG TableID, IN PSP_TARGET_ENT TargetInfo ) /*++ Routine Description: Given an entry in the LookupTable, sets info Arguments: QueueHandle - Queue we're looking at TableID - ID relating to string entry we've found (via pSetupBackupGetTargetByPath) TargetInfo - Filled with information about target Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { PVOID LookupTable = NULL; DWORD Err = NO_ERROR; PSP_FILE_QUEUE Queue; Queue = (PSP_FILE_QUEUE)QueueHandle; try { LookupTable = Queue->TargetLookupTable; // used for strings in source queue } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean0; } if ( pSetupStringTableSetExtraData(LookupTable, TableID, TargetInfo, sizeof(SP_TARGET_ENT)) == FALSE) { Err = ERROR_INVALID_HANDLE; goto clean0; } Err = NO_ERROR; clean0: SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupGetReinstallKeyStrings( IN PSP_FILE_QUEUE BackupFileQueue, IN HDEVINFO DeviceInfoSet, IN PSP_DEVINFO_DATA DeviceInfoData, IN PCTSTR DeviceID ) /*++ Routine Description: This routine will save the values needed to create the Reinstall backup key in the string table of the backup queue. We save these strings in the string table before the new drivers are installed and then create the registry key after the new device is installed. It is done this way because the Rollback UI code will look for the Reinstall subkeys, so we want to make sure that we have successfully backed-up all of the needed files before we create this Reinstall subkey. Arguments: BackupFileQueue - DeviceInfoSet - DeviceInfoData - DeviceID - Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { DWORD Err = NO_ERROR; HKEY hKeyDevReg = INVALID_HANDLE_VALUE; DWORD RegCreated, cbData; TCHAR Buffer[MAX_PATH]; try { // // Get the DeviceDesc of the device and fill in the BackupDevDescID and // BackupDisplayNameID values in the string table. This value is // required since it is needed during a rollback for us to choose the // exact driver that was installed from the specific INF. // if (!SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfoData, SPDRP_DEVICEDESC, NULL, (PBYTE)Buffer, sizeof(Buffer), NULL)) { Err = GetLastError(); goto clean0; } BackupFileQueue->BackupDeviceDescID = pSetupStringTableAddString(BackupFileQueue->StringTable, Buffer, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupDeviceDescID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // At this point we will also set the BackupDisplayNameID value just in // case the device does not have a FriendlyName. // BackupFileQueue->BackupDisplayNameID = pSetupStringTableAddString(BackupFileQueue->StringTable, Buffer, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupDisplayNameID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // We will try and get the device's FriendlyName. If it has one then we // will set the BackupDisplayNameID to this value, otherwise DisplayName // will just be the DeviceDesc. // if (SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)Buffer, sizeof(Buffer), NULL)) { BackupFileQueue->BackupDisplayNameID = pSetupStringTableAddString(BackupFileQueue->StringTable, Buffer, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupDisplayNameID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } // // Set the BackupMfgID value. // if (!SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfoData, SPDRP_MFG, NULL, (PBYTE)Buffer, sizeof(Buffer), NULL)) { Err = GetLastError(); goto clean0; } BackupFileQueue->BackupMfgID = pSetupStringTableAddString(BackupFileQueue->StringTable, Buffer, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupMfgID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // Set the BackupProviderNameID value. // hKeyDevReg = SetupDiOpenDevRegKey(DeviceInfoSet, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ ); if (hKeyDevReg == INVALID_HANDLE_VALUE) { goto clean0; } cbData = sizeof(Buffer); Err = RegQueryValueEx(hKeyDevReg, pszProviderName, NULL, NULL, (PBYTE)Buffer, &cbData ); RegCloseKey(hKeyDevReg); if (Err != ERROR_SUCCESS) { goto clean0; } BackupFileQueue->BackupProviderNameID = pSetupStringTableAddString(BackupFileQueue->StringTable, Buffer, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupProviderNameID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // Set the DeviceInstanceIds value. This is a multi-sz value so make // sure we put a double NULL on the end. // BackupFileQueue->BackupDeviceInstanceID = pSetupStringTableAddString(BackupFileQueue->StringTable, (PTSTR)DeviceID, STRTAB_CASE_SENSITIVE); if (BackupFileQueue->BackupDeviceInstanceID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } clean0: ; // Nothing to do. } except(EXCEPTION_EXECUTE_HANDLER) { // // if we except, assume it's due to invalid parameter // Err = ERROR_INVALID_PARAMETER; } SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupCreateReinstallKey( IN PSP_FILE_QUEUE BackupFileQueue ) /*++ Routine Description: This routine will create the needed reinstall registry key so that these drivers can be later rolled back. The reinstall registry key lives in the following location: HKLM\Software\Microsoft\Windows\CurrentVersion\Reinstall\xxxx where xxxx is the BackupInstanceId. Under this key we will store the following information DisplayName - This is the name that is displayed in any UI of drivers that can be reinstalled. This is normally just the device description. DeviceInstanceIds - Multi-sz string of the device instance Ids of every device that is using this backup. Setupapi only sets the first device instance Id. Newdev can append other device instance Ids to this list if it is doing multiple device installs (in the case of UpdateDriverForPlugAndPlayDevices or InstallWindowsUpdateDriver). ReinstallString - The full backup path including the INF file DeviceDesc - The DeviceDesc of the driver that was installed. This is needed to make sure we pick the identical driver during a roll back. Mfg - The Mfg of the driver that was installed. This is needed to make sure we pick the identical driver during a roll back. ProviderName - The ProviderName of the driver that was installed. This is needed to make sure we pick the identical driver during a roll back. Arguments: BackupFileQueue - Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { DWORD Err = NO_ERROR; HKEY hKeyReinstall = INVALID_HANDLE_VALUE; HKEY hKeyReinstallInstance = INVALID_HANDLE_VALUE; DWORD RegCreated, cbData; TCHAR Buffer[MAX_PATH]; BOOL b; try { // // Make sure the BackupInfID is valid. If it is -1 then something went // wrong during the backup and so we do not want to create the Reinstall // instance subkey. // if (BackupFileQueue->BackupInfID == -1) { Err = ERROR_NO_BACKUP; goto clean0; } // // Open/Create the Reinstall registry key. Call RegCreateKeyEx in case // this is the first time a backup is being performed and this key // does not yet exist. // Err = RegCreateKeyEx(HKEY_LOCAL_MACHINE, pszReinstallPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKeyReinstall, &RegCreated ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Create the Reinstall instance key under the Reinstall key. // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupInstanceID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegCreateKeyEx(hKeyReinstall, Buffer, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKeyReinstallInstance, &RegCreated ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Add the DeviceDesc to the Reinstall instance subkey // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupDeviceDescID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegSetValueEx(hKeyReinstallInstance, pszDeviceDesc, 0, REG_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 1) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Add the DisplayName to the Reinstall instance subkey // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupDisplayNameID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } RegSetValueEx(hKeyReinstallInstance, pszReinstallDisplayName, 0, REG_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 1) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Add the Mfg to the Reinstall instance subkey // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupMfgID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegSetValueEx(hKeyReinstallInstance, pszMfg, 0, REG_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 1) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Add the ProviderName to the Reinstall instance subkey // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupProviderNameID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegSetValueEx(hKeyReinstallInstance, pszProviderName, 0, REG_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 1) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Set the DeviceInstanceIds value. This is a multi-sz value so make // sure we put a double NULL on the end. // // // Add the Mfg to the Reinstall instance subkey // cbData = MAX_PATH; ZeroMemory(Buffer, sizeof(Buffer)); b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupDeviceInstanceID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegSetValueEx(hKeyReinstallInstance, pszReinstallDeviceInstanceIds, 0, REG_MULTI_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 2) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } // // Add the ReinstallString to the Reinstall instance subkey // cbData = MAX_PATH; b = pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupInfID, Buffer, &cbData); if (b == FALSE) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } Err = RegSetValueEx(hKeyReinstallInstance, pszReinstallString, 0, REG_SZ, (PBYTE)Buffer, (lstrlen(Buffer) + 1) * sizeof(TCHAR) ); if (Err != ERROR_SUCCESS) { goto clean0; } clean0: ; // Nothing to do. } except(EXCEPTION_EXECUTE_HANDLER) { // // if we except, assume it's due to invalid parameter // Err = ERROR_INVALID_PARAMETER; } if (hKeyReinstallInstance != INVALID_HANDLE_VALUE) { RegCloseKey(hKeyReinstallInstance); } if (hKeyReinstall != INVALID_HANDLE_VALUE) { RegCloseKey(hKeyReinstall); } SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupBackupAppendFiles( IN HSPFILEQ TargetQueueHandle, IN PCTSTR BackupSubDir, IN DWORD BackupFlags, IN HSPFILEQ SourceQueueHandle OPTIONAL ) /*++ Routine Description: This routine will take a list of files from SourceQueueHandle Copy sub-queue's These files will appear in the Target Queue's target cache And may be placed into the Target Backup Queue Typically the copy queue is entries of.. \\ copied to \ Arguments: TargetQueueHandle - Where Backups are queued to BackupSubDir - Directory to backup to, relative to backup root BackupFlags - How backup should occur SourceQueueHandle - Handle that has a series of copy operations (backup hint) created, say, by pretending to do the re-install If not specified, only flags are passed Return Value: Returns error code (LastError is also set) If the function succeeds, returns NO_ERROR --*/ { TCHAR BackupPath[MAX_PATH]; PSP_FILE_QUEUE SourceQueue = NULL; PSP_FILE_QUEUE TargetQueue = NULL; PSP_FILE_QUEUE_NODE QueueNode = NULL; PSOURCE_MEDIA_INFO SourceMediaInfo = NULL; BOOL b = TRUE; PVOID SourceStringTable = NULL; PVOID TargetStringTable = NULL; LONG BackupRootID = -1; DWORD Err = NO_ERROR; LONG PathID = -1; SP_TARGET_ENT TargetInfo; SourceQueue = (PSP_FILE_QUEUE)SourceQueueHandle; // optional TargetQueue = (PSP_FILE_QUEUE)TargetQueueHandle; b=TRUE; // set if we can skip this routine try { TargetStringTable = TargetQueue->StringTable; // used for strings in target queue if (SourceQueue == NULL) { b = TRUE; // nothing to do } else { SourceStringTable = SourceQueue->StringTable; // used for strings in source queue b = (!SourceQueue->CopyNodeCount); } } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean0; } // these are backup flags to be passed into the queue if (BackupFlags & SP_BKFLG_CALLBACK) { TargetQueue->Flags |= FQF_BACKUP_AWARE; } if (b) { // nothing to do goto clean0; } // // get full directory path of backup - this appears as the "dest" for any backup entries // if ( BackupSubDir == NULL ) { Err = ERROR_INVALID_HANDLE; goto clean0; } if ( pSetupGetFullBackupPath(BackupPath, BackupSubDir, MAX_PATH,NULL) == FALSE ) { Err = ERROR_INVALID_HANDLE; goto clean0; } // // Target will often use this, so we create the ID now instead of later // BackupRootID = pSetupStringTableAddString(TargetStringTable, BackupPath, STRTAB_CASE_SENSITIVE); if (BackupRootID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // CopyQueue is split over a number of media's // we're not (currently) bothered about media // iterate through all the copy sub-queue's // and (1) add them to the target lookup table // (2) if wanted, add them into the backup queue for (SourceMediaInfo=SourceQueue->SourceMediaList; SourceMediaInfo!=NULL ; SourceMediaInfo=SourceMediaInfo->Next) { if (!SourceMediaInfo->CopyNodeCount) { continue; } MYASSERT(SourceMediaInfo->CopyQueue); for (QueueNode = SourceMediaInfo->CopyQueue; QueueNode!=NULL; QueueNode = QueueNode->Next) { // for each "Copy" // we want information about the destination path // Err = pSetupBackupGetTargetByPath(TargetQueueHandle, SourceStringTable, NULL, // precalculated string QueueNode->TargetDirectory, -1, QueueNode->TargetFilename, &PathID, &TargetInfo); if (Err != NO_ERROR) { goto clean0; } // we now have a created (or obtained) TargetInfo, and PathID // provide a source name for backup TargetInfo.BackupRoot = BackupRootID; Err = pSetupBackupCopyString(TargetStringTable, &TargetInfo.BackupSubDir, SourceStringTable, QueueNode->SourcePath); if (Err != NO_ERROR) { goto clean0; } Err = pSetupBackupCopyString(TargetStringTable, &TargetInfo.BackupFilename, SourceStringTable, QueueNode->SourceFilename); if (Err != NO_ERROR) { goto clean0; } if ((BackupFlags & SP_BKFLG_LATEBACKUP) == FALSE) { // we need to add this item to the backup queue Err = pSetupQueueBackupCopy(TargetQueueHandle, // source TargetInfo.TargetRoot, TargetInfo.TargetSubDir, TargetInfo.TargetFilename, TargetInfo.BackupRoot, TargetInfo.BackupSubDir, TargetInfo.BackupFilename); if (Err != NO_ERROR) { goto clean0; } // flag that we've added it to the pre-copy backup sub-queue TargetInfo.InternalFlags |= SP_TEFLG_BACKUPQUEUE; } // any backups should go to this specified directory TargetInfo.InternalFlags |= SP_TEFLG_ORIGNAME; Err = pSetupBackupSetTargetByID(TargetQueueHandle, PathID, &TargetInfo); if (Err != NO_ERROR) { goto clean0; } } } Err = NO_ERROR; clean0: SetLastError(Err); return (Err); } // // ========================================================== // DWORD pSetupBackupFile( IN HSPFILEQ QueueHandle, IN PCTSTR TargetPath, IN PCTSTR BackupPath, IN LONG TargetID, OPTIONAL IN LONG TargetRootPath, IN LONG TargetSubDir, IN LONG TargetFilename, IN LONG BackupRootPath, IN LONG BackupSubDir, IN LONG BackupFilename, OUT BOOL *InUseFlag ) /*++ Routine Description: If BackupFilename not supplied, it is obtained/created Will either 1) copy a file to the backup directory, or 2) queue that a file is backed up on reboot The latter occurs if the file was locked. Arguments: HSPFILEQ - QueueHandle - specifies Queue LONG - TargetID - if specified (not -1), use for target LONG - TargetRootPath - used if TargetID == -1 LONG - TargetSubDir - used if TargetID == -1 LONG - TargetFilename - used if TargetID == -1 LONG - BackupRootPath - alternate root (valid if BackupFilename != -1) LONG - BackupSubDir - alternate directory (valid if BackupFilename != -1) LONG - BackupFilename - alternate filename Return Value: If the function succeeds, return value is TRUE If the function fails, return value is FALSE --*/ { PSP_FILE_QUEUE Queue = NULL; PVOID StringTable = NULL; PVOID LookupTable = NULL; DWORD Err = NO_ERROR; SP_TARGET_ENT TargetInfo; PTSTR FullTargetPath = NULL; PTSTR FullBackupPath = NULL; BOOL InUse = FALSE; PTSTR TempNamePtr = NULL, DirTruncPos; TCHAR TempPath[MAX_PATH]; TCHAR TempFilename[MAX_PATH]; TCHAR ParsedPath[MAX_PATH]; UINT OldMode; LONG NewTargetFilename; BOOL DoRename = FALSE; OldMode = SetErrorMode(SEM_FAILCRITICALERRORS); Queue = (PSP_FILE_QUEUE)QueueHandle; try { StringTable = Queue->StringTable; // used for strings in source queue } except (EXCEPTION_EXECUTE_HANDLER) { Err = ERROR_INVALID_HANDLE; goto clean1; } if(TargetPath == NULL && TargetID == -1) { if (TargetRootPath == -1 || TargetFilename == -1) { Err = ERROR_INVALID_HANDLE; goto clean0; } // complete target path FullTargetPath = pSetupFormFullPath( StringTable, TargetRootPath, TargetSubDir, TargetFilename ); if (!FullTargetPath) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } TargetPath = FullTargetPath; } if (TargetID == -1) { Err = pSetupBackupGetTargetByPath(QueueHandle, NULL, // string table TargetPath, // precalculated string TargetRootPath, TargetSubDir, TargetFilename, &TargetID, &TargetInfo); } else { Err = pSetupBackupGetTargetByID(QueueHandle, TargetID, &TargetInfo); } if(Err != NO_ERROR) { goto clean0; } // // if we're not interested in backing up (global flag) we can skip // but it's only safe to do so if we'd copy & then later throw the copy away on success // note that if FQF_DEVICE_BACKUP is set, we'll always backup // if (((TargetInfo.InternalFlags & SP_TEFLG_RENAMEEXISTING) == 0) && ((Queue->Flags & FQF_DEVICE_BACKUP)==0) && ((GlobalSetupFlags & PSPGF_NO_BACKUP)!=0)) { Err = NO_ERROR; goto clean0; } // // Figure out whether we've been asked to rename the existing file to a // temp name in the same directory, but haven't yet done so. // DoRename = ((TargetInfo.InternalFlags & (SP_TEFLG_RENAMEEXISTING | SP_TEFLG_MOVED)) == SP_TEFLG_RENAMEEXISTING); if(BackupFilename == -1) { // // non-specific backup // if((TargetInfo.InternalFlags & SP_TEFLG_SAVED) && !DoRename) { // // Already backed up, and we don't need to rename the existing file. // Nothing to do. // Err = NO_ERROR; goto clean0; } if(TargetInfo.InternalFlags & SP_TEFLG_INUSE) { // // Previously marked as INUSE, not allowed to change it. If we // were asked to rename the existing file, then we need to return // failure, otherwise, we can report success. // // InUse = TRUE; Err = DoRename ? ERROR_SHARING_VIOLATION : NO_ERROR; goto clean0; } if(TargetInfo.InternalFlags & SP_TEFLG_ORIGNAME) { // // original name given, use that // BackupRootPath = TargetInfo.BackupRoot; BackupSubDir = TargetInfo.BackupSubDir; BackupFilename = TargetInfo.BackupFilename; } } else { // // We should never be called if the file has already been // saved. // MYASSERT(!(TargetInfo.InternalFlags & SP_TEFLG_SAVED)); // // Even if the above assert fires, we should still deal with // the case where this occurs. Also, we should deal with the // case where a backup was previously attempted but failed due // to the file being in-use. // if(TargetInfo.InternalFlags & SP_TEFLG_SAVED) { // // nothing to do, we shouldn't treat this as an actual error // Err = NO_ERROR; goto clean0; } else if(TargetInfo.InternalFlags & SP_TEFLG_INUSE) { // // force the issue of InUse // InUse = TRUE; Err = ERROR_SHARING_VIOLATION; goto clean0; } TargetInfo.BackupRoot = BackupRootPath; TargetInfo.BackupSubDir = BackupSubDir; TargetInfo.BackupFilename = BackupFilename; TargetInfo.InternalFlags |= SP_TEFLG_ORIGNAME; TargetInfo.InternalFlags &= ~SP_TEFLG_TEMPNAME; } if(TargetPath == NULL) { // // must have looked up using TargetID, use TargetInfo to generate TargetPath // complete target path // FullTargetPath = pSetupFormFullPath(StringTable, TargetInfo.TargetRoot, TargetInfo.TargetSubDir, TargetInfo.TargetFilename ); if(!FullTargetPath) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } TargetPath = FullTargetPath; } if(DoRename) { // // We'd better not already have a temp filename stored in our TargetInfo. // MYASSERT(TargetInfo.NewTargetFilename == -1); // // First, strip the filename off the path. // _tcscpy(TempPath, TargetPath); TempNamePtr = (PTSTR)pSetupGetFileTitle(TempPath); *TempNamePtr = TEXT('\0'); // // Now get a temp filename within that directory... // if(GetTempFileName(TempPath, TEXT("OLD"), 0, TempFilename) == 0 ) { Err = GetLastError(); goto clean0; } // // ...and store this path's string ID in our TargetInfo // NewTargetFilename = pSetupStringTableAddString(StringTable, TempFilename, STRTAB_CASE_SENSITIVE ); if(NewTargetFilename == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } if(!(TargetInfo.InternalFlags & (SP_TEFLG_ORIGNAME | SP_TEFLG_TEMPNAME))) { // // If we don't yet have a name to use in backing up this file, then // generate one now. If we are doing a rename, we can use that name. // if(DoRename) { // // Make sure that all flags agree on the fact that we need to back // up this file. // MYASSERT(!(TargetInfo.InternalFlags & SP_TEFLG_SAVED)); // // Temp filename was stored in TempFilename buffer above. // TempNamePtr = (PTSTR)pSetupGetFileTitle(TempFilename); BackupFilename = pSetupStringTableAddString(StringTable, TempNamePtr, STRTAB_CASE_SENSITIVE); if(BackupFilename == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } DirTruncPos = CharPrev(TempFilename, TempNamePtr); // // (We know pSetupGetFileTitle will never return a pointer to a path // separator character, so the following check is valid.) // if(*DirTruncPos == TEXT('\\')) { // // If this is in a root directory (e.g., "A:\"), then we don't want to strip off // the trailing backslash. // if(((DirTruncPos - TempFilename) != 2) || (*CharNext(TempFilename) != TEXT(':'))) { TempNamePtr = DirTruncPos; } } lstrcpyn(TempPath, TempFilename, (int)(TempNamePtr - TempFilename) + 1); BackupRootPath = pSetupStringTableAddString(StringTable, TempPath, STRTAB_CASE_SENSITIVE); if(BackupRootPath == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } else { // // specify "NULL" as the sub-directory, since all we want is a temporary location // if(pSetupGetFullBackupPath(TempPath, NULL, MAX_PATH,NULL) == FALSE ) { Err = ERROR_INVALID_HANDLE; goto clean0; } _tcscpy(TempFilename,TempPath); // // Note: In the code below, we employ a "trick" to get the // pSetupMakeSurePathExists API to make sure that a directory // exists. Since we don't yet know the filename (we need to call // GetTempFileName against an existing directory to find that out), // we just use a dummy placeholder filename ("OLD") so that it can // be discarded by the pSetupMakeSurePathExists API. // if(pSetupConcatenatePaths(TempFilename, TEXT("OLD"), MAX_PATH, NULL) == FALSE ) { Err = GetLastError(); goto clean0; } pSetupMakeSurePathExists(TempFilename); if(GetTempFileName(TempPath, TEXT("OLD"), 0, TempFilename) == 0 ) { Err = GetLastError(); goto clean0; } TempNamePtr = TempFilename + _tcslen(TempPath) + 1 /* 1 to skip past \ */; BackupRootPath = pSetupStringTableAddString( StringTable, TempPath, STRTAB_CASE_SENSITIVE ); if(BackupRootPath == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } BackupFilename = pSetupStringTableAddString( StringTable, TempNamePtr, STRTAB_CASE_SENSITIVE ); if(BackupFilename == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } BackupPath = TempFilename; TargetInfo.BackupRoot = BackupRootPath; TargetInfo.BackupSubDir = BackupSubDir = -1; TargetInfo.BackupFilename = BackupFilename; TargetInfo.InternalFlags |= SP_TEFLG_TEMPNAME; } if(BackupPath == NULL) { // // make a complete path from this source // FullBackupPath = pSetupFormFullPath(StringTable, BackupRootPath, BackupSubDir, BackupFilename ); if (!FullBackupPath) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } BackupPath = FullBackupPath; } // // If we need to make a copy of the existing file, do so now. // if(!DoRename || (TargetInfo.InternalFlags & SP_TEFLG_ORIGNAME)) { SetFileAttributes(BackupPath, FILE_ATTRIBUTE_NORMAL); pSetupMakeSurePathExists(BackupPath); Err = CopyFile(TargetPath, BackupPath, FALSE) ? NO_ERROR : GetLastError(); if(Err == NO_ERROR) { TargetInfo.InternalFlags |= SP_TEFLG_SAVED; } else { // // Delete placeholder file created by GetTempFileName. // SetFileAttributes(BackupPath, FILE_ATTRIBUTE_NORMAL); DeleteFile(BackupPath); if(Err == ERROR_SHARING_VIOLATION) { // // Unless we were also going to attempt a rename, don't // consider sharing violations to be errors. // InUse = TRUE; TargetInfo.InternalFlags |= SP_TEFLG_INUSE; if(!DoRename) { Err = NO_ERROR; } } } } // // OK, now rename the existing file, if necessary. // if(DoRename && (Err == NO_ERROR)) { if(DoMove(TargetPath, TempFilename)) { TargetInfo.InternalFlags |= SP_TEFLG_MOVED; TargetInfo.NewTargetFilename = NewTargetFilename; // // Post a delayed deletion for this temp filename so it'll get // cleaned up after reboot. // if(!PostDelayedMove(Queue, TempFilename, NULL, -1, FALSE)) { // // Don't abort just because we couldn't schedule a delayed // delete. If this fails, the only bad thing that will happen // is a turd will get left over after reboot. // // We should log an event about this, however. // Err = GetLastError(); WriteLogEntry(Queue->LogContext, SETUP_LOG_WARNING | SETUP_LOG_BUFFER, MSG_LOG_RENAME_EXISTING_DELAYED_DELETE_FAILED, NULL, TargetPath, TempFilename ); WriteLogError(Queue->LogContext, SETUP_LOG_WARNING, Err ); Err = NO_ERROR; } } else { Err = GetLastError(); SetFileAttributes(TempFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(TempFilename); if(Err == ERROR_SHARING_VIOLATION) { InUse = TRUE; TargetInfo.InternalFlags |= SP_TEFLG_INUSE; } } } // // update internal info (this call should never fail) // pSetupBackupSetTargetByID(QueueHandle, TargetID, &TargetInfo ); clean0: if (FullTargetPath != NULL) { MyFree(FullTargetPath); } if (FullBackupPath != NULL) { MyFree(FullBackupPath); } if (Err != NO_ERROR) { // // note the fact that at least one backup error has occurred // Queue->Flags |= FQF_BACKUP_INCOMPLETE; } clean1: SetErrorMode(OldMode); SetLastError(Err); if(InUseFlag) { *InUseFlag = InUse; } return Err; } // // ========================================================== // VOID pSetupDeleteBackup( IN PCTSTR BackupInstance ) /*++ Routine Description: This function will delete an entire backup instance. This entails deleting the relative BackupInstance out of the registry Reinstall key as well Arguments: BackupInstance - Instance Id of the backup Return Value: If the function succeeds, return value is TRUE If the function fails, return value is FALSE --*/ { TCHAR Buffer[MAX_PATH]; HKEY hKeyReinstall = INVALID_HANDLE_VALUE; if (BackupInstance == NULL) { return; } // // Delete this instance from the Reinstall key. // if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszReinstallPath, 0, KEY_ALL_ACCESS, &hKeyReinstall ) == ERROR_SUCCESS) { RegDeleteKey(hKeyReinstall, BackupInstance); RegCloseKey(hKeyReinstall); } // // Now delete the entire backup directory. // if (pSetupGetFullBackupPath(Buffer, BackupInstance, MAX_PATH, NULL)) { pRemoveDirectory(Buffer); } } // // ========================================================== // DWORD pSetupGetCurrentlyInstalledDriverNode( IN HDEVINFO DeviceInfoSet, IN OUT PSP_DEVINFO_DATA DeviceInfoData ) /*++ Routine Description: Get driver node that relates to current INF file of device Arguments: DeviceInfoSet DeviceInfoData Return Value: Error Status --*/ { DWORD Err; SP_DEVINSTALL_PARAMS DeviceInstallParams; SP_DRVINFO_DATA DriverInfoData; ZeroMemory(&DeviceInstallParams, sizeof(DeviceInstallParams)); ZeroMemory(&DriverInfoData, sizeof(DriverInfoData)); DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA); DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); if(!SetupDiGetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams)) { return GetLastError(); } // // Set the flags that tell SetupDiBuildDriverInfoList to just put the currently installed // driver node in the list, and that it should allow excluded drivers. // DeviceInstallParams.FlagsEx |= (DI_FLAGSEX_INSTALLEDDRIVER | DI_FLAGSEX_ALLOWEXCLUDEDDRVS); if(!SetupDiSetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams)) { Err = GetLastError(); goto clean0; } // // Now build a class driver list that just contains the currently installed driver. // if(!SetupDiBuildDriverInfoList(DeviceInfoSet, DeviceInfoData, SPDIT_CLASSDRIVER)) { Err = GetLastError(); goto clean0; } // // The only driver in the list should be the currently installed driver, if there // is a currently installed driver. // if (!SetupDiEnumDriverInfo(DeviceInfoSet, DeviceInfoData, SPDIT_CLASSDRIVER, 0, &DriverInfoData)) { Err = GetLastError(); goto clean0; } // // Make the currently installed driver the selected driver. // if(!SetupDiSetSelectedDriver(DeviceInfoSet, DeviceInfoData, &DriverInfoData)) { Err = GetLastError(); goto clean0; } // // At this point, we've successfully selected the currently installed driver for the specified // device information element. We're done! // Err = NO_ERROR; clean0: SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupGetBackupQueue( IN PCTSTR DeviceID, IN OUT HSPFILEQ FileQueue, IN DWORD BackupFlags ) /*++ Routine Description: Creates a backup Queue for current device (DeviceID) Also makes sure that the INF file is backed up Arguments: DeviceID String ID of device FileQueue Backup queue is filled with files that need copying BackupFlags Various flags Return Value: Error Status --*/ { // // we want to obtain a copy/move list of device associated with DeviceID // // PSP_FILE_QUEUE FileQ = (PSP_FILE_QUEUE)FileQueue; HDEVINFO TempInfoSet = (HDEVINFO)INVALID_HANDLE_VALUE; HSPFILEQ TempQueueHandle = (HSPFILEQ)INVALID_HANDLE_VALUE; SP_DEVINFO_DATA TempInfoData; SP_DEVINSTALL_PARAMS TempParams; TCHAR SubDir[MAX_PATH]; LONG Instance; PDEVINFO_ELEM DevInfoElem = NULL; PTSTR szInfFileName = NULL; PTSTR szInfFileNameExt = NULL; PTSTR BackupPathExt = NULL; TCHAR BackupInstance[MAX_PATH]; TCHAR BackupPath[MAX_PATH]; TCHAR ReinstallString[MAX_PATH]; TCHAR OemOrigName[MAX_PATH]; TCHAR CatBackupPath[MAX_PATH]; TCHAR CatSourcePath[MAX_PATH]; DWORD Err; PDEVICE_INFO_SET pDeviceInfoSet = NULL; int InstanceId; DWORD BackupInfID = -1; DWORD BackupInstanceID = -1; PSP_INF_INFORMATION pInfInformation = NULL; DWORD InfInformationSize; SP_ORIGINAL_FILE_INFO InfOriginalFileInformation; BOOL success; PSETUP_LOG_CONTEXT SavedLogContext = NULL; PSETUP_LOG_CONTEXT LocalLogContext = NULL; BOOL ChangedThreadLogContext = FALSE; // // if backup information exists, abort (no flags will be set) // if(FileQ->BackupInfID != -1) { return ERROR_ALREADY_EXISTS; } // // detach any backup related logging from current log section // putting it into it's own section // this stops confusion when debugging (v)verbose logs // and we're going down this path // CreateLogContext(NULL,FALSE,&LocalLogContext); if(LocalLogContext) { DWORD LogTag = AllocLogInfoSlot(LocalLogContext,TRUE); if(LogTag) { WriteLogEntry(LocalLogContext, LogTag, MSG_LOG_DRIVERBACKUP, NULL, DeviceID ); } } ChangedThreadLogContext = SetThreadLogContext(LocalLogContext,&SavedLogContext); CatBackupPath[0] = 0; // by default, don't bother with a catalog CatSourcePath[0] = 0; // pretend we're installing old INF // this gives us a list of files we need TempInfoSet = SetupDiCreateDeviceInfoList(NULL, NULL); if ( TempInfoSet == (HDEVINFO)INVALID_HANDLE_VALUE ) { Err = GetLastError(); goto clean0; } if(!(pDeviceInfoSet = AccessDeviceInfoSet(TempInfoSet))) { Err = ERROR_INVALID_HANDLE; goto clean0; } // // make sure info-set has our local context // InheritLogContext(LocalLogContext,&pDeviceInfoSet->InstallParamBlock.LogContext); // // Open the driver info, related to DeviceID I was given // TempInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if ( SetupDiOpenDeviceInfo(TempInfoSet ,DeviceID, NULL, 0, &TempInfoData) == FALSE ) { Err = GetLastError(); goto clean0; } // // make sure the temporary element has our backup logging context // DevInfoElem = FindAssociatedDevInfoElem(TempInfoSet, &TempInfoData, NULL); MYASSERT(DevInfoElem); if(DevInfoElem) { InheritLogContext(LocalLogContext,&DevInfoElem->InstallParamBlock.LogContext); } // // Get the currently-installed driver node selected for this element. // if ( pSetupGetCurrentlyInstalledDriverNode(TempInfoSet, &TempInfoData) != NO_ERROR ) { Err = GetLastError(); goto clean0; } // // Now queue all files to be copied by this driver node into our own file queue. // it'll inherit the backup logging context // TempQueueHandle = SetupOpenFileQueue(); if ( TempQueueHandle == (HSPFILEQ)INVALID_HANDLE_VALUE ) { // // SetupOpenFileQueue modified to return error // Err = GetLastError(); goto clean0; } TempParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); if ( !SetupDiGetDeviceInstallParams(TempInfoSet, &TempInfoData, &TempParams) ) { Err = GetLastError(); goto clean0; } TempParams.FileQueue = TempQueueHandle; TempParams.Flags |= DI_NOVCP; if ( !SetupDiSetDeviceInstallParams(TempInfoSet, &TempInfoData, &TempParams) ) { Err = GetLastError(); goto clean0; } if ( !SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, TempInfoSet, &TempInfoData) ) { Err = GetLastError(); goto clean0; } // // We want this backup to be in a unique directory. To do this we just do // the standard instance number trick where we enumerate from 0000 to 9999 // and choose the first number where there isn't a backup directory with // that name already created. // for (InstanceId=0; InstanceId<=9999; InstanceId++) { wsprintf(SubDir, TEXT("\\%04d\\%s"), (LONG) InstanceId, (PCTSTR) SP_BACKUP_DRIVERFILES ); if ( pSetupGetFullBackupPath(BackupPath, SubDir, MAX_PATH, NULL) == FALSE ) { Err = ERROR_INVALID_HANDLE; goto clean0; } // // If this backup path does not exist then we have a valid directory. // if (!FileExists(BackupPath, NULL)) { break; } } if (InstanceId <= 9999) { // // Add string indicating Backup InstanceID to file Queue // for later retrieval // wsprintf(BackupInstance, TEXT("%04d"), (LONG) InstanceId); BackupInstanceID = pSetupStringTableAddString(FileQ->StringTable, BackupInstance, STRTAB_CASE_SENSITIVE); if (BackupInstanceID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } else { // // If we don't have any free backup directories then we will fail. There // should never be this many drivers backed up so this shouldn't be a // problem. // Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // get the path of the INF file, we will need to back it up // if(!(DevInfoElem = FindAssociatedDevInfoElem(pDeviceInfoSet, &TempInfoData, NULL))) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } szInfFileName = pStringTableStringFromId(pDeviceInfoSet->StringTable, DevInfoElem->SelectedDriver->InfFileName ); if (szInfFileName == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // we want to get the "real" name of the INF - we may have a precompiled inf // ZeroMemory(&InfOriginalFileInformation, sizeof(InfOriginalFileInformation)); // // if nothing else, use same name as is in the INF directory // lstrcpy(OemOrigName,pSetupGetFileTitle(szInfFileName)); // // but use the original name if available // InfInformationSize = 8192; // I'd rather have this too big and succeed first time, than read the INF twice pInfInformation = (PSP_INF_INFORMATION)MyMalloc(InfInformationSize); if (pInfInformation != NULL) { success = SetupGetInfInformation(szInfFileName,INFINFO_INF_NAME_IS_ABSOLUTE,pInfInformation,InfInformationSize,&InfInformationSize); if (!success && GetLastError()==ERROR_INSUFFICIENT_BUFFER) { PVOID newbuff = MyRealloc(pInfInformation,InfInformationSize); if (!newbuff) { MyFree(pInfInformation); pInfInformation = NULL; } else { pInfInformation = (PSP_INF_INFORMATION)newbuff; success = SetupGetInfInformation(szInfFileName,INFINFO_INF_NAME_IS_ABSOLUTE,pInfInformation,InfInformationSize,&InfInformationSize); } } if (success) { InfOriginalFileInformation.cbSize = sizeof(InfOriginalFileInformation); if (SetupQueryInfOriginalFileInformation(pInfInformation,0,NULL,&InfOriginalFileInformation)) { if (InfOriginalFileInformation.OriginalInfName[0]) { // // we have a "real" inf name // lstrcpy(OemOrigName,pSetupGetFileTitle(InfOriginalFileInformation.OriginalInfName)); } else { MYASSERT(InfOriginalFileInformation.OriginalInfName[0]); } // // Don't bother finding out about the INF's catalog if we're in // "minimal embedded" mode... // if(!(GlobalSetupFlags & PSPGF_MINIMAL_EMBEDDED)) { if (InfOriginalFileInformation.OriginalCatalogName[0]) { TCHAR CurrentCatName[MAX_PATH]; // // given that the file is ....\OEMx.INF the catalog is "OEMx.CAT" // we key off OemOrigName (eg mydisk.inf ) // and we won't bother copying the catalog if we can't verify the inf // lstrcpy(CurrentCatName,pSetupGetFileTitle(szInfFileName)); lstrcpy(_tcsrchr(CurrentCatName, TEXT('.')), pszCatSuffix); // // we have a catalog name // now consider making a copy of the cached catalog into our backup // we get out CatProblem and szCatFileName // // if all seems ok, copy file from szCatFileName to backupdir\OriginalCatalogName // Err = _VerifyFile( FileQ->LogContext, &(FileQ->hCatAdmin), NULL, CurrentCatName, // eg "OEMx.CAT" NULL,0, // we're not verifying against another catalog image OemOrigName, // eg "mydisk.inf" szInfFileName, // eg "....\OEMx.INF" NULL, // return: problem info NULL, // return: problem file FALSE, // has to be FALSE because we're getting full path ((PSP_FILE_QUEUE)TempQueueHandle)->ValidationPlatform, // alt platform info VERIFY_FILE_IGNORE_SELFSIGNED | VERIFY_FILE_NO_DRIVERBLOCKED_CHECK, CatSourcePath, // return: catalog file, full path NULL, // return: number of catalogs considered NULL, NULL ); if (Err == NO_ERROR && CatSourcePath[0]) { // // we have a catalog file of interest to copy // lstrcpy(CatBackupPath,BackupPath); if (!pSetupConcatenatePaths(CatBackupPath, InfOriginalFileInformation.OriginalCatalogName, MAX_PATH, NULL)) { // // non-fatal // CatSourcePath[0]=0; CatBackupPath[0]=0; } } } } } } if (pInfInformation != NULL) { MyFree(pInfInformation); pInfInformation = NULL; } } if ( pSetupConcatenatePaths(BackupPath, OemOrigName, MAX_PATH, NULL) == FALSE ) { Err = ERROR_INVALID_HANDLE; goto clean0; } pSetupMakeSurePathExists(BackupPath); SetFileAttributes(BackupPath,FILE_ATTRIBUTE_NORMAL); Err = CopyFile(szInfFileName, BackupPath ,FALSE) ? NO_ERROR : GetLastError(); if (Err != NO_ERROR) { goto clean0; } if(CatSourcePath[0] && CatBackupPath[0]) { // // if we copied Inf file, try to copy catalog file // if we don't succeed, don't consider this a fatal error // SetFileAttributes(CatBackupPath,FILE_ATTRIBUTE_NORMAL); CopyFile(CatSourcePath, CatBackupPath ,FALSE); } // // Add string indicating Backup INF location to file Queue // for later retrieval // BackupInfID = pSetupStringTableAddString(FileQ->StringTable, BackupPath, STRTAB_CASE_SENSITIVE); if (BackupInfID == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } // // Save the full inf backup path since we need to put it in the registry // below. // lstrcpy(ReinstallString, BackupPath); // // Also backup the PNF file // // WARNING: We reuse the szInfFileName and BackupPath variables at this point // so if you add code that needs them then add it above. // szInfFileNameExt = _tcsrchr(szInfFileName,TEXT('.')); MYASSERT(szInfFileNameExt); BackupPathExt = _tcsrchr(BackupPath,TEXT('.')); MYASSERT(BackupPathExt); if (szInfFileNameExt && BackupPathExt) { lstrcpy(szInfFileNameExt,pszPnfSuffix); lstrcpy(BackupPathExt,pszPnfSuffix); SetFileAttributes(BackupPath,FILE_ATTRIBUTE_NORMAL); CopyFile(szInfFileName, BackupPath, FALSE); } // // add items we may need to backup // (ie the copy queue of TempQueueHandle is converted to a backup queue of FileQueue) // if ( pSetupBackupAppendFiles(FileQueue, SubDir, BackupFlags, TempQueueHandle) != NO_ERROR ) { Err = GetLastError(); goto clean0; } // // Create the Reinstall registry key that points to the backup directory we // just made. // if (pSetupBackupGetReinstallKeyStrings(FileQ, TempInfoSet, &TempInfoData, DeviceID ) != NO_ERROR) { Err = GetLastError(); goto clean0; } Err = NO_ERROR; // // update BackupInfID so that INF name can be queried later. We will set // these values at the very end since the fact that FileQ->BackupInfID is // not -1 means the backup initialization has been successful. // FileQ->BackupInfID = BackupInfID; FileQ->BackupInstanceID = BackupInstanceID; // // Set the FQF_DEVICE_BACKUP flag so that we know this is a device install // backup. // FileQ->Flags |= FQF_DEVICE_BACKUP; clean0: // // If we encountered an error during our backup initialization then we want // to clean out the backup directory and Reinstall subkey. // if ((Err != NO_ERROR) && (BackupInstanceID != -1)) { if (pSetupStringTableStringFromIdEx(FileQ->StringTable, BackupInstanceID, BackupInstance, NULL)) { pSetupDeleteBackup(BackupInstance); } } // // delete temporary structures used // if (pDeviceInfoSet != NULL ) { UnlockDeviceInfoSet(pDeviceInfoSet); } if ( TempInfoSet != (HDEVINFO)INVALID_HANDLE_VALUE ) { SetupDiDestroyDeviceInfoList(TempInfoSet); } if ( TempQueueHandle != (HSPFILEQ)INVALID_HANDLE_VALUE ) { SetupCloseFileQueue(TempQueueHandle); } if(ChangedThreadLogContext) { // // restore thread log context if we changed (cleared) it // SetThreadLogContext(SavedLogContext,NULL); } DeleteLogContext(LocalLogContext); SetLastError(Err); return Err; } // // ========================================================== // DWORD pSetupCompleteBackup( IN OUT HSPFILEQ FileQueue ) /*++ Routine Description: This routine is called after we have successfully finished installing a device. At this point the new backup that we have created is valid and so any old backups that this device was using need to be removed. To do this the code will enumerate all of the backup instances under the Reinstall key and scan their DeviceInstanceIds multi-sz value. If it finds this DeviceInstanceId in the list then it will remove it. If the list is empty after this removal then the entire backup instance and its cooresponding backup directory will be deleted. Arguments: FileQueue Backup queue is filled with files that need copying Return Value: Error Status --*/ { PSP_FILE_QUEUE BackupFileQueue = (PSP_FILE_QUEUE)FileQueue; HKEY hKeyReinstall; HKEY hKeyReinstallInstance; DWORD Index; TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN]; TCHAR ReinstallInstance[MAX_PATH]; FILETIME ftLastWriteTime; DWORD cbData, cbInstanceSize; BOOL bDeleteBackupInstance; DWORD Err = NO_ERROR; PTSTR DeviceInstanceIdsList, p; try { // // If we don't have a BackupInfID then the backup failed, so don't bother // cleaning out the old backup information or creating the new Reinstall // instance key. // if (BackupFileQueue->BackupInfID == -1) { Err = ERROR_NO_BACKUP; goto clean0; } // // Get the Device Instance Id from the backup queue. This value must be // cleaned out from all other Reinstall Instance keys. // cbData = MAX_PATH; if (!pSetupStringTableStringFromIdEx(BackupFileQueue->StringTable, BackupFileQueue->BackupDeviceInstanceID, DeviceInstanceId, &cbData)) { if (cbData == 0) { Err = ERROR_NO_BACKUP; } else { Err = ERROR_INSUFFICIENT_BUFFER; } goto clean0; } // // Open the Reinstall key so we can enumerate all of the instance subkeys. // Err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszReinstallPath, 0, KEY_ALL_ACCESS, &hKeyReinstall ); if (Err == ERROR_SUCCESS) { cbInstanceSize = sizeof(ReinstallInstance) / sizeof(TCHAR); Index = 0; while (RegEnumKeyEx(hKeyReinstall, Index++, ReinstallInstance, &cbInstanceSize, NULL, NULL, NULL, &ftLastWriteTime ) == ERROR_SUCCESS) { // // Assume that we don't need to delete this backup instance // bDeleteBackupInstance = FALSE; Err = RegOpenKeyEx(hKeyReinstall, ReinstallInstance, 0, KEY_ALL_ACCESS, &hKeyReinstallInstance ); if (Err == ERROR_SUCCESS) { cbData = 0; if ((RegQueryValueEx(hKeyReinstallInstance, pszReinstallDeviceInstanceIds, NULL, NULL, NULL, &cbData ) == ERROR_SUCCESS) && (cbData)) { DeviceInstanceIdsList = MyMalloc(cbData + sizeof(TCHAR)); if (DeviceInstanceIdsList) { if (RegQueryValueEx(hKeyReinstallInstance, pszReinstallDeviceInstanceIds, NULL, NULL, (LPBYTE)DeviceInstanceIdsList, &cbData) == ERROR_SUCCESS) { // // Walk the list of DeviceInstanceIds and check for // a match with our device. // for (p = DeviceInstanceIdsList; *p; p += (lstrlen(p) + 1)) { if (lstrcmpi(p, DeviceInstanceId) == 0) { // // We have a match! First we will check to // see if this is the only DeviceInstanceId // in the list. To do this take the length of // the string and add two for the two terminating // NULLs of a multi-sz string and compare that // to cbData. If it is the same as (or larger // than) cbData then this is the only string // in the multi-sz list. // if ((p == DeviceInstanceIdsList) && (((lstrlen(DeviceInstanceIdsList) + 2) * sizeof(TCHAR)) >= cbData)) { // // Since there is only this one DeviceInstanceId // in the list set bDeleteBackupInstance to TRUE // so we can delete this entire subkey along // with the files that are backed up for it. // bDeleteBackupInstance = TRUE; } else { // // Since there is more than this DeviceInstanceId // in the list we need to remove just this // one Id from the multi-sz string and // put the new multi-sz string back // into the registry. // DWORD pLength = lstrlen(p); PTSTR p2 = p + (pLength + 1); memcpy(p, p2, cbData - ((ULONG_PTR)p2 - (ULONG_PTR)DeviceInstanceIdsList)); RegSetValueEx(hKeyReinstallInstance, pszReinstallDeviceInstanceIds, 0, REG_MULTI_SZ, (PBYTE)DeviceInstanceIdsList, cbData - ((pLength + 1) * sizeof(TCHAR)) ); } break; } } } MyFree(DeviceInstanceIdsList); } } RegCloseKey(hKeyReinstallInstance); // // If this entire subkey and it's corresponding directory need // to be deleted then do it now. // if (bDeleteBackupInstance) { pSetupDeleteBackup(ReinstallInstance); } } // // Need to update the cbInstanceSize variable before calling // RegEnumKeyEx again. // cbInstanceSize = sizeof(ReinstallInstance) / sizeof(TCHAR); } RegCloseKey(hKeyReinstall); } // // Create the new Reinstall instance backup subkey. // Err = pSetupBackupCreateReinstallKey(BackupFileQueue); clean0: ; // Nothing to do. } except(EXCEPTION_EXECUTE_HANDLER) { // // if we except, assume it's due to invalid parameter // Err = ERROR_INVALID_PARAMETER; } SetLastError(Err); return Err; } // // ========================================================== // VOID pSetupCleanupBackup( IN PSP_FILE_QUEUE Queue ) /*++ Routine Description: This routine is called to delete any backup directories or registry entries associated with this queue. Arguments: Queue file queue Return Value: VOID --*/ { TCHAR BackupInstance[MAX_PATH]; DWORD cbData; // // If we don't have a BackupInfID or a BackupInstanceID then the backup // must have failed much earlier. If the backup failed then it would have // cleaned itself up so there is no cleanup that needs to be done now. // if ((Queue->BackupInfID == -1) || (Queue->BackupInstanceID == -1)) { return; } // // Get the Backup Instance from the backup queue. // cbData = MAX_PATH; if (pSetupStringTableStringFromIdEx(Queue->StringTable, Queue->BackupInstanceID, BackupInstance, &cbData)) { pSetupDeleteBackup(BackupInstance); } } // // ========================================================== // BOOL PostDelayedMove( IN PSP_FILE_QUEUE Queue, IN PCTSTR CurrentName, IN PCTSTR NewName, OPTIONAL IN DWORD SecurityDesc, IN BOOL TargetIsProtected ) /*++ Routine Description: Helper for DelayedMove We don't do any delayed Moves until we know all else succeeded Arguments: Queue Queue that the move is applied to CurrentName Name of file we want to move NewName Name we want to move to SecurityDesc Index in string table of Security Descriptor string or -1 if not present TargetIsProtected Indicates whether target file is a protected system file Return Value: FALSE if error --*/ { PSP_DELAYMOVE_NODE DelayMoveNode; LONG SourceFilename; LONG TargetFilename; DWORD Err; if (CurrentName == NULL) { SourceFilename = -1; } else { SourceFilename = pSetupStringTableAddString(Queue->StringTable, (PTSTR)CurrentName, STRTAB_CASE_SENSITIVE ); if (SourceFilename == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } if (NewName == NULL) { TargetFilename = -1; } else { TargetFilename = pSetupStringTableAddString(Queue->StringTable, (PTSTR)NewName, STRTAB_CASE_SENSITIVE ); if (TargetFilename == -1) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } } DelayMoveNode = MyMalloc(sizeof(SP_DELAYMOVE_NODE)); if (DelayMoveNode == NULL) { Err = ERROR_NOT_ENOUGH_MEMORY; goto clean0; } DelayMoveNode->NextNode = NULL; DelayMoveNode->SourceFilename = SourceFilename; DelayMoveNode->TargetFilename = TargetFilename; DelayMoveNode->SecurityDesc = SecurityDesc; DelayMoveNode->TargetIsProtected = TargetIsProtected; if (Queue->DelayMoveQueueTail == NULL) { Queue->DelayMoveQueue = DelayMoveNode; } else { Queue->DelayMoveQueueTail->NextNode = DelayMoveNode; } Queue->DelayMoveQueueTail = DelayMoveNode; Err = NO_ERROR; clean0: SetLastError(Err); return (Err == NO_ERROR); } // // ========================================================== // DWORD DoAllDelayedMoves( IN PSP_FILE_QUEUE Queue ) /*++ Routine Description: Execute the Delayed Moves previously posted Arguments: Queue Queue that has the list in Return Value: Error Status --*/ { PSP_DELAYMOVE_NODE DelayMoveNode; PTSTR CurrentName; PTSTR TargetName; BOOL b = TRUE; PSP_DELAYMOVE_NODE DoneQueue = NULL; PSP_DELAYMOVE_NODE NextNode = NULL; DWORD Err = NO_ERROR; BOOL EnableProtectedRenames = FALSE; for (DelayMoveNode = Queue->DelayMoveQueue ; DelayMoveNode ; DelayMoveNode = NextNode ) { NextNode = DelayMoveNode->NextNode; MYASSERT(DelayMoveNode->SourceFilename != -1); CurrentName = pSetupStringTableStringFromId(Queue->StringTable, DelayMoveNode->SourceFilename); MYASSERT(CurrentName); if (DelayMoveNode->TargetFilename == -1) { TargetName = NULL; } else { TargetName = pSetupStringTableStringFromId( Queue->StringTable, DelayMoveNode->TargetFilename ); MYASSERT(TargetName); } // // Keep track of whether we've encountered any protected system files. // EnableProtectedRenames |= DelayMoveNode->TargetIsProtected; #ifdef UNICODE // // If this is a move (instead of a delete), then set security (letting // SCE know what the file's final name will be. // if((DelayMoveNode->SecurityDesc != -1) && TargetName) { Err = pSetupCallSCE(ST_SCE_RENAME, CurrentName, Queue, TargetName, DelayMoveNode->SecurityDesc, NULL ); if(Err != NO_ERROR ){ // // If we're on the first delay-move node, then we can abort. // However, if we've already processed one or more nodes, then // we can't abort--we must simply log an error indicating what // happened and keep on going. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_DELAYED_MOVE_SCE_FAILED, NULL, CurrentName, TargetName ); WriteLogError(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, Err ); if(DelayMoveNode == Queue->DelayMoveQueue) { // // Failure occurred on 1st node--we can abort. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR, MSG_LOG_OPERATION_CANCELLED, NULL ); break; } else { // // There's no turning back--log an error and keep on going. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR, MSG_LOG_ERROR_IGNORED, NULL ); Err = NO_ERROR; } } } else #endif { Err = NO_ERROR; } // // finally delay the move // if(!DelayedMove(CurrentName, TargetName)) { Err = GetLastError(); // // Same deal as above with SCE call--if we've already processed one // or more delay-move nodes, we can't abort. // if(TargetName) { WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_DELAYED_MOVE_FAILED, NULL, CurrentName, TargetName ); } else { WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_DELAYED_DELETE_FAILED, NULL, CurrentName ); } WriteLogError(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, Err ); if(DelayMoveNode == Queue->DelayMoveQueue) { // // Failure occurred on 1st node--we can abort. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR, MSG_LOG_OPERATION_CANCELLED, NULL ); break; } else { // // There's no turning back--log an error and keep on going. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR, MSG_LOG_ERROR_IGNORED, NULL ); Err = NO_ERROR; } } // // Move node to queue containing nodes that have already been processed // DelayMoveNode->NextNode = DoneQueue; DoneQueue = DelayMoveNode; } // // If we have any replacement of protected system files, then we need to // inform session manager so that it allows the replacement to occur upon // reboot. // // NOTE: We don't have to worry about enabling replacement of system files // with unsigned (hence, untrusted) versions. We only queue up unsigned // system files for replacement if the user was explicitly warned of (and // agreed to) the consequences. // // NTRAID#55485-2000/02/03-jamiehun // Protected renames only allows "rename all" or "rename none" // // the session manager only allows the granularity of "allow all // renames" or "allow no renames". If Err != NO_ERROR, then we // might want to clear out this flag, but that means we'd negate // any renames that were previously allowed. Yuck. So we flip a // coin, decide to do nothing, and hope for the best if an error // occurred. We have similar situation above -- it's all or // nothing. // if((Err == NO_ERROR) && EnableProtectedRenames) { pSetupProtectedRenamesFlag(TRUE); } // // any nodes that are left are dropped // for ( ; DelayMoveNode ; DelayMoveNode = NextNode ) { NextNode = DelayMoveNode->NextNode; MyFree(DelayMoveNode); } Queue->DelayMoveQueue = NULL; Queue->DelayMoveQueueTail = NULL; // // delete all nodes we queue'd // for ( ; DoneQueue ; DoneQueue = NextNode ) { NextNode = DoneQueue->NextNode; // // done with node // MyFree(DoneQueue); } return Err; } // // ========================================================== // VOID pSetupUnwindAll( IN PSP_FILE_QUEUE Queue, IN BOOL Succeeded ) /*++ Routine Description: Processes the Unwind Queue. If Succeeded is FALSE, restores any data that was backed up Arguments: Queue Queue to be unwound Succeeded Indicates if we should treat the whole operation as succeeded or failed Return Value: None--this routine should always succeed. (Any file errors encountered along the way are logged in the setupapi logfile.) --*/ { // if Succeeded, we need to delete Temp files // if we didn't succeed, we need to restore backups PSP_UNWIND_NODE UnwindNode; PSP_UNWIND_NODE ThisNode; SP_TARGET_ENT TargetInfo; PTSTR BackupFilename; PTSTR TargetFilename; PTSTR RenamedFilename; DWORD Err = NO_ERROR; TCHAR TempPath[MAX_PATH]; PTSTR TempNamePtr; TCHAR TempFilename[MAX_PATH]; BOOL RestoreByRenaming; BOOL OkToDeleteBackup; try { if (Succeeded == FALSE) { // // we need to restore backups // WriteLogEntry( Queue->LogContext, SETUP_LOG_WARNING, MSG_LOG_UNWIND, NULL); for ( UnwindNode = Queue->UnwindQueue; UnwindNode != NULL; ) { ThisNode = UnwindNode; UnwindNode = UnwindNode->NextNode; if (pSetupBackupGetTargetByID((HSPFILEQ)Queue, ThisNode->TargetID, &TargetInfo) == NO_ERROR) { BackupFilename = NULL; TargetFilename = NULL; RenamedFilename = NULL; // restore backup if(!(TargetInfo.InternalFlags & SP_TEFLG_RESTORED)) { // get target name TargetFilename = pSetupFormFullPath( Queue->StringTable, TargetInfo.TargetRoot, TargetInfo.TargetSubDir, TargetInfo.TargetFilename); if(TargetInfo.InternalFlags & SP_TEFLG_MOVED) { // // Get renamed filename // RenamedFilename = pSetupStringTableStringFromId(Queue->StringTable, TargetInfo.NewTargetFilename ); } if(TargetInfo.InternalFlags & SP_TEFLG_SAVED) { // // get backup name // BackupFilename = pSetupFormFullPath( Queue->StringTable, TargetInfo.BackupRoot, TargetInfo.BackupSubDir, TargetInfo.BackupFilename); } } if(TargetFilename && (RenamedFilename || BackupFilename)) { // // We either renamed the original file or we backed it up. // We need to put it back. // RestoreByRenaming = RenamedFilename ? TRUE : FALSE; RestoreRenamedOrBackedUpFile(TargetFilename, (RestoreByRenaming ? RenamedFilename : BackupFilename), RestoreByRenaming, Queue->LogContext ); // // If we were doing a copy (i.e., from a backup) as opposed // to a rename, then we need to reapply timestamp and // security. // if(!RestoreByRenaming) { Err = GetSetFileTimestamp(TargetFilename, &(ThisNode->CreateTime), &(ThisNode->AccessTime), &(ThisNode->WriteTime), TRUE ); if(Err != NO_ERROR) { // // We just blew away the timestamp on the file--log // an error entry to that effect. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_BACKUP_EXISTING_RESTORE_FILETIME_FAILED, NULL, TargetFilename ); WriteLogError(Queue->LogContext, SETUP_LOG_ERROR, Err ); } if(ThisNode->SecurityDesc != NULL){ Err = StampFileSecurity(TargetFilename, ThisNode->SecurityDesc); if(Err != NO_ERROR) { // // We just blew away the existing security on // the file--log an error entry to that effect. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_BACKUP_EXISTING_RESTORE_SECURITY_FAILED, NULL, TargetFilename ); WriteLogError(Queue->LogContext, SETUP_LOG_ERROR, Err ); } #ifdef UNICODE Err = pSetupCallSCE(ST_SCE_UNWIND, TargetFilename, NULL, NULL, -1, ThisNode->SecurityDesc ); if(Err != NO_ERROR) { // // We just blew away the existing security on // the file--log an error entry to that effect. // WriteLogEntry(Queue->LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, MSG_LOG_BACKUP_EXISTING_RESTORE_SCE_FAILED, NULL, TargetFilename ); WriteLogError(Queue->LogContext, SETUP_LOG_ERROR, Err ); } #endif } } // // Now mark that we've restored this file. We'll delete // tempfiles later // TargetInfo.InternalFlags |= SP_TEFLG_RESTORED; pSetupBackupSetTargetByID((HSPFILEQ)Queue, ThisNode->TargetID, &TargetInfo); } if(BackupFilename) { MyFree(BackupFilename); } if(TargetFilename) { MyFree(TargetFilename); } } } } // // cleanup - remove temporary files // for ( UnwindNode = Queue->UnwindQueue; UnwindNode != NULL; ) { ThisNode = UnwindNode; UnwindNode = UnwindNode->NextNode; if (pSetupBackupGetTargetByID((HSPFILEQ)Queue, ThisNode->TargetID, &TargetInfo) == NO_ERROR) { // delete temporary file if (TargetInfo.InternalFlags & SP_TEFLG_TEMPNAME) { // // get name of file that was used for backup // BackupFilename = pSetupFormFullPath( Queue->StringTable, TargetInfo.BackupRoot, TargetInfo.BackupSubDir, TargetInfo.BackupFilename); if(BackupFilename) { // // If this operation was a bootfile replacement, then we // don't want to delete the backup (if we used the renamed // file for the backup as well). A delayed delete will // have been queued to get rid of the file after a reboot. // OkToDeleteBackup = TRUE; if(TargetInfo.InternalFlags & SP_TEFLG_MOVED) { // // Retrieve the renamed filename to see if it's the // same as the backup filename. // RenamedFilename = pSetupStringTableStringFromId(Queue->StringTable, TargetInfo.NewTargetFilename ); if(!lstrcmpi(BackupFilename, RenamedFilename)) { OkToDeleteBackup = FALSE; } } if(OkToDeleteBackup) { // // since it was temporary, delete it // SetFileAttributes(BackupFilename, FILE_ATTRIBUTE_NORMAL); if(!DeleteFile(BackupFilename)) { // // Alright, see if we can set it up for delayed delete // instead. // if(!DelayedMove(BackupFilename, NULL)) { // // Oh well, just write a log entry indicating that // this file turd was left on the user's disk. // Err = GetLastError(); WriteLogEntry(Queue->LogContext, SETUP_LOG_WARNING | SETUP_LOG_BUFFER, MSG_LOG_BACKUP_DELAYED_DELETE_FAILED, NULL, BackupFilename ); WriteLogError(Queue->LogContext, SETUP_LOG_WARNING, Err ); } } } MyFree(BackupFilename); } } pSetupResetTarget(Queue->TargetLookupTable, ThisNode->TargetID, NULL, &TargetInfo, sizeof(TargetInfo), (LPARAM)0 ); } // cleanup node if (ThisNode->SecurityDesc != NULL) { MyFree(ThisNode->SecurityDesc); } MyFree(ThisNode); } Queue->UnwindQueue = NULL; } except (EXCEPTION_EXECUTE_HANDLER) { // // should generally not get here // unless Queue is invalid // } } // // ========================================================== // DWORD _SetupGetBackupInformation( IN PSP_FILE_QUEUE Queue, OUT PSP_BACKUP_QUEUE_PARAMS_V2 BackupParams ) /*++ Routine Description: Get Backup INF path - Internal version Arguments: Queue - pointer to queue structure (validated) BackupParams OUT - filled with INF file path Return Value: TRUE if success, else FALSE --*/ { // // Queue is assumed to have been validated // BackupParams is in Native format // LONG BackupInfID; ULONG BufSize = MAX_PATH; BOOL b; DWORD err = NO_ERROR; LPCTSTR filename; INT offset; BackupInfID = Queue->BackupInfID; if (BackupInfID != -1) { // // get inf from stringtable // b = pSetupStringTableStringFromIdEx(Queue->StringTable, BackupInfID, BackupParams->FullInfPath, &BufSize); if (b == FALSE) { if (BufSize == 0) { err = ERROR_NO_BACKUP; } else { err = ERROR_INSUFFICIENT_BUFFER; } goto Clean0; } // // find index of filename // filename = pSetupGetFileTitle(BackupParams->FullInfPath); offset = (INT)(filename - BackupParams->FullInfPath); BackupParams->FilenameOffset = offset; // // If the caller passed in a SP_BACKUP_QUEUE_PARAMS_V2 structure then // also fill in the ReinstallInstance string value. // if (BackupParams->cbSize >= sizeof(SP_BACKUP_QUEUE_PARAMS_V2)) { BufSize = MAX_PATH; if(Queue->BackupInstanceID != -1) { b = pSetupStringTableStringFromIdEx(Queue->StringTable, Queue->BackupInstanceID, BackupParams->ReinstallInstance, &BufSize); } else { // // no instance ID // BackupParams->ReinstallInstance[0] = TEXT('\0'); } if (b == FALSE) { if (BufSize == 0) { err = ERROR_NO_BACKUP; } else { err = ERROR_INSUFFICIENT_BUFFER; } goto Clean0; } } } else { // // no backup path // err = ERROR_NO_BACKUP; } Clean0: return err; } #ifdef UNICODE // // ANSI version in UNICODE // BOOL WINAPI SetupGetBackupInformationA( IN HSPFILEQ QueueHandle, OUT PSP_BACKUP_QUEUE_PARAMS_V2_A BackupParams ) { BOOL b; int i; INT c; LPCSTR p; SP_BACKUP_QUEUE_PARAMS_W BackupParamsW; // // confirm structure size // try { if((BackupParams->cbSize != sizeof(SP_BACKUP_QUEUE_PARAMS_V2_A)) && (BackupParams->cbSize != sizeof(SP_BACKUP_QUEUE_PARAMS_V1_A))) { SetLastError(ERROR_INVALID_PARAMETER); b = FALSE; leave; // exit try block } // // call Unicode version of API // ZeroMemory( &BackupParamsW, sizeof(BackupParamsW) ); BackupParamsW.cbSize = sizeof(BackupParamsW); b = SetupGetBackupInformationW(QueueHandle,&BackupParamsW); if (b) { // // success, convert structure from UNICODE to ANSI // i = WideCharToMultiByte( CP_ACP, 0, BackupParamsW.FullInfPath, MAX_PATH, BackupParams->FullInfPath, MAX_PATH, NULL, NULL ); if (i==0) { // // error occurred (LastError set to error) // b = FALSE; leave; // exit try block } // // we need to recalc the offset of INF filename // taking care of internationalization // p = BackupParams->FullInfPath; for(c = 0; c < BackupParamsW.FilenameOffset; c++) { p = CharNextA(p); } BackupParams->FilenameOffset = (int)(p-(BackupParams->FullInfPath)); // new offset in ANSI if (BackupParams->cbSize >= sizeof(SP_BACKUP_QUEUE_PARAMS_V2_A)) { // // instance // i = WideCharToMultiByte( CP_ACP, 0, BackupParamsW.ReinstallInstance, MAX_PATH, BackupParams->ReinstallInstance, MAX_PATH, NULL, NULL ); if (i==0) { // // error occurred (LastError set to error) // b = FALSE; leave; // exit try block } } } } except(EXCEPTION_EXECUTE_HANDLER) { // // if we except, assume it's due to invalid parameter // SetLastError(ERROR_INVALID_PARAMETER); b = FALSE; } return b; } #else // // Unicode version in ANSI // BOOL WINAPI SetupGetBackupInformationW( IN HSPFILEQ QueueHandle, OUT PSP_BACKUP_QUEUE_PARAMS_V2_W BackupParams ) { UNREFERENCED_PARAMETER(QueueHandle); UNREFERENCED_PARAMETER(BackupParams); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif // // Native version // BOOL WINAPI SetupGetBackupInformation( IN HSPFILEQ QueueHandle, OUT PSP_BACKUP_QUEUE_PARAMS_V2 BackupParams ) /*++ Routine Description: Get Backup INF path Arguments: QueueHandle - handle of queue to retrieve backup INF file from BackupParams - IN - has cbSize set, OUT - filled with INF file path Return Value: TRUE if success, else FALSE --*/ { BOOL b = TRUE; PSP_FILE_QUEUE Queue = (PSP_FILE_QUEUE)QueueHandle; DWORD res; // // first validate QueueHandle // try { if(Queue->Signature != SP_FILE_QUEUE_SIG) { b = FALSE; } } except(EXCEPTION_EXECUTE_HANDLER) { b = FALSE; } if(!b) { SetLastError(ERROR_INVALID_HANDLE); goto Clean0; } // // now fill in structure // if we except, assume bad pointer // try { if((BackupParams->cbSize != sizeof(SP_BACKUP_QUEUE_PARAMS_V2)) && (BackupParams->cbSize != sizeof(SP_BACKUP_QUEUE_PARAMS_V1))) { SetLastError(ERROR_INVALID_PARAMETER); b = FALSE; leave; // exit try block } res = _SetupGetBackupInformation(Queue,BackupParams); if (res == NO_ERROR) { b = TRUE; } else { SetLastError(res); b = FALSE; } } except(EXCEPTION_EXECUTE_HANDLER) { // // if we except, assume it's due to invalid parameter // SetLastError(ERROR_INVALID_PARAMETER); b = FALSE; } Clean0: return b; } // // ========================================================== // VOID RestoreRenamedOrBackedUpFile( IN PCTSTR TargetFilename, IN PCTSTR CurrentFilename, IN BOOL RenameFile, IN PSETUP_LOG_CONTEXT LogContext OPTIONAL ) /*++ Routine Description: This routine does its best to restore a backed-up or renamed file back to its original name. Arguments: TargetFilename - filename to be restored to CurrentFilename - file to restore RenameFile - if TRUE, CurrentFilename was previously renamed from TargetFilename (hence should be renamed back). If FALSE, CurrentFilename is merely a copy, and should be copied back. LogContext - supplies a log context used if errors are encountered. Return Value: None. --*/ { DWORD Err; TCHAR TempPath[MAX_PATH]; PTSTR TempNamePtr; TCHAR TempFilename[MAX_PATH]; DWORD LogTag = AllocLogInfoSlotOrLevel(LogContext,SETUP_LOG_INFO,FALSE); WriteLogEntry( LogContext, LogTag, MSG_LOG_UNWIND_FILE, NULL, CurrentFilename, TargetFilename ); // // First, clear target attributes... // SetFileAttributes(TargetFilename, FILE_ATTRIBUTE_NORMAL); if(RenameFile) { // // simple case, move temporary file over existing file // pSetupExemptFileFromProtection( TargetFilename, SFC_ACTION_ADDED | SFC_ACTION_REMOVED | SFC_ACTION_MODIFIED | SFC_ACTION_RENAMED_OLD_NAME |SFC_ACTION_RENAMED_NEW_NAME, LogContext, NULL ); Err = DoMove(CurrentFilename, TargetFilename) ? NO_ERROR : GetLastError(); } else { pSetupExemptFileFromProtection( TargetFilename, SFC_ACTION_ADDED | SFC_ACTION_REMOVED | SFC_ACTION_MODIFIED | SFC_ACTION_RENAMED_OLD_NAME |SFC_ACTION_RENAMED_NEW_NAME, LogContext, NULL ); Err = CopyFile(CurrentFilename, TargetFilename, FALSE) ? NO_ERROR : GetLastError(); } if(Err != NO_ERROR) { // // Can't replace the file that got copied in place of // the original one--try to move that one to a tempname // and schedule it for delayed deletion. // WriteLogEntry(LogContext, SETUP_LOG_ERROR|SETUP_LOG_BUFFER, MSG_LOG_UNWIND_TRY1_FAILED, NULL, CurrentFilename, TargetFilename ); WriteLogError(LogContext, SETUP_LOG_ERROR, Err ); // // First, strip the filename off the path. // _tcscpy(TempPath, TargetFilename); TempNamePtr = (PTSTR)pSetupGetFileTitle(TempPath); *TempNamePtr = TEXT('\0'); // // Now get a temp filename within that directory... // if(GetTempFileName(TempPath, TEXT("OLD"), 0, TempFilename) == 0 ) { // // Uh oh! // Err = GetLastError(); } else if(!DoMove(TargetFilename, TempFilename)) { Err = GetLastError(); } else { // // OK, we were able to rename the current file to a // temp filename--now attempt to copy or move the // original file back to its original name. // if(RenameFile) { Err = DoMove(CurrentFilename, TargetFilename) ? NO_ERROR : GetLastError(); } else { Err = CopyFile(CurrentFilename, TargetFilename, FALSE) ? NO_ERROR : GetLastError(); } if(Err != NO_ERROR) { // // This is very bad--put the current file back (it's probably // better to have something than nothing at all). // DoMove(TempFilename, TargetFilename); } } if(Err == NO_ERROR) { // // We successfully moved the current file to a temp // filename, and put the original file back. Now // queue a delayed delete for the temp file. // if(!DelayedMove(TempFilename, NULL)) { // // All this means is that a file turd will get // left on the disk--simply log an event about // this. // Err = GetLastError(); WriteLogEntry(LogContext, SETUP_LOG_WARNING | SETUP_LOG_BUFFER, MSG_LOG_RENAME_EXISTING_DELAYED_DELETE_FAILED, NULL, TargetFilename, TempFilename ); WriteLogError(LogContext, SETUP_LOG_WARNING, Err ); } } else { // // We were unable to put the original file back--we // can't fail, so just log an error about this and // keep on going. // // in the case of a backed-up file, // we might get away with queueing the original file // for a delayed rename and then prompting the user // to reboot. However, that won't work for renamed // files, because they're typically needed very // early on in the boot (i.e., before session // manager has had a chance to process the delayed // rename operations). // WriteLogEntry(LogContext, SETUP_LOG_ERROR | SETUP_LOG_BUFFER, (RenameFile ? MSG_LOG_RENAME_EXISTING_RESTORE_FAILED : MSG_LOG_BACKUP_EXISTING_RESTORE_FAILED), NULL, CurrentFilename, TargetFilename ); WriteLogError(LogContext, SETUP_LOG_ERROR, Err ); } } if (LogTag) { ReleaseLogInfoSlot(LogContext,LogTag); } } // // ========================================================== // BOOL UnPostDelayedMove( IN PSP_FILE_QUEUE Queue, IN PCTSTR CurrentName, IN PCTSTR NewName OPTIONAL ) /*++ Routine Description: Locates a delay-move node (either for rename or delete), and removes it from the delay-move queue. Arguments: Queue Queue that the move was applied to CurrentName Name of file to be moved NewName Name to move file to (NULL if delayed-delete) Return Value: If successful, the return value is TRUE, otherwise it is FALSE. --*/ { PSP_DELAYMOVE_NODE CurNode, PrevNode; PCTSTR SourceFilename, TargetFilename; // // Since the path string IDs in the delay-move nodes are case-sensitive, we // don't attempt to match on ID. We instead retrieve the strings, and do // case-insensitive string compares. Since this routine is rarely used, the // performance hit isn't a big deal. // for(CurNode = Queue->DelayMoveQueue, PrevNode = NULL; CurNode; PrevNode = CurNode, CurNode = CurNode->NextNode) { if(NewName) { // // We're searching for a delayed rename, so we must pay attention // to the target filename. // if(CurNode->TargetFilename == -1) { continue; } else { TargetFilename = pSetupStringTableStringFromId(Queue->StringTable, CurNode->TargetFilename); MYASSERT(TargetFilename); if(lstrcmpi(NewName, TargetFilename)) { // // Target filenames differ--move on. // continue; } } } else { // // We're searching for a delayed delete. // if(CurNode->TargetFilename != -1) { // // This is a rename, not a delete--move on. // continue; } } // // If we get to here, then the target filenames match (if this is a // rename), or they're both empty (if it's a delete). Now compare the // source filenames. // MYASSERT(CurNode->SourceFilename != -1); SourceFilename = pSetupStringTableStringFromId(Queue->StringTable, CurNode->SourceFilename); MYASSERT(SourceFilename); if(lstrcmpi(CurrentName, SourceFilename)) { // // Source filenames differ--move on. // continue; } else { // // We have a match--remove the node from the delay-move queue. // if(PrevNode) { PrevNode->NextNode = CurNode->NextNode; } else { Queue->DelayMoveQueue = CurNode->NextNode; } if(!CurNode->NextNode) { MYASSERT(Queue->DelayMoveQueueTail == CurNode); Queue->DelayMoveQueueTail = PrevNode; } MyFree(CurNode); return TRUE; } } // // We didn't find a match. // return FALSE; } DWORD pSetupDoLastKnownGoodBackup( IN struct _SP_FILE_QUEUE *Queue, OPTIONAL IN PCTSTR TargetFilename, IN DWORD Flags, IN PSETUP_LOG_CONTEXT LogContext OPTIONAL ) /*++ Routine Description: Process LastKnownGood backups into <>. If files are to be deleted on restore, write apropriate flags to HKLM\System\LastGoodRecovery\LastGood\ Caviats: If file is not inside <> or sub-directory, exit no-error. Backup will not occur if PSPGF_NO_BACKUP is set. If !SP_LKG_FLAG_FORCECOPY If file is inside <>, exit with error. If file is inside <> throw warnings If an INF inside of <> is backed up, it's PNF is backed up too. If backup fails, we don't abort copy. Arguments: Queue Queue (optional) if specified, flags will be checked TargetFilename Name of file to backup Flags SP_LKG_FLAG_FORCECOPY - if set, turns copy safety-guards off SP_LKG_FLAG_DELETEIFNEW - if set, writes a delete entry for new files SP_LKG_FLAG_DELETEEXISTING - if set, writes a delete entry for existing files SP_LKG_FLAG_DELETEOP - if set, primary operation is trying to delete/rename file LogContext If specified, provides preferred logging context Return Value: error if operation should be aborted, NO_ERROR otherwise. --*/ { #ifdef UNICODE int wd_len; // windows directory len int tf_len; // target file len int id_len; // inf directory len int lkgd_len; // last known good directory len int rf_len; // relative file len (including preceeding slash) BOOL is_inf = FALSE; BOOL is_infdir = FALSE; BOOL write_delete = FALSE; BOOL no_copy = FALSE; BOOL source_exists = FALSE; BOOL target_exists = FALSE; TCHAR FullTargetFilename[MAX_PATH]; TCHAR BackupTargetFilename[MAX_PATH+14]; TCHAR TempFilename[MAX_PATH]; TCHAR RegName[MAX_PATH]; PCTSTR RelativeFilename; PCTSTR CharPtr; PTSTR DestPtr; PTSTR NamePart = NULL; PTSTR ExtPart = NULL; DWORD attr; HANDLE hFile; HKEY hKeyLastGood; DWORD disposition; LONG regres; DWORD LastGoodFlags = 0; DWORD rval = NO_ERROR; PSETUP_LOG_CONTEXT LocalLogContext = NULL; if (!LogContext) { // // LogContext may be obmitted if there's a Queue parameter // if (Queue && Queue->LogContext) { LogContext = Queue->LogContext; } else { if(CreateLogContext(NULL,TRUE,&LocalLogContext)==NO_ERROR) { LogContext = LocalLogContext; } } MYASSERT(LogContext); } if ((GlobalSetupFlags & PSPGF_NO_BACKUP)!=0) { // // in a scenario where (1) we trust what we're doing and // (2) what we're doing modifies lots of files // or (3) what we're doing is undoable (eg, upgrading OS) // no_copy = TRUE; } #if 0 else if (Queue && !(Queue->Flags & FQF_DEVICE_INSTALL)) { // // in this scenario, a queue was specified, but it's not marked // for device install // we're not interested in this case // no_copy = TRUE; } #endif // // cannonicalize the Target name so user doesn't do stuff like .../TEMP/../INF // tf_len = (int)GetFullPathName(TargetFilename, MAX_PATH, FullTargetFilename, &NamePart); if (tf_len <= 0 || tf_len > MAX_PATH) { // // we don't do large paths very well // rval = NO_ERROR; goto final; } wd_len = lstrlen(WindowsDirectory); lkgd_len = lstrlen(LastGoodDirectory); id_len = lstrlen(InfDirectory); // // see if this file is nested below <> // note that such a file must be at least two characters longer // if((tf_len <= wd_len) || (FullTargetFilename[wd_len] != TEXT('\\')) || (_tcsnicmp(WindowsDirectory,FullTargetFilename,wd_len)!=0)) { // // this file is outside of %windir%, not handled by LKG // rval = NO_ERROR; goto final; } if (!(Flags&SP_LKG_FLAG_FORCECOPY)) { // // sanity check for files being copied into LKG dir // if((tf_len > lkgd_len) && (FullTargetFilename[lkgd_len] == TEXT('\\')) && (_tcsnicmp(LastGoodDirectory,FullTargetFilename,lkgd_len)==0)) { // // this file is prefixed by LastGoodDirectory // not allowed - throw a log message and inform caller of this mistake // return FALSE to abort the operation // WriteLogEntry(LogContext, SETUP_LOG_ERROR, MSG_LOG_FILE_BLOCK, NULL, FullTargetFilename, LastGoodDirectory ); rval = ERROR_ACCESS_DENIED; goto final; } } if((tf_len > id_len) && (FullTargetFilename[id_len] == TEXT('\\')) && (_tcsnicmp(InfDirectory,FullTargetFilename,id_len)==0) && ((NamePart-FullTargetFilename) == (id_len+1))) { // // the file sits in the primary INF directory // is_infdir = TRUE; // // check for name ending in ".INF" - if so, we need to backup ".PNF" too // ExtPart = FullTargetFilename+tf_len; while ((ExtPart = CharPrev(NamePart,ExtPart)) != NamePart) { if (ExtPart[0] == TEXT('.')) { break; } } if(lstrcmpi(ExtPart,TEXT(".INF"))==0) { // // ends in .INF // is_inf = TRUE; // // we should only get here if Force is set (ie, we've already determined // what is being copied and all is OK). If we don't, this implies someone // is trying a back-door INF copy. we've already logged above that they're writing // to this directory when they shouldn't. However, if we don't do anything // about it, culprit could render machine in bad state // change this behavior into a "Force" behavior // if (!(Flags&SP_LKG_FLAG_FORCECOPY)) { no_copy = FALSE; // ensure we'll go through copy logic WriteLogEntry(LogContext, SETUP_LOG_ERROR, MSG_LOG_INF_WARN, NULL, FullTargetFilename, InfDirectory ); if(!(Flags&SP_LKG_FLAG_DELETEOP)) { // // we're invalidly trying to overwrite an INF/create an INF // Flags|=SP_LKG_FLAG_DELETEIFNEW; } } } else if (!(Flags&SP_LKG_FLAG_FORCECOPY)) { // // writing something else into this directory - huh? // well, if it's not an INF, we won't pick it up via INF searching // if it's a PNF or cache, we're regenerate it // don't fret too much, but slap wrist. // WriteLogEntry(LogContext, SETUP_LOG_ERROR, MSG_LOG_FILE_WARN, NULL, FullTargetFilename, InfDirectory ); } } if (no_copy) { // // we determined that we're not going to backup, and we've now done logging items // rval = NO_ERROR; goto final; } // // does source really exist? // if ((attr=GetFileAttributes(FullTargetFilename))!=(DWORD)(-1)) { source_exists = TRUE; if (Flags & SP_LKG_FLAG_DELETEEXISTING) { write_delete = TRUE; } } else if (Flags & SP_LKG_FLAG_DELETEIFNEW) { write_delete = TRUE; } else { // // we're done // rval = NO_ERROR; goto final; } // // remap to LKG directory // RelativeFilename = FullTargetFilename+wd_len; // includes preceeding backslash rf_len = tf_len-wd_len; MYASSERT((MAX_PATH+(lkgd_len-wd_len))<=SIZECHARS(BackupTargetFilename)); lstrcpy(BackupTargetFilename,LastGoodDirectory); lstrcpy(BackupTargetFilename+lkgd_len,RelativeFilename); // // does backup already exist? // if ((attr=GetFileAttributes(BackupTargetFilename))!=(DWORD)(-1)) { // // if it does, nothing useful to do. // rval = NO_ERROR; goto final; } // // create intermediate directories as needed // pSetupMakeSurePathExists(BackupTargetFilename); // // we need to use a temporary file first, and then move it into place // so we don't get in the situation where we write a bad file, reboot // and decide to use LKG. // if(GetTempFileName(LastGoodDirectory, TEXT("TMP"), 0, TempFilename) == 0 ) { // // if this fails, it could be because we haven't got right permissions // non-fatal // rval = NO_ERROR; goto final; } // // after this point, aborts require cleaning up of temporary file // if (write_delete) { // // GetTempFileName created an empty place holder // ensure it has right attributes // before moving into place // SetFileAttributes(TempFilename,FILE_ATTRIBUTE_HIDDEN); } else { // // copy original to this temporary file // apply apropriate permissions // if(!CopyFile(FullTargetFilename, TempFilename ,FALSE)) { // // copy failed - non fatal // goto cleanup; } } // // we have a temporary file ready to be moved into place // move it to final name, ensuring that while we were doing above, a new file // was not already written // if(!MoveFileEx(TempFilename,BackupTargetFilename,MOVEFILE_WRITE_THROUGH)) { // // could be that a file with that name now exists, but didn't earlier // oh well, clean up the mess and leave gracefully // goto cleanup; } if (write_delete) { // // if we successfully wrote an empty placeholder for a file to be deleted, we need to shadow this file with // an entry in registry // regres = RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_LASTGOOD, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKeyLastGood, &disposition); if (regres == NO_ERROR) { // // copy string, remapping slash's from '\\' to '/' // CharPtr = RelativeFilename+1; // after initial '\' DestPtr = RegName; while(*CharPtr) { PCTSTR Next = _tcschr(CharPtr,TEXT('\\')); if (!Next) { Next = CharPtr + lstrlen(CharPtr); } if(Next-CharPtr) { CopyMemory(DestPtr,CharPtr,(Next-CharPtr)*sizeof(TCHAR)); DestPtr+=(Next-CharPtr); CharPtr = Next; } if (*CharPtr == TEXT('\\')) { *DestPtr = TEXT('/'); DestPtr++; CharPtr++; } } *DestPtr = TEXT('\0'); // // write key, name = modified relative path, value = flags // LastGoodFlags = LASTGOOD_OPERATION_DELETE; regres = RegSetValueEx(hKeyLastGood, RegName, 0, REG_DWORD, (PBYTE)&LastGoodFlags, sizeof(LastGoodFlags)); RegCloseKey(hKeyLastGood); } } // // ok, now we've populated the LKG directory with this file // if (is_inf) { // // if we backed up an INF that's in primary INF directory, we should also backup existing PNF // if we're writing an entry to delete INF, we'll always write an entry to delete PNF // MYASSERT(ExtPart); lstrcpy(ExtPart,TEXT(".PNF")); if(pSetupDoLastKnownGoodBackup(NULL, FullTargetFilename, SP_LKG_FLAG_FORCECOPY|SP_LKG_FLAG_DELETEIFNEW|(write_delete?SP_LKG_FLAG_DELETEEXISTING:0), LogContext) != NO_ERROR) { // // should never fail // MYASSERT(FALSE); } } // // done! // rval = NO_ERROR; goto final; cleanup: // // cleanup in the case where we've already created temporary file // SetFileAttributes(TempFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(TempFilename); rval = NO_ERROR; final: if(LocalLogContext) { DeleteLogContext(LocalLogContext); } return rval; #else // // ANSI - not supported // return NO_ERROR; #endif } #ifdef UNICODE BOOL pSetupRestoreLastKnownGoodFile( IN PCTSTR TargetFilename, IN DWORD Flags, IN PSETUP_LOG_CONTEXT LogContext OPTIONAL ) /*++ Routine Description: Restore a single LKG file The assumption here is that if this API was called, we detected something really bad that needs to be fixed immediately Arguments: TargetFilename Name of file to restore Flags LogContext If specified, provides preferred logging context Return Value: TRUE if file was successfully restored --*/ { int wd_len; // windows directory len int tf_len; // target file len int lkgd_len; // last known good directory len int rf_len; // relative file len (including preceeding slash) TCHAR FullTargetFilename[MAX_PATH]; TCHAR BackupTargetFilename[MAX_PATH+14]; TCHAR TempFilename[MAX_PATH]; TCHAR TempPathname[MAX_PATH]; TCHAR RegName[MAX_PATH]; PCTSTR RelativeFilename; PTSTR NamePart = NULL; BOOL rflag = FALSE; PSETUP_LOG_CONTEXT LocalLogContext = NULL; LONG regres; HKEY hKeyLastGood; PCTSTR CharPtr; PTSTR DestPtr; DWORD RegType; DWORD RegSize; DWORD LastGoodFlags; if (!LogContext) { // // LogContext may be obmitted if there's a Queue parameter // if(CreateLogContext(NULL,TRUE,&LocalLogContext)==NO_ERROR) { LogContext = LocalLogContext; } MYASSERT(LogContext); } // // cannonicalize the Target name so user doesn't do stuff like .../TEMP/../INF // tf_len = (int)GetFullPathName(TargetFilename, MAX_PATH, FullTargetFilename, &NamePart); if (tf_len <= 0 || tf_len > MAX_PATH) { // // we don't do large paths very well // goto final; } wd_len = lstrlen(WindowsDirectory); lkgd_len = lstrlen(LastGoodDirectory); // // see if this file is nested below <> // note that such a file must be at least two characters longer // if((tf_len <= wd_len) || (FullTargetFilename[wd_len] != TEXT('\\')) || (_tcsnicmp(WindowsDirectory,FullTargetFilename,wd_len)!=0)) { // // this file is outside of %windir%, not handled by LKG // goto final; } // // remap to LKG directory // RelativeFilename = FullTargetFilename+wd_len; // includes preceeding backslash rf_len = tf_len-wd_len; MYASSERT((MAX_PATH+(lkgd_len-wd_len))<=SIZECHARS(BackupTargetFilename)); lstrcpy(BackupTargetFilename,LastGoodDirectory); lstrcpy(BackupTargetFilename+lkgd_len,RelativeFilename); // // does backup already exist? // if (GetFileAttributes(BackupTargetFilename)==(DWORD)(-1)) { // // if not, nothing we can do // goto final; } // // find LKG flags to see what we need to do // regres = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_LASTGOOD, 0, KEY_READ, &hKeyLastGood); if (regres == NO_ERROR) { // // copy string, remapping slash's from '\\' to '/' // CharPtr = RelativeFilename+1; // after initial '\' DestPtr = RegName; while(*CharPtr) { PCTSTR Next = _tcschr(CharPtr,TEXT('\\')); if (!Next) { Next = CharPtr + lstrlen(CharPtr); } if(Next-CharPtr) { CopyMemory(DestPtr,CharPtr,(Next-CharPtr)*sizeof(TCHAR)); DestPtr+=(Next-CharPtr); CharPtr = Next; } if (*CharPtr == TEXT('\\')) { *DestPtr = TEXT('/'); DestPtr++; CharPtr++; } } *DestPtr = TEXT('\0'); RegSize = sizeof(LastGoodFlags); regres = RegQueryValueEx(hKeyLastGood, RegName, NULL, &RegType, (PBYTE)&LastGoodFlags, &RegSize); if((regres != NO_ERROR) || (RegType != REG_DWORD) || (RegSize != sizeof(DWORD))) { // // default action is copy // LastGoodFlags = 0; } RegCloseKey(hKeyLastGood); } // // base directory of target file // lstrcpyn(TempPathname, FullTargetFilename, MAX_PATH); *((PTSTR)pSetupGetFileTitle(TempPathname)) = TEXT('\0'); if (LastGoodFlags & LASTGOOD_OPERATION_DELETE) { // // delete // if(GetFileAttributes(FullTargetFilename)==(DWORD)(-1)) { // // already deleted // rflag = TRUE; goto final; } pSetupExemptFileFromProtection( FullTargetFilename, SFC_ACTION_ADDED | SFC_ACTION_REMOVED | SFC_ACTION_MODIFIED | SFC_ACTION_RENAMED_OLD_NAME |SFC_ACTION_RENAMED_NEW_NAME, LogContext, NULL ); // // try the simple way first // SetFileAttributes(FullTargetFilename, FILE_ATTRIBUTE_NORMAL); if(!DeleteFile(FullTargetFilename)) { // // can't delete target directly // if(!GetTempFileName(TempPathname, TEXT("SETP"), 0, BackupTargetFilename)) { // // can't create backup temp, nothing we can do // goto final; } // // move existing file into a temp backup // if(!MoveFileEx(FullTargetFilename,BackupTargetFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)) { // // this failed too for some reason // SetFileAttributes(BackupTargetFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(BackupTargetFilename); goto final; } // // now do something with the bad file // we don't care if this fails // SetFileAttributes(BackupTargetFilename, FILE_ATTRIBUTE_NORMAL); if(!DeleteFile(BackupTargetFilename)) { MoveFileEx(BackupTargetFilename,NULL,MOVEFILE_DELAY_UNTIL_REBOOT); } } } else { // // restore back to LKG file // // // create intermediate directories as needed as part of the restore // pSetupMakeSurePathExists(FullTargetFilename); // // create a temporary filename to copy to // before moving restored file into place // if(!GetTempFileName(TempPathname, TEXT("SETP"), 0, TempFilename)) { // // can't create temp, nothing we can do // goto final; } if(!CopyFile(BackupTargetFilename,TempFilename,FALSE)) { // // failed to copy to temporary file // DeleteFile(TempFilename); goto final; } // // simple case, move temporary file over existing file // pSetupExemptFileFromProtection( FullTargetFilename, SFC_ACTION_ADDED | SFC_ACTION_REMOVED | SFC_ACTION_MODIFIED | SFC_ACTION_RENAMED_OLD_NAME |SFC_ACTION_RENAMED_NEW_NAME, LogContext, NULL ); SetFileAttributes(FullTargetFilename, FILE_ATTRIBUTE_NORMAL); if(!MoveFileEx(TempFilename,FullTargetFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)) { // // we failed to overwrite file, need slightly different stratagy // if(!GetTempFileName(TempPathname, TEXT("SETP"), 0, BackupTargetFilename)) { // // can't create backup temp, nothing we can do // SetFileAttributes(TempFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(TempFilename); goto final; } // // move existing file into a temp backup // if(!MoveFileEx(FullTargetFilename,BackupTargetFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)) { // // this failed too for some reason // SetFileAttributes(BackupTargetFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(BackupTargetFilename); SetFileAttributes(TempFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(TempFilename); goto final; } // // we moved existing file out of place, now move new file into place // if(!MoveFileEx(TempFilename,FullTargetFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH)) { // // huh? Ok, that failed, try to recover // MoveFileEx(BackupTargetFilename,FullTargetFilename,MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH); SetFileAttributes(TempFilename, FILE_ATTRIBUTE_NORMAL); DeleteFile(TempFilename); goto final; } // // now do something with the bad file // we don't care if this fails // SetFileAttributes(BackupTargetFilename, FILE_ATTRIBUTE_NORMAL); if(!DeleteFile(BackupTargetFilename)) { MoveFileEx(BackupTargetFilename,NULL,MOVEFILE_DELAY_UNTIL_REBOOT); } } } // // done! // rflag = TRUE; final: if(LocalLogContext) { DeleteLogContext(LocalLogContext); } return rflag; } #endif #ifdef UNICODE WINSETUPAPI BOOL WINAPI SetupPrepareQueueForRestoreA( IN HSPFILEQ QueueHandle, IN PCSTR BackupPath, IN DWORD RestoreFlags ) /*++ See SetupPrepareQueueForRestore --*/ { BOOL f; DWORD rc; PCWSTR UnicodeBackupPath; if(BackupPath) { rc = pSetupCaptureAndConvertAnsiArg(BackupPath, &UnicodeBackupPath); if(rc != NO_ERROR) { SetLastError(rc); return FALSE; } } else { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } f = SetupPrepareQueueForRestore(QueueHandle,UnicodeBackupPath,RestoreFlags); rc = GetLastError(); MyFree(UnicodeBackupPath); SetLastError(rc); return f; } #else WINSETUPAPI BOOL WINAPI SetupPrepareQueueForRestoreW( IN HSPFILEQ QueueHandle, IN PCWSTR BackupPath, IN DWORD RestoreFlags ) /*++ ANSI stub --*/ { UNREFERENCED_PARAMETER(QueueHandle); UNREFERENCED_PARAMETER(BackupPath); UNREFERENCED_PARAMETER(RestoreFlags); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return(FALSE); } #endif WINSETUPAPI BOOL WINAPI SetupPrepareQueueForRestore( IN HSPFILEQ QueueHandle, IN PCTSTR BackupPath, IN DWORD RestoreFlags ) /*++ Routine Description: Initializes restore directory Arguments: QueueHandle - file queue to modify BackupPath - original backup directory to use for restore RestoreFlags - options Return Value: TRUE if success, else FALSE --*/ { BOOL b = TRUE; DWORD rc; BOOL f = FALSE; PSP_FILE_QUEUE Queue = (PSP_FILE_QUEUE)QueueHandle; LONG RestorePathID; // // validate string pointer // if(!BackupPath) { rc = ERROR_INVALID_PARAMETER; goto clean; } // // validate flags (currently not implemented) // if(RestoreFlags) { rc = ERROR_INVALID_PARAMETER; goto clean; } // // validate QueueHandle // try { if(Queue->Signature != SP_FILE_QUEUE_SIG) { b = FALSE; } } except(EXCEPTION_EXECUTE_HANDLER) { b = FALSE; } if(!b) { rc = ERROR_INVALID_HANDLE; goto clean; } try { // // if a restore point has previously been set, return error // if(Queue->RestorePathID != -1) { rc = ERROR_ALREADY_EXISTS; leave; } RestorePathID = pSetupStringTableAddString(Queue->StringTable, (PTSTR)BackupPath , STRTAB_CASE_SENSITIVE); if (RestorePathID == -1) { rc = ERROR_NOT_ENOUGH_MEMORY; leave; } // // done - just need to set the restore-path // Queue->RestorePathID = RestorePathID; WriteLogEntry(Queue->LogContext, SETUP_LOG_WARNING, MSG_LOG_RESTORE, NULL, BackupPath ); } except(EXCEPTION_EXECUTE_HANDLER) { rc = ERROR_INVALID_DATA; } f = TRUE; rc = NO_ERROR; clean: // // no cleanup required // SetLastError(rc); return f; } #define SP_TEFLG_BITS_TO_RESET ( SP_TEFLG_SAVED \ | SP_TEFLG_TEMPNAME \ | SP_TEFLG_ORIGNAME \ | SP_TEFLG_MODIFIED \ | SP_TEFLG_MOVED \ | SP_TEFLG_BACKUPQUEUE \ | SP_TEFLG_RESTORED \ | SP_TEFLG_UNWIND \ | SP_TEFLG_SKIPPED \ | SP_TEFLG_INUSE \ | SP_TEFLG_RENAMEEXISTING ) BOOL pSetupResetTarget( IN PVOID StringTable, IN LONG StringId, IN PCTSTR String, OPTIONAL IN PVOID ExtraData, IN UINT ExtraDataSize, IN LPARAM lParam ) /*++ Routine Description: This routine resets the SP_TARGET_ENT data stored with a string table entry in a file queue's TargetLookupTable. This routine may be used as the callback function to iterate such entries via pSetupStringTableEnum. Arguments: StringTable - Supplies a handle to the string table being enumerated StringId - Supplies the ID of the current string String - Optionally, supplies a pointer to the current string (this will always be filled in when this routine is used as a callback for pSetupStringTableEnum, but other callers may omit it, as it isn't needed). ExtraData - Supplies a pointer to the SP_TARGET_ENT data associatd with the string ExtraDataSize - Supplies the size of the buffer pointed to by ExtraData-- should always be sizeof(SP_TARGET_ENT) lParam - unused Return Value: This routine always returns TRUE, so that all string entries will be enumerated. --*/ { PSP_TARGET_ENT TargetInfo; BOOL b; UNREFERENCED_PARAMETER(String); UNREFERENCED_PARAMETER(lParam); MYASSERT(ExtraData); MYASSERT(ExtraDataSize == sizeof(SP_TARGET_ENT)); // // Clear the bits that will get re-generated when the queue is committed // again. // ((PSP_TARGET_ENT)ExtraData)->InternalFlags &= ~SP_TEFLG_BITS_TO_RESET; // // Also need to reset the NewTargetFilename // ((PSP_TARGET_ENT)ExtraData)->NewTargetFilename = -1; // // Store the modified data back to the string table entry // b = pSetupStringTableSetExtraData(StringTable, StringId, ExtraData, ExtraDataSize ); // // This should never fail // MYASSERT(b); return TRUE; }