/*++ Copyright (c) 1995 Microsoft Corporation Module Name: log.c Abstract: Routines for logging actions performed during setup. Author: Ted Miller (tedm) 4-Apr-1995 Revision History: --*/ #include "setupp.h" #pragma hdrstop #include // to define HRESULT for richedit.h #include #include "setuplog.h" // // Severity descriptions. Initialized in InitializeSetupActionLog. // PCSTR SeverityDescriptions[LogSevMaximum]; // // Constant strings used for logging in various places. // PCWSTR szWaitForSingleObject = L"WaitForSingleObject"; PCWSTR szFALSE = L"FALSE"; PCWSTR szSetGroupOfValues = L"SetGroupOfValues"; PCWSTR szSetArrayToMultiSzValue = L"pSetupSetArrayToMultiSzValue"; PCWSTR szCreateProcess = L"CreateProcess"; PCWSTR szRegOpenKeyEx = L"RegOpenKeyEx"; PCWSTR szRegQueryValueEx = L"RegQueryValueEx"; PCWSTR szRegSetValueEx = L"RegSetValueEx"; PCWSTR szDeleteFile = L"DeleteFile"; PCWSTR szRemoveDirectory = L"RemoveDirectory"; PCWSTR szSetupInstallFromInfSection = L"SetupInstallFromInfSection"; // // This structure is passed as the parameter to DialogBoxParam to provide // initialization data. // typedef struct _LOGVIEW_DIALOG_DATA { PCWSTR LogFileName; // actual file used PCWSTR WindowHeading; // actual title of main window } LOGVIEW_DIALOG_DATA, *PLOGVIEW_DIALOG_DATA; PVOID pOpenFileCallback( IN PCTSTR Filename, IN BOOL WipeLogFile ) { WCHAR CompleteFilename[MAX_PATH]; HANDLE hFile; DWORD Result; // // Form the pathname of the logfile. // Result = GetWindowsDirectory(CompleteFilename,MAX_PATH); if( Result == 0) { MYASSERT(FALSE); return NULL; } pSetupConcatenatePaths(CompleteFilename,Filename,MAX_PATH,NULL); // // If we're wiping the logfile clean, attempt to delete // what's there. // if(WipeLogFile) { SetFileAttributes(CompleteFilename,FILE_ATTRIBUTE_NORMAL); DeleteFile(CompleteFilename); } // // Open existing file or create a new one. // hFile = CreateFile( CompleteFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); return (PVOID)hFile; } BOOL pWriteFile ( IN PVOID LogFile, IN LPCTSTR Buffer ) { PCSTR AnsiBuffer; BOOL Status; DWORD BytesWritten; // Write message to log file // if(AnsiBuffer = pSetupUnicodeToAnsi (Buffer)) { SetFilePointer (LogFile, 0, NULL, FILE_END); Status = WriteFile ( LogFile, AnsiBuffer, lstrlenA(AnsiBuffer), &BytesWritten, NULL ); MyFree (AnsiBuffer); } else { Status = FALSE; } // Write log message to debugging log // SetupDebugPrint((LPWSTR)Buffer); return Status; } BOOL pAcquireMutex ( IN PVOID Mutex ) /*++ Routine Description: Waits on the log mutex for a max of 1 second, and returns TRUE if the mutex was claimed, or FALSE if the claim timed out. Arguments: Mutex - specifies which mutex to acquire. Return Value: TRUE if the mutex was claimed, or FALSE if the claim timed out. --*/ { DWORD rc; if (!Mutex) { SetLastError (ERROR_INVALID_HANDLE); return FALSE; } // Wait a max of 1 second for the mutex rc = WaitForSingleObject (Mutex, 1000); if (rc != WAIT_OBJECT_0) { SetLastError (ERROR_EXCL_SEM_ALREADY_OWNED); return FALSE; } return TRUE; } VOID InitializeSetupLog( IN PSETUPLOG_CONTEXT Context ) /*++ Routine Description: Initialize the setup action log. This file is a textual description of actions performed during setup. The log file is called setuplog.txt and it exists in the windows dir. Arguments: Context - context structrure used by Setuplog. Return Value: Boolean value indicating whether initialization was sucessful. --*/ { UINT i; PWSTR p; Context->OpenFile = pOpenFileCallback; Context->CloseFile = CloseHandle; Context->AllocMem = MyMalloc; Context->FreeMem = MyFree; Context->Format = RetrieveAndFormatMessageV; Context->Write = pWriteFile; Context->Lock = pAcquireMutex; Context->Unlock = ReleaseMutex; Context->Mutex = CreateMutex(NULL,FALSE,L"SetuplogMutex"); // // Initialize the log severity descriptions. // for(i=0; iSeverityDescriptions[i] = MyLoadString(IDS_LOGSEVINFO+i); } SetuplogInitialize (Context, FALSE); SetuplogError( LogSevInformation, SETUPLOG_USE_MESSAGEID, MSG_LOG_GUI_START, NULL,NULL); } VOID TerminateSetupLog( IN PSETUPLOG_CONTEXT Context ) /*++ Routine Description: Close the Setup log and free resources. Arguments: Context - context structrure used by Setuplog. Return Value: None. --*/ { UINT i; if(Context->Mutex) { CloseHandle(Context->Mutex); Context->Mutex = NULL; } for (i=0; iSeverityDescriptions[i]) { MyFree (Context->SeverityDescriptions[i]); } } SetuplogTerminate(); } DWORD CALLBACK EditStreamCallback ( IN HANDLE hLogFile, IN LPBYTE Buffer, IN LONG cb, IN PLONG pcb ) /*++ Routine Description: Callback routine used by the rich edit control to read in the log file. Arguments: hLogFile - handle of file to read. This module provides the value through the EDITSTREAM structure. Buffer - address of buffer that receives the data cb - number of bytes to read pcb - address of number of bytes actually read Return Value: 0 to continue the stream operation, or nonzero to abort it. --*/ { DWORD error; if (!ReadFile (hLogFile, Buffer, cb, pcb, NULL)) { error = GetLastError(); return error; } return 0; } BOOL FormatText ( IN HWND hWndRichEdit ) /*++ Routine Description: Modify the contents of the rich edit control to make the log file look prettier. The modifications are driven by the array FormatStrings. It contains a list of strings to search for, and modifications to make when a target string is found. Arguments: hWndRichEdit - handle to the Rich Edit control. Return Value: Boolean indicating whether routine was successful. --*/ { // // separate items in the log with a horizontal line // PCWSTR NewTerm = L"----------------------------------------" L"----------------------------------------\r\n\r\n"; FINDTEXT FindText; // target text to change INT Position; // start of where target was found INT LineIndex; // index of line containing target CHARRANGE SelectRange; // range where target was found CHARFORMAT NewFormat; // structure to hold our format changes INT i; // loop counter PWSTR pw; // temporary pointer BOOL Status; // return status // // An array of changes we're going to make // struct tagFormatStrings { PCWSTR Find; // target string PCWSTR Replace; // change the target to this COLORREF Color; // make target text this color DWORD Effects; // modifications to target's font } FormatStrings[] = { {NULL, NULL, RGB(0,150,0), CFE_UNDERLINE}, {NULL, NULL, RGB(150,150,0), CFE_UNDERLINE}, {NULL, NULL, RGB(255,0,0), CFE_UNDERLINE}, {NULL, NULL, RGB(255,0,0), CFE_UNDERLINE|CFE_ITALIC}, {NULL, NULL, RGB(0,0,255), 0} }; // // Number of elements in FormatStrings array // #define FORMATSTRINGSCOUNT \ (sizeof(FormatStrings) / sizeof(struct tagFormatStrings)) MYASSERT(FORMATSTRINGSCOUNT == LogSevMaximum + 1); // // Initialize those parts of our data structures that won't change // Status = TRUE; NewFormat.cbSize = sizeof(NewFormat); FindText.chrg.cpMax = -1; // search to the end for (i=0; iWindowHeading); hWndRichEdit = GetDlgItem (hDialog, IDT_RICHEDIT1); if (!ReadLogFile (((LOGVIEW_DIALOG_DATA *)lParam)->LogFileName, hWndRichEdit)) { MessageBoxFromMessage (hDialog, MSG_UNABLE_TO_SHOW_LOG, NULL, IDS_ERROR, MB_OK|MB_ICONSTOP); EndDialog (hDialog, FALSE); } // if we have the BB window, do the positioning on that. MainWindowHandle point to that window if (GetBBhwnd()) CenterWindowRelativeToWindow(hDialog, MainWindowHandle, FALSE); else pSetupCenterWindowRelativeToParent(hDialog); PostMessage(hDialog,WM_APP,0,0); break; case WM_APP: hWndRichEdit = GetDlgItem (hDialog, IDT_RICHEDIT1); SendMessage(hWndRichEdit,EM_SETSEL,0,0); SendMessage(hWndRichEdit,EM_SCROLLCARET,0,0); break; case WM_COMMAND: switch (wParam) { case IDOK: EndDialog (hDialog, TRUE); default: return FALSE; } break; default: return FALSE; } return TRUE; } BOOL ViewSetupActionLog ( IN HWND hOwnerWindow, IN PCWSTR OptionalFileName OPTIONAL, IN PCWSTR OptionalHeading OPTIONAL ) /*++ Routine Description: Formats the setup action log and displays it in a window. The log file is called setuplog.txt and it exists in the windows dir. Arguments: hOwnerWindow - handle to window that owns the dialog box OptionalFileName - full path of the file to be displayed. OptionalHeading - text to be shown at the top of the window. Return Value: Boolean value indicating whether the routine was sucessful. --*/ { LOGVIEW_DIALOG_DATA Global; // initialization data for dialog box WCHAR TmpFileName[MAX_PATH]; // used to create the log file name PCWSTR TmpHeading; // used to create the heading HANDLE hRichedDLL; // DLL used for rich edit INT Status; // what we're going to return DWORD Result; // // Form the pathname of the logfile. // if (!ARGUMENT_PRESENT(OptionalFileName)) { Result = GetWindowsDirectory (TmpFileName,MAX_PATH); if( Result == 0) { MYASSERT(FALSE); return FALSE; } pSetupConcatenatePaths (TmpFileName,SETUPLOG_ERROR_FILENAME,MAX_PATH,NULL); Global.LogFileName = pSetupDuplicateString (TmpFileName); } else { if (wcslen(OptionalFileName) > MAX_PATH) { Status = 0; goto err0; } Global.LogFileName = pSetupDuplicateString (OptionalFileName); } if (!Global.LogFileName) { Status = FALSE; goto err0; } // // Form the heading for the dialog box. // if (!ARGUMENT_PRESENT(OptionalHeading)) { TmpHeading = MyLoadString (IDS_LOG_DEFAULT_HEADING); } else { TmpHeading = pSetupDuplicateString (OptionalHeading); } if (!TmpHeading) { Status = FALSE; goto err1; } Global.WindowHeading = FormatStringMessage (IDS_LOG_WINDOW_HEADING, TmpHeading, Global.LogFileName); if (!Global.WindowHeading) { Status = FALSE; goto err2; } // // Create the dialog box. // if (!(hRichedDLL = LoadLibrary (L"RICHED20.DLL"))) { Status = FALSE; goto err3; } Status = (BOOL)DialogBoxParam (MyModuleHandle, MAKEINTRESOURCE(IDD_VIEWLOG), hOwnerWindow, DialogProc, (LPARAM) &Global); // // Clean up and return. // FreeLibrary (hRichedDLL); err3: MyFree (Global.WindowHeading); err2: MyFree (TmpHeading); err1: MyFree (Global.LogFileName); err0: return Status; } VOID LogRepairInfo( IN PCWSTR Source, IN PCWSTR Target ) { static WCHAR RepairLog[MAX_PATH]; static DWORD WinDirLength; static DWORD SourcePathLength; PWSTR SourceName; DWORD LastSourceChar, LastTargetChar; DWORD LastSourcePeriod, LastTargetPeriod; WCHAR Filename[MAX_PATH]; WCHAR Line[MAX_PATH]; WCHAR tmp[MAX_PATH]; BOOLEAN IsNtImage; ULONG Checksum; BOOLEAN Valid; DWORD Result; if(!RepairLog[0]) { // // We haven't calculated the path to setup.log yet // Result = GetWindowsDirectory( RepairLog, MAX_PATH ); if( Result == 0) { MYASSERT(FALSE); return; } WinDirLength = lstrlen( RepairLog ); pSetupConcatenatePaths( RepairLog, L"repair\\setup.log", MAX_PATH, NULL ); SourcePathLength = lstrlen( SourcePath ); } // // Only log the file if it's inside the Windows directory. // if( !wcsncmp( Target, RepairLog, WinDirLength )) { // // If we're installing an OEM driver, we shouldn't log it because we can't // repair it. Make sure the file comes from either the local source or // the windows directory (for files from driver.cab). // if (wcsncmp( Source, SourcePath, SourcePathLength ) && wcsncmp( Source, RepairLog, WinDirLength ) ) { SetupDebugPrint2(L"SETUP: oem driver not logged: %ws -> %ws.", Source, Target); return; } if( ValidateAndChecksumFile( Target, &IsNtImage, &Checksum, &Valid )) { // // Strip off drive letter. // swprintf( Filename, L"\"%s\"", Target+2 ); // // Convert source name to uncompressed form. // SourceName = pSetupDuplicateString( wcsrchr( Source, (WCHAR)'\\' ) + 1 ); if(!SourceName) { SetupDebugPrint( L"SETUP: pSetupDuplicateString failed in LogRepairInfo." ); return; } LastSourceChar = wcslen (SourceName) - 1; if(SourceName[LastSourceChar] == L'_') { LastSourcePeriod = (DWORD)(wcsrchr( SourceName, (WCHAR)'.' ) - SourceName); MYASSERT(LastSourceChar - LastSourcePeriod < 4); if(LastSourceChar - LastSourcePeriod == 1) { // // No extension - just truncate the "._" // SourceName[LastSourceChar-1] = L'\0'; } else { // // Make sure the extensions on source and target match. // If this fails, we can't log the file copy // LastTargetChar = wcslen (Target) - 1; LastTargetPeriod = (ULONG)(wcsrchr( Target, (WCHAR)'.' ) - Target); if( _wcsnicmp( SourceName + LastSourcePeriod, Target + LastTargetPeriod, LastSourceChar - LastSourcePeriod - 1 )) { SetupDebugPrint2(L"SETUP: unable to log the following file copy: %ws -> %ws.", Source, Target); MyFree (SourceName); return; } if(LastTargetChar - LastTargetPeriod < 3) { // // Short extension - just truncate the "_" // SourceName[LastSourceChar] = L'\0'; } else { // // Need to replace "_" with last character from target // MYASSERT(LastTargetChar - LastTargetPeriod == 3); SourceName[LastSourceChar] = Target[LastTargetChar]; } } } swprintf( Line, L"\"%s\",\"%x\"", SourceName, Checksum); if (GetPrivateProfileString(L"Files.WinNt",Filename,L"",tmp,sizeof(tmp)/sizeof(tmp[0]),RepairLog)) { // // there is already an entry for this file present (presumably // from textmode phase of setup.) Favor this entry over what we // are about to add // SetupDebugPrint1(L"SETUP: skipping log of %ws since it's already present in setup.log.", Target); } else { WritePrivateProfileString( L"Files.WinNt", Filename, Line, RepairLog); } MyFree (SourceName); } else { SetupDebugPrint1(L"SETUP: unable to compute checksum for %ws.", Target); } } } BOOL WINAPI pSetuplogSfcError( IN PCWSTR String, IN DWORD Index ) /*++ Routine Description: This function is used by sfc.dll to log any file signature problems when sfc is run during setup. if you change this you MUST change the caller in \nt\private\sm\sfc\dll\eventlog.c Arguments: String - pointer to a filename string for the problem file. Index - this identifies what message should be logged onto the system. Return Value: TRUE for success (the message was added to the errorlog), FALSE for failure. --*/ { DWORD MessageId; DWORD Severity; #if PRERELEASE SfcErrorOccurred = TRUE; #endif switch (Index) { case 0: MessageId= MSG_DLL_CHANGE; Severity = LogSevInformation; break; case 1: MessageId= MSG_DLL_CHANGE_FAILURE; Severity = LogSevError; break; case 2: MessageId= MSG_DLL_CACHE_COPY_ERROR; Severity = LogSevInformation; break; default: MYASSERT(FALSE && "Unknown message id pSetuplogSfcError"); return(FALSE); } return SetuplogError( SETUPLOG_SINGLE_MESSAGE | Severity, SETUPLOG_USE_MESSAGEID, MessageId, String, NULL, NULL ); }