windows-nt/Source/XPSP1/NT/base/ntsetup/syssetup/log.c

956 lines
24 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
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 <wtypes.h> // to define HRESULT for richedit.h
#include <richedit.h>
#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; i<LogSevMaximum; i++) {
Context->SeverityDescriptions[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; i<LogSevMaximum; i++) {
if (Context->SeverityDescriptions[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; i<LogSevMaximum; i++) { // load severity strings
if (!(pw = MyLoadString (IDS_LOGSEVINFO+i))) {
Status = FALSE;
goto cleanup;
}
FormatStrings[i].Find = MyMalloc((lstrlen(pw)+4)*sizeof(WCHAR));
if(!FormatStrings[i].Find) {
MyFree(pw);
Status = FALSE;
goto cleanup;
}
lstrcpy ((PWSTR)FormatStrings[i].Find, pw);
lstrcat ((PWSTR)FormatStrings[i].Find, L":\r\n");
MyFree(pw);
if(pw = MyMalloc((lstrlen(FormatStrings[i].Find)+3)*sizeof(WCHAR))) {
lstrcpy(pw,FormatStrings[i].Find);
lstrcat(pw,L"\r\n");
FormatStrings[i].Replace = pw;
} else {
Status = FALSE;
goto cleanup;
}
}
FormatStrings[LogSevMaximum].Find =
pSetupDuplicateString(SETUPLOG_ITEM_TERMINATOR);
if (!FormatStrings[LogSevMaximum].Find) {
Status = FALSE;
goto cleanup;
}
FormatStrings[LogSevMaximum].Replace = pSetupDuplicateString (NewTerm);
if (!FormatStrings[LogSevMaximum].Replace) {
Status = FALSE;
goto cleanup;
}
//
// Change 1 string at a time in the rich edit control
//
for (i=0; i<FORMATSTRINGSCOUNT; i++) {
FindText.chrg.cpMin = 0; // start search at beginning
FindText.lpstrText = (PWSTR) FormatStrings[i].Find;
//
// Search for current target until we've found each instance
//
while ((Position = (INT)SendMessage
(hWndRichEdit, EM_FINDTEXT, FR_MATCHCASE, (LPARAM) &FindText))
!= -1) {
//
// Verify that the target is at the beginning of the line
//
LineIndex = (INT)SendMessage (hWndRichEdit, EM_LINEFROMCHAR,
Position, 0);
if (SendMessage (hWndRichEdit, EM_LINEINDEX, LineIndex, 0) !=
Position) {
FindText.chrg.cpMin = Position + lstrlen (FindText.lpstrText);
continue;
}
//
// Select the target text and get its format
//
SelectRange.cpMin = Position;
SelectRange.cpMax = Position + lstrlen (FindText.lpstrText);
SendMessage (hWndRichEdit, EM_EXSETSEL, 0, (LPARAM) &SelectRange);
SendMessage (hWndRichEdit, EM_GETCHARFORMAT, TRUE,
(LPARAM) &NewFormat);
//
// Modify the target's format
//
NewFormat.dwMask = CFM_COLOR | CFM_UNDERLINE | CFM_ITALIC;
NewFormat.dwEffects &= ~CFE_AUTOCOLOR;
NewFormat.crTextColor = FormatStrings[i].Color;
NewFormat.dwEffects |= FormatStrings[i].Effects;
SendMessage (hWndRichEdit, EM_SETCHARFORMAT, SCF_SELECTION,
(LPARAM) &NewFormat);
//
// Replace the target with new text. Set the starting point for
// the next search at the end of the current string
//
if (FormatStrings[i].Replace != NULL) {
SendMessage (hWndRichEdit, EM_REPLACESEL, FALSE,
(LPARAM) FormatStrings[i].Replace);
FindText.chrg.cpMin = Position +
lstrlen (FormatStrings[i].Replace);
} else {
FindText.chrg.cpMin = Position + lstrlen (FindText.lpstrText);
}
}
}
cleanup:
for (i=0; i<=LogSevMaximum; i++) {
if (FormatStrings[i].Find) {
MyFree (FormatStrings[i].Find);
}
if (FormatStrings[i].Replace) {
MyFree (FormatStrings[i].Replace);
}
}
return Status;
}
BOOL
ReadLogFile (
PCWSTR LogFileName,
HWND hWndRichEdit
)
/*++
Routine Description:
This routine reads the log file and initializes the contents of the Rich
Edit control.
Arguments:
LogFileName - path to the file we're going to read.
hWndRichEdit - handle to the Rich Edit control.
Return Value:
Boolean indicating whether routine was successful.
--*/
{
HANDLE hLogFile; // handle to log file
EDITSTREAM eStream; // structure used by EM_STREAMIN message
hLogFile = CreateFile(
LogFileName,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hLogFile == INVALID_HANDLE_VALUE) {
hLogFile = NULL;
return FALSE;
}
//
// Read the file into the Rich Edit control.
//
eStream.dwCookie = (DWORD_PTR) hLogFile;
eStream.pfnCallback = (EDITSTREAMCALLBACK) EditStreamCallback;
eStream.dwError = 0;
SendMessage (hWndRichEdit, EM_STREAMIN, SF_TEXT, (LPARAM) &eStream);
CloseHandle (hLogFile);
if (!FormatText (hWndRichEdit)) {
return FALSE;
}
SendMessage (hWndRichEdit, EM_SETMODIFY, TRUE, 0);
return TRUE;
}
INT_PTR
DialogProc (
IN HWND hDialog,
IN UINT message,
IN WPARAM wParam,
IN LPARAM lParam
)
/*++
Routine Description:
This is the window proc for the dialog box.
Arguments:
Standard window proc arguments.
Return Value:
Bool that indicates whether we handled the message.
--*/
{
HWND hWndRichEdit; // handle to rich edit window
switch (message) {
case WM_INITDIALOG:
SetWindowText (hDialog,
((LOGVIEW_DIALOG_DATA *)lParam)->WindowHeading);
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
);
}