/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: cntxtlog.c Abstract: This module implements more logging for setupapi Author: Gabe Schaffer (t-gabes) 25-Jun-1998 Revision History: Jamie Hunter (jamiehun) Apr 11 2000 - added #xnnnn identifiers Jamie Hunter (jamiehun) Feb 2 2000 - cleanup Jamie Hunter (jamiehun) Aug 31 1998 --*/ #include "precomp.h" #pragma hdrstop // // global data used by logging // struct _GlobalLogData { CRITICAL_SECTION CritSec; BOOL DoneInitCritSec; LONG UID; ULONG Flags; PTSTR FileName; } GlobalLogData; #define LogLock() EnterCriticalSection(&GlobalLogData.CritSec) #define LogUnlock() LeaveCriticalSection(&GlobalLogData.CritSec) // process-wide log counter // // C = critical // E = error // W = warning // I = information // V = verbose // T = timing // * = currently undefined // static const TCHAR LogLevelShort[17] = TEXT("CEWIVTTV********"); #define LOGLEVELSHORT_MASK (0x0f) #define LOGLEVELSHORT_INIT (0x100) #define LOGLEVELSHORT_SHIFT (4) __inline // we want to always optimize this out BOOL _WouldNeverLog( IN DWORD Level ) /*++ Routine Description: Determines if at the current logging level and the required level, we would never log inline'd for optimization (used only in this file) Arguments: Level - only required to check for special case of 0. Return Value: TRUE if we know we would never log based on passed information --*/ { if (Level == 0) { // // don't-log level // return TRUE; } if (((GlobalLogData.Flags & SETUP_LOG_LEVELMASK) <= SETUP_LOG_NOLOG) &&((GlobalLogData.Flags & DRIVER_LOG_LEVELMASK) <= DRIVER_LOG_NOLOG)) { // // Global flags indicate do no logging at all // return TRUE; } return FALSE; } __inline // we want to always optimize this out BOOL _WouldLog( IN DWORD Level ) /*++ Routine Description: Determines if at the current logging level and the required level, we would log inline'd for optimization (used only in this file) Note that if _WouldNeverLog is TRUE, _WouldLog is always FALSE if _WouldLog is TRUE, _WouldNeverLog is always FALSE if both are FALSE, then we are on "maybe" Arguments: Level - bitmask indicating logging flags. See SETUP_LOG_* and DRIVER_LOG_* at the beginning of cntxtlog.h for details. It may also be a slot returned by AllocLogInfoSlot, or 0 (no logging) Return Value: TRUE if we know we would log --*/ { if (_WouldNeverLog(Level)) { // // some simple tests (LogLevel==NULL is a not sure case) // return FALSE; } if ((Level & SETUP_LOG_IS_CONTEXT)!=0) { // // context logging - ignored here (a not sure case) // return FALSE; } // // determine logability // if ((Level & SETUP_LOG_LEVELMASK) > 0 && (Level & SETUP_LOG_LEVELMASK) <= (GlobalLogData.Flags & SETUP_LOG_LEVELMASK)) { // // we're interested in logging - raw error level // return TRUE; } if ((Level & DRIVER_LOG_LEVELMASK) > 0 && (Level & DRIVER_LOG_LEVELMASK) <= (GlobalLogData.Flags & DRIVER_LOG_LEVELMASK)) { // // we're interested in logging - driver error level // return TRUE; } return FALSE; } VOID UnMapLogFile( IN PSTR baseaddr, IN HANDLE hLogfile, IN HANDLE hMapping, IN BOOL seteof ) /*++ Routine Description: Unmap, possibly unlock, maybe set the EOF, and close a file. Note, setting EOF must occur after unmapping. Arguments: baseaddr - this is the address where the file is mapped. It must be what was returned by MapLogFile. hLogfile - this is the Win32 handle for the log file. hMapping - this is the Win32 handle to the mapping object. seteof - Boolean value indicating whether the EOF should be set to the current file pointer. If the EOF is set and the file pointer has not been moved, the EOF will be set at byte 0, thus truncating the file to 0 bytes. Return Value: NONE. --*/ { DWORD success; // // we brute-force try to close everything up // try { if (baseaddr != NULL) { success = UnmapViewOfFile(baseaddr); } } except(EXCEPTION_EXECUTE_HANDLER) { // // do nothing // } try { if (hMapping != NULL) { // // hMapping uses NULL to indicate a problem // success = CloseHandle(hMapping); } } except(EXCEPTION_EXECUTE_HANDLER) { // // do nothing // } try { if (hLogfile != INVALID_HANDLE_VALUE && seteof) { success = SetEndOfFile(hLogfile); } } except(EXCEPTION_EXECUTE_HANDLER) { // // do nothing // } try { if (hLogfile != INVALID_HANDLE_VALUE) { if (!(GlobalLogData.Flags & SETUP_LOG_NOFLUSH)) { FlushFileBuffers(hLogfile); } success = CloseHandle(hLogfile); } } except(EXCEPTION_EXECUTE_HANDLER) { // // do nothing // } // // Win9x provides no way to wait for a file to become unlocked, so we // have to poll. Putting this Sleep(0) allows others to have a chance // at the file. // Sleep(0); } VOID WriteLogFileHeader( IN HANDLE hLogFile ) /*++ Routine Description: Write general information at start of log file [SetupAPI Log] OS Version = %1!u!.%2!u!.%3!u! %4!s! Platform ID = %5!u! Service Pack = %6!u!.%7!u! Suite = 0x%8!04x! Product Type = %9!u! Arguments: hLogfile - file to write header to Return Value: NONE --*/ { #ifdef UNICODE OSVERSIONINFOEX VersionInfo; #else OSVERSIONINFO VersionInfo; #endif DWORD count; DWORD written; PTSTR buffer; PSTR ansibuffer; ULONG_PTR args[14]; DWORD MessageId = MSG_LOGFILE_HEADER_OTHER; ZeroMemory(&VersionInfo,sizeof(VersionInfo)); #ifdef UNICODE VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(!GetVersionEx((POSVERSIONINFO)&VersionInfo)) { VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(!GetVersionEx((POSVERSIONINFO)&VersionInfo)) { return; } } #else VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if(!GetVersionEx((POSVERSIONINFO)&VersionInfo)) { return; } #endif args[1] = (ULONG_PTR)VersionInfo.dwMajorVersion; args[2] = (ULONG_PTR)VersionInfo.dwMinorVersion; args[4] = (ULONG_PTR)VersionInfo.szCSDVersion; // string args[5] = (ULONG_PTR)VersionInfo.dwPlatformId; if(VersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) { args[3] = (ULONG_PTR)VersionInfo.dwBuildNumber; #ifdef UNICODE MessageId = MSG_LOGFILE_HEADER_NT; #endif } else { args[3] = (ULONG_PTR)LOWORD(VersionInfo.dwBuildNumber); // Win9x re-uses high word } #ifdef UNICODE args[6] = (ULONG_PTR)VersionInfo.wServicePackMajor; args[7] = (ULONG_PTR)VersionInfo.wServicePackMinor; args[8] = (ULONG_PTR)VersionInfo.wSuiteMask; args[9] = (ULONG_PTR)VersionInfo.wProductType; args[10] = (ULONG_PTR)pszPlatformName; #endif count = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_ARGUMENT_ARRAY|FORMAT_MESSAGE_FROM_HMODULE, MyDllModuleHandle, MessageId, 0, (LPTSTR) &buffer, 0, (va_list*)(args+1)); if (count && buffer) { #ifdef UNICODE ansibuffer = pSetupUnicodeToMultiByte(buffer,CP_ACP); if (ansibuffer) { WriteFile(hLogFile,ansibuffer,strlen(ansibuffer),&written,NULL); MyFree(ansibuffer); } #else WriteFile(hLogFile,buffer,strlen(buffer),&written,NULL); #endif LocalFree(buffer); } } DWORD MapLogFile( IN PCTSTR FileName, OUT PHANDLE hLogfile, OUT PHANDLE hMapping, OUT PDWORD dwFilesize, OUT PSTR *mapaddr, IN DWORD extrabytes ) /*++ Routine Description: Open the log file for writing and memory map it. On NT the file is locked, but Win9x doesn't allow memory mapped access to locked files, so the file is opened without FILE_SHARE_WRITE access. Since CreateFile won't block like LockFileEx, we have to poll once per second on Win9x until the file opens. Arguments: FileName - supplies path name to the log file. hLogfile - receives the Win32 file handle for the log file. hMapping - receives the Win32 handle to the mapping object. dwFileSize - receives the size of the file before it is mapped, because mapping increases the size of the file by extrabytes. mapaddr - receives the address of where the log file is mapped. extrabytes - supplies the number of extra bytes (beyond the size of the file) to add to the size of the mapping object to allow for appending the new log line and possibly a section header. Return Value: NO_ERROR if the file is successfully opened and mapped. The caller must call UnMapLogFile when finished with the file. Win32 error code if the file is not open. --*/ { HANDLE logfile = INVALID_HANDLE_VALUE; HANDLE mapping = NULL; DWORD filesize = 0; DWORD lockretrywait = 1; DWORD wait_total = 0; PSTR baseaddr = NULL; DWORD retval = ERROR_INVALID_PARAMETER; // // wrap it all up in a nice big try/except, because you just never know // try { // // give initial "failed" values // this also validates the pointers // *hLogfile = logfile; *hMapping = mapping; *dwFilesize = filesize; *mapaddr = baseaddr; do { // // retry here, in case lock fails // logfile = CreateFile( FileName, GENERIC_READ | GENERIC_WRITE, // access mode FILE_SHARE_READ, NULL, // security OPEN_ALWAYS, // open, or create if not already there //FILE_FLAG_WRITE_THROUGH, // flags - ensures that if machine crashes in the next operation, we are still logged 0, NULL); // template if (logfile == INVALID_HANDLE_VALUE) { retval = GetLastError(); if (retval != ERROR_SHARING_VIOLATION) { MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Could not create file %s. Error %d\n"), FileName, retval)); leave; } if(wait_total >= MAX_LOG_WAIT) { MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Given up waiting for log file %s.\n"), FileName)); leave; } // // don't want to wait more than a second at a time // if (lockretrywait < MAX_LOG_INTERVAL) { lockretrywait *= 2; } MYTRACE((DPFLTR_WARNING_LEVEL, TEXT("Setup: Could not open file. Error %d; waiting %ums\n"), GetLastError(), lockretrywait)); Sleep(lockretrywait); wait_total += lockretrywait; } } while (logfile == INVALID_HANDLE_VALUE); // // this will NOT work with files >= 4GB, but it's not supposed to // filesize = GetFileSize(logfile,NULL); if (filesize == 0) { // // fill some OS information into file // WriteLogFileHeader(logfile); filesize = GetFileSize(logfile,NULL); } // // make the mapping object with extra space to accomodate the new log entry // mapping = CreateFileMapping( logfile, // file to map NULL, // security PAGE_READWRITE, // protection 0, // maximum size high filesize + extrabytes, // maximum size low NULL); // name if (mapping != NULL) { // // NULL isn't a bug, CreateFileMapping returns this // to indicate error, instead of INVALID_HANDLE_VALUE // // // now we have a section object, so attach it to the log file // baseaddr = (PSTR) MapViewOfFile( mapping, // file mapping object FILE_MAP_ALL_ACCESS, // desired access 0, // file offset high 0, // file offset low 0); // number of bytes to map (0 = whole file) } else { retval = GetLastError(); MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Could not create mapping. Error %d\n"), retval)); leave; } if (baseaddr == NULL) { // // either the mapping object couldn't be created or // the file couldn't be mapped // retval = GetLastError(); MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Could not map file. Error %d\n"), retval)); leave; } // // now put everything where the caller can see it, but make sure we clean // up first // *hLogfile = logfile; *hMapping = mapping; *dwFilesize = filesize; *mapaddr = baseaddr; retval = NO_ERROR; } except(EXCEPTION_EXECUTE_HANDLER) { // // something bad happened, probably an AV, so just dump everything // and return an error meaning "Attempt to access invalid address." // } if (retval != NO_ERROR) { // // an error occurred, cleanup what we need to // UnMapLogFile(baseaddr, logfile, mapping, FALSE); } return retval; } BOOL IsSectionHeader( IN PCSTR Header, IN DWORD Size, IN PCSTR Beginning ) /*++ Routine Description: Determines whether a given string starts with a section header. This is the routine that essentially defines what a valid section header is. Arguments: Header - supplies a pointer to what may be the first character in a header. Size - supplies the length of the string passed in, which is NOT the size of the header. Beginning - supplies a pointer to the beginning of the file. Return Value: BOOL indicating if Header points to a valid section header. --*/ { // // assume a header looks like [foobar]\r\n // DWORD i; // // state holds the value we're looking for UINT state = '['; // // a section header must always be either at the start of a line or at // the beginning of a file // if (Header != Beginning && Header[-1] != '\n') return FALSE; for (i = 0; i < Size; i++) { switch (state) { case '[': if (Header[i] == '[') { state = ']'; } else { return FALSE; } break; case ']': if (Header[i] == ']') { state = '\r'; } break; case '\r': if (Header[i] == '\r') { state = '\n'; // // allow for the case where a line has a linefeed, but no CR // } else if (Header[i] == '\n') { return TRUE; } else { return FALSE; } break; case '\n': if (Header[i] == '\n') { return TRUE; } else { return FALSE; } // // break; -- commented out to avoid unreachable code error // default: MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Invalid state! (%d)\n"), state)); MYASSERT(0); } } return FALSE; } BOOL IsEqualSection( IN PCSTR Section1, IN DWORD Len1, IN PCSTR Section2, IN DWORD Len2 ) /*++ Routine Description: Says whether two ANSI strings both start with the same section header. One of the strings must be just a section header, while the other one may be anything, such as the entire log file. Arguments: Section1 - supplies the address of the first string. Len1 - supplies the length of the first string. Section2 - supplies the address of the second string. Len2 - supplies the length of the second string. Return Value: BOOL indicating if the longer string starts with the shorter string. --*/ { // // maxlen is the maximum length that both strings could be, and still be // the same section name // DWORD maxlen = Len2; if (Len1 < Len2) { maxlen = Len1; } if (_strnicmp(Section1, Section2, maxlen) == 0) { // // they're the same (ignoring case) // return TRUE; } return FALSE; } DWORD AppendLogEntryToSection( IN PCTSTR FileName, IN PCSTR Section, IN PCSTR Entry, IN BOOL SimpleAppend ) /*++ Routine Description: Opens the log file, finds the appropriate section, moves it to the end of the file, appends the new entry, and closes the file. Arguments: FileName - supplies the path name of the log file. Section - supplies the ANSI name of the section to be logged to. Entry - supplies the ANSI string to be logged. SimpleAppend - specifies whether entries will simply be appended to the log file or appended to the section where they belong. Return Value: NO_ERROR if the entry gets written to the log file. Win32 error or exception code if anything went wrong. --*/ { DWORD retval = NO_ERROR; DWORD fpoff; HANDLE hLogfile = INVALID_HANDLE_VALUE; HANDLE hMapping = NULL; DWORD filesize = 0; PSTR baseaddr = NULL; DWORD sectlen = lstrlenA(Section); DWORD entrylen = lstrlenA(Entry); DWORD error; BOOL seteof = FALSE; BOOL mapped = FALSE; PSTR eof; PSTR curptr; PSTR lastsect = NULL; try { MYASSERT(Section != NULL && Entry != NULL); sectlen = lstrlenA(Section); entrylen = lstrlenA(Entry); if (sectlen == 0 || entrylen == 0) { // // not an error as such, but not useful either // retval = NO_ERROR; leave; } error = MapLogFile( FileName, &hLogfile, &hMapping, &filesize, &baseaddr, sectlen + entrylen + 8);// add some extra space to the mapping // to take into account the log entry // +2 to terminate unterminated last line // +2 to append CRLF or ": " after section // +2 to append CRLF after entrylen if req // +2 for good measure if (error != NO_ERROR) { // // could not map file // retval = error; leave; } mapped = TRUE; eof = baseaddr + filesize; // end of file, as of now curptr = eof; while (curptr > baseaddr && (curptr[-1]==0 || curptr[-1]==0x1A)) { // // eat up trailing Nul's or ^Z's // the former is a side-effect of mapping // the latter could be introduced by an editor // curptr --; eof = curptr; } if (eof > baseaddr && eof[-1] != '\n') { // // make sure file already ends in LF // if it doesn't, append a CRLF // memcpy(eof, "\r\n", 2); eof += 2; } if (SimpleAppend) { // // instead of having a regular section header, the section name is // placed at the beginning of each log line followed by a colon. // this is particularly only of interest when debugging the logging functions // memcpy(eof, Section, sectlen); eof += sectlen; memcpy(eof, ": ", 2); eof += 2; } else { // // the entry must be appended to the correct section in the log, // which requires finding the section and moving it to the end of // the file if required. // // search backwards in the file, looking for the section header // if (eof == baseaddr) { // // truncated (empty) file // curptr = NULL; } else { curptr = eof - 1; while(curptr > baseaddr) { // // scan for section header a line at a time // going backwards, since our section should be near end // if (curptr[-1] == '\n') { // // speed optimization: only bother checking if we think we're at the beginning of a new line // this may find a '\n' that is part of a MBCS char, // but should be eliminated by IsSectionHeader check // if (IsSectionHeader(curptr, (DWORD)(eof - curptr), baseaddr)) { // // looks like a section header, now see if it's the one we want // if (IsEqualSection(curptr, (DWORD)(eof - curptr), Section, sectlen)) { // // yep - done // break; } else { // // will eventually be the section after the one of interest // lastsect = curptr; } } } curptr --; } if (curptr == baseaddr) { // // final check if we got to the beginning of the file (no find) // if (IsSectionHeader(curptr, (DWORD)(eof - curptr), baseaddr)) { // // the first line should always be a section header // if (!IsEqualSection(curptr, (DWORD)(eof - curptr), Section, sectlen)) { // // first section isn't the one of interest // so therefore we couldn't find it // curptr = NULL; } } } } if (curptr == NULL) { // // no matching section found (or file was empty) // copy the section header to the end of the file // eof is known to be actual end of file // memcpy(eof, Section, sectlen); eof += sectlen; memcpy(eof, "\r\n", 2); eof += 2; } else if (lastsect != NULL) { // // we have to rearrange the sections, as we have a case as follows: // // .... // .... // (curptr) [section A] = section of interest // .... // .... // (lastsect) [section B] = section after section of interest // .... // .... // // we want to move the text between curptr and lastsect to end of file // PSTR buffer = MyMalloc((DWORD)(lastsect - curptr)); if (buffer) { // first copy the important section to the buffer // memcpy(buffer, curptr, (size_t)(lastsect - curptr)); // // now move the rest of the thing back // memcpy(curptr, lastsect, (size_t)(eof - lastsect)); // // put the important section at the end where it belongs // memcpy(curptr - lastsect + eof, buffer, (size_t)(lastsect - curptr)); MyFree(buffer); } else { // // For some reason, we cannot allocate enough memory. // // There are 4 options here: // 1. Do nothing; this will cause the entry to be appended to // the file, but as part of the wrong section. // 2. Bail; this will cause the log entry to get lost. // 3. Create a second file to contain a temporary copy of the // section; this will require creating another file, and // then deleting it. // 4. Extend the mapping of the current file to be big enough // to hold another copy of the section; this will cause the // file to have a lot of 0s or possibly another copy of the // section, should the machine crash during the processing. // // we do option 2 - BAIL! // retval = ERROR_NOT_ENOUGH_MEMORY; leave; } } } // // now append the log entry // memcpy(eof, Entry, entrylen); eof += entrylen; if (eof[-1] != '\n') { // // entry did not supply en end of line, so we will // memcpy(eof, "\r\n", 2); eof += 2; } // // because of the memory mapping, the file size will not be correct, // so set the pointer to where we think the end of file is, and then // the real EOF will be set after unmapping, but before closing // fpoff = SetFilePointer( hLogfile, // handle of file (LONG)(eof - baseaddr), // number of bytes to move file pointer NULL, // pointer to high-order DWORD of // distance to move FILE_BEGIN); // how to move if (fpoff == (DWORD)(-1) && (error = GetLastError()) != NO_ERROR) { MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: SFP returned %u; eof = %u\n"), error, (eof - baseaddr))); retval = error; leave; } seteof = TRUE; retval = NO_ERROR; } except (EXCEPTION_EXECUTE_HANDLER) { // // invalid data // retval = ERROR_INVALID_DATA; } // // unmap // if (mapped) { UnMapLogFile(baseaddr, hLogfile, hMapping, seteof); } return retval; } VOID WriteLogSectionEntry( IN PCTSTR FileName, IN PCTSTR Section, IN PCTSTR Entry, IN BOOL SimpleAppend ) /*++ Routine Description: Convert parameters to ANSI, then append an entry to a given section of the log file. Arguments: FileName - supplies the path name of the log file. Section - supplies the name of section. Entry - supplies the string to append to section. SimpleAppend - specifies whether entries will simply be appended to the log file or appended to the section where they belong. Return Value: NONE. --*/ { PCSTR ansiSection = NULL; PCSTR ansiEntry = NULL; try { MYASSERT(Section != NULL && Entry != NULL); #ifdef UNICODE ansiSection = pSetupUnicodeToMultiByte(Section, CP_ACP); ansiEntry = pSetupUnicodeToMultiByte(Entry, CP_ACP); if(!ansiSection || !ansiEntry) { leave; } #else ansiSection = Section; ansiEntry = Entry; #endif AppendLogEntryToSection( FileName, ansiSection, ansiEntry, SimpleAppend); } except (EXCEPTION_EXECUTE_HANDLER) { // // invalid data // } #ifdef UNICODE if (ansiSection != NULL) { MyFree(ansiSection); } if (ansiEntry != NULL) { MyFree(ansiEntry); } #endif } DWORD MakeUniqueName( IN PCTSTR Component, OPTIONAL OUT PTSTR * UniqueString ) /*++ Routine Description: Create a section name that's unique by using a timestamp. If Component is supplied, append that to the timestamp. Arguments: Component - supplies a string to be included in the unique name. UniqueString - supplies a pointer to be set with return string Return Value: Error status --*/ { SYSTEMTIME now; LPTSTR buffer = NULL; DWORD status = ERROR_INVALID_DATA; ULONG sz; LONG UID; try { if (UniqueString == NULL) { // // invalid param // status = ERROR_INVALID_PARAMETER; leave; } *UniqueString = NULL; if (Component == NULL) { // // treat as empty string // Component = TEXT(""); } UID = InterlockedIncrement(&(GlobalLogData.UID)); // returns a new ID value whenever called, ensures uniqueness per process // // calculate how big string is going to be, be generous (see wsprintf below) // sz = /*[] and padding*/ 4 /*date*/ +5+3+3 /*time*/ +3+3+3 /*PID*/ +12 /*UID*/ +12 /*Component*/ +1+lstrlen(Component); buffer = MyTaggedMalloc(sz * sizeof(TCHAR),MEMTAG_LCSECTION); if (buffer == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; leave; } GetLocalTime(&now); wsprintf(buffer, TEXT("[%04d/%02d/%02d %02d:%02d:%02d %u.%u%s%s]"), now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, (UINT)GetCurrentProcessId(), (UINT)UID, (Component[0] ? TEXT(" ") : TEXT("")), Component); *UniqueString = buffer; buffer = NULL; status = NO_ERROR; } except (EXCEPTION_EXECUTE_HANDLER) { // // status remains ERROR_INVALID_DATA // } if (buffer != NULL) { MyTaggedFree(buffer,MEMTAG_LCSECTION); } return status; } DWORD CreateLogContext( IN PCTSTR SectionName, OPTIONAL IN BOOL UseDefault, OUT PSETUP_LOG_CONTEXT *LogContext ) /*++ Routine Description: Creates and initializes a SETUP_LOG_CONTEXT struct. Arguments: SectionName - supplies an initial string to be used as part of the section name. LogContext - supplies a pointer to where the pointer to the allocated SETUP_LOG_CONTEXT should be stored. Return Value: NO_ERROR in case of successful structure creation. Win32 error code in case of error. --*/ { PSETUP_LOG_CONTEXT lc = NULL; DWORD status = ERROR_INVALID_DATA; DWORD rc; try { if (LogContext == NULL) { status = ERROR_INVALID_PARAMETER; leave; } *LogContext = NULL; if (UseDefault) { lc = GetThreadLogContext(); RefLogContext(lc); } if (!lc) { lc = (PSETUP_LOG_CONTEXT) MyTaggedMalloc(sizeof(SETUP_LOG_CONTEXT),MEMTAG_LOGCONTEXT); if (lc == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; leave; } // // all fields start out at 0 // ZeroMemory(lc, sizeof(SETUP_LOG_CONTEXT)); lc->RefCount = 1; lc->ContextInfo = NULL; lc->ContextIndexes = NULL; lc->ContextBufferSize = 0; lc->ContextLastUnused = -1; lc->ContextFirstUsed = -1; lc->ContextFirstAuto = -1; rc = MakeUniqueName(SectionName,&(lc->SectionName)); if (rc != NO_ERROR) { status = rc; leave; } } *LogContext = lc; status = NO_ERROR; } except (EXCEPTION_EXECUTE_HANDLER) { // // status remains ERROR_INVALID_DATA // } if (status != NO_ERROR) { if (lc != NULL) { DeleteLogContext(lc); lc = NULL; } } return status; } DWORD AllocLogInfoSlotOrLevel( IN PSETUP_LOG_CONTEXT LogContext, IN DWORD Level, IN BOOL AutoRelease ) /*++ Routine Description: Obtain a new context stack entry for a context string only if current logging level is less verbose than specified Eg, if we specified DRIVER_LOG_VERBOSE, we will either return DRIVER_LOG_VERBOSE (if we would log it) or a slot if we would not normally log it. Arguments: LogContext - supplies a pointer to the SETUP_LOG_CONTEXT to use Level - logging level we want to always log the information at AutoRelease - if set, will release the context when dumped Return Value: Slot value to pass to logging functions, or a copy of Level note that if there is an error, 0 is returned return value can always be passed to ReleaseLogInfoSlot --*/ { if((LogContext == NULL) || _WouldNeverLog(Level)) { // // when 0 get's passed to logging functions, it will exit out very quickly // return 0; } if(_WouldLog(Level)) { // // Level specifies a verbosity level that would cause logging // return Level; } else { // // interestingly enough, we will also get here if Level is a slot // this is what we want // return AllocLogInfoSlot(LogContext,AutoRelease); } } DWORD AllocLogInfoSlot( IN PSETUP_LOG_CONTEXT LogContext, IN BOOL AutoRelease ) /*++ Routine Description: Obtain a new context stack entry for a context string Arguments: LogContext - supplies a pointer to the SETUP_LOG_CONTEXT to use AutoRelease - if set, will release the context when dumped Return Value: Slot value to pass to logging functions note that if there is an error, 0 is returned which may be safely used (means don't log) --*/ { DWORD retval = 0; LPVOID newbuffer; int newsize; int newitem; BOOL locked = FALSE; if (LogContext == NULL) { // // if they pass no LogContext - duh! // return 0; } if (((GlobalLogData.Flags & SETUP_LOG_LEVELMASK) <= SETUP_LOG_NOLOG) &&((GlobalLogData.Flags & DRIVER_LOG_LEVELMASK) <= DRIVER_LOG_NOLOG)) { // // no logging, period! Don't waste time in locked code // return 0; } try { LogLock(); locked = TRUE; if (LogContext->ContextLastUnused < 0) { // // need to allocate more // if (LogContext->ContextBufferSize >= SETUP_LOG_CONTEXTMASK) { // // too many contexts // leave; } // // need to (re)alloc buffer // newsize = LogContext->ContextBufferSize+10; if (LogContext->ContextInfo) { newbuffer = MyTaggedRealloc(LogContext->ContextInfo,sizeof(PTSTR)*(newsize),MEMTAG_LCINFO); } else { newbuffer = MyTaggedMalloc(sizeof(PTSTR)*(newsize),MEMTAG_LCINFO); } if (newbuffer == NULL) { leave; } LogContext->ContextInfo = (PTSTR*)newbuffer; if (LogContext->ContextIndexes) { newbuffer = MyTaggedRealloc(LogContext->ContextIndexes,sizeof(UINT)*(newsize),MEMTAG_LCINDEXES); } else { newbuffer = MyTaggedMalloc(sizeof(UINT)*(newsize),MEMTAG_LCINDEXES); } if (newbuffer == NULL) { leave; } LogContext->ContextIndexes = (UINT*)newbuffer; LogContext->ContextLastUnused = LogContext->ContextBufferSize; LogContext->ContextBufferSize ++; while(LogContext->ContextBufferSize < newsize) { LogContext->ContextIndexes[LogContext->ContextBufferSize-1] = LogContext->ContextBufferSize; LogContext->ContextBufferSize ++; } LogContext->ContextIndexes[LogContext->ContextBufferSize-1] = -1; } newitem = LogContext->ContextLastUnused; LogContext->ContextLastUnused = LogContext->ContextIndexes[newitem]; if(AutoRelease) { if (LogContext->ContextFirstAuto<0) { // // first auto-release context item // LogContext->ContextFirstAuto = newitem; } else { int lastitem = LogContext->ContextFirstAuto; while (LogContext->ContextIndexes[lastitem]>=0) { lastitem = LogContext->ContextIndexes[lastitem]; } LogContext->ContextIndexes[lastitem] = newitem; } } else { if (LogContext->ContextFirstUsed<0) { // // first context item // LogContext->ContextFirstUsed = newitem; } else { int lastitem = LogContext->ContextFirstUsed; while (LogContext->ContextIndexes[lastitem]>=0) { lastitem = LogContext->ContextIndexes[lastitem]; } LogContext->ContextIndexes[lastitem] = newitem; } } LogContext->ContextIndexes[newitem] = -1; // init LogContext->ContextInfo[newitem] = NULL; retval = (DWORD)(newitem) | SETUP_LOG_IS_CONTEXT; } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing special; this just allows us to catch errors // retval = 0; } if(locked) { LogUnlock(); } // // returns a logging flag (SETUP_LOG_IS_CONTEXT | n) or 0 // return retval; } VOID ReleaseLogInfoSlot( IN PSETUP_LOG_CONTEXT LogContext, DWORD Slot ) /*++ Routine Description: Releases (non auto-release) slot previously obtained Arguments: LogContext - supplies a pointer to the SETUP_LOG_CONTEXT to use Slot - supplies Slot value returned by AllocLogInfoSlot Return Value: none --*/ { int item; int lastitem; BOOL locked = FALSE; if ((Slot & SETUP_LOG_IS_CONTEXT) == 0) { // // GetLogContextMark had failed, value wasn't set, or not a context log // return; } MYASSERT(LogContext != NULL); try { LogLock(); locked = TRUE; // // log context must have been supplied // item = (int)(Slot & SETUP_LOG_CONTEXTMASK); MYASSERT(item >= 0); MYASSERT(item < LogContext->ContextBufferSize); MYASSERT(LogContext->ContextFirstUsed >= 0); // // remove item out of linked list // if (item == LogContext->ContextFirstUsed) { // // removing first in list // LogContext->ContextFirstUsed = LogContext->ContextIndexes[item]; } else { lastitem = LogContext->ContextFirstUsed; while (lastitem >= 0) { if (LogContext->ContextIndexes[lastitem] == item) { LogContext->ContextIndexes[lastitem] = LogContext->ContextIndexes[item]; break; } lastitem = LogContext->ContextIndexes[lastitem]; } } // // drop a string that hasn't been output // if (LogContext->ContextInfo[item] != NULL) { MyTaggedFree(LogContext->ContextInfo[item],MEMTAG_LCBUFFER); LogContext->ContextInfo[item] = NULL; } // // add item into free list // LogContext->ContextIndexes[item] = LogContext->ContextLastUnused; LogContext->ContextLastUnused = item; } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing special; this just allows us to catch errors // } if(locked) { LogUnlock(); } } VOID ReleaseLogInfoList( IN PSETUP_LOG_CONTEXT LogContext, IN OUT PINT ListStart ) /*++ Routine Description: Releases whole list of slots Helper function. Caller must have exclusive access to LogContext Arguments: LogContext - supplies a pointer to the SETUP_LOG_CONTEXT to use ListStart - pointer to list index Return Value: none --*/ { int item; MYASSERT(ListStart); try { if (*ListStart < 0) { // // list is empty // leave; } // // log context must have been supplied // MYASSERT(LogContext != NULL); while (*ListStart >= 0) { item = *ListStart; // item we're about to release MYASSERT(item < LogContext->ContextBufferSize); *ListStart = LogContext->ContextIndexes[item]; // next item on list (we're going to trash this index) if (LogContext->ContextInfo[item] != NULL) { MyTaggedFree(LogContext->ContextInfo[item],MEMTAG_LCBUFFER); // release string if still allocated LogContext->ContextInfo[item] = NULL; } // // add to free list // LogContext->ContextIndexes[item] = LogContext->ContextLastUnused; LogContext->ContextLastUnused = item; } } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing special; this just allows us to catch errors // } } VOID DeleteLogContext( IN PSETUP_LOG_CONTEXT LogContext ) /*++ Routine Description: Decrement ref count of LogContext, and delete if zero. Arguments: LogContext - supplies a pointer to the SETUP_LOG_CONTEXT to be deleted. Return Value: NONE. --*/ { BOOL locked = FALSE; if (!LogContext) { return; } try { LogLock(); locked = TRUE; // // check ref count // MYASSERT(LogContext->RefCount > 0); if (--LogContext->RefCount) { leave; } // // we can unlock now, since we have exclusive access to this context (it is unowned) // and we don't want to hold global lock longer than needed // LogUnlock(); locked = FALSE; ReleaseLogInfoList(LogContext,&LogContext->ContextFirstAuto); ReleaseLogInfoList(LogContext,&LogContext->ContextFirstUsed); if (LogContext->SectionName) { MyTaggedFree(LogContext->SectionName,MEMTAG_LCSECTION); } if (LogContext->Buffer) { MyTaggedFree(LogContext->Buffer,MEMTAG_LCBUFFER); } if (LogContext->ContextInfo) { MyTaggedFree(LogContext->ContextInfo,MEMTAG_LCINFO); } if (LogContext->ContextIndexes) { MyTaggedFree(LogContext->ContextIndexes,MEMTAG_LCINDEXES); } // // now deallocate the struct // MyTaggedFree(LogContext,MEMTAG_LOGCONTEXT); } except (EXCEPTION_EXECUTE_HANDLER) { // // cleanup below // } // // if we have not yet released global lock, release it now // if(locked) { LogUnlock(); } return; } DWORD RefLogContext( // increment reference count IN PSETUP_LOG_CONTEXT LogContext ) /*++ Routine Description: Increment the reference count on a SETUP_LOG_CONTEXT object. Arguments: LogContext - supplies a pointer to a valid SETUP_LOG_CONTEXT object. If NULL, this is a NOP. Return Value: DWORD containing old reference count. --*/ { DWORD ref = 0; BOOL locked = FALSE; if (LogContext == NULL) { return 0; } try { LogLock(); locked = TRUE; ref = LogContext->RefCount++; MYASSERT(LogContext->RefCount); } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing; this just allows us to catch errors // } if(locked) { LogUnlock(); } return ref; } VOID SendLogString( IN PSETUP_LOG_CONTEXT LogContext, IN PCTSTR Buffer ) /*++ Routine Description: Send a string to the logfile and/or debugger based on settings. It's expected that LogLock has been called prior to calling this function LogLock causes per-process thread synchronisation Arguments: LogContext - supplies a pointer to a valid SETUP_LOG_CONTEXT object. Buffer - supplies the buffer to be sent to the logfile/debugger. Return Value: NONE. --*/ { int len; try { MYASSERT(LogContext); MYASSERT(Buffer); if (Buffer[0] == 0) { // // useless call // leave; } if (GlobalLogData.FileName) { WriteLogSectionEntry( GlobalLogData.FileName, LogContext->SectionName, Buffer, (GlobalLogData.Flags & SETUP_LOG_SIMPLE) ? TRUE : FALSE); } // // do debugger output here // if (GlobalLogData.Flags & SETUP_LOG_DEBUGOUT) { DebugPrintEx(DPFLTR_ERROR_LEVEL, TEXT("SetupAPI: %s: %s"), LogContext->SectionName, Buffer); len = lstrlen(Buffer); if (Buffer[len-1] != TEXT('\n')) { DebugPrintEx(DPFLTR_ERROR_LEVEL, TEXT("\r\n")); } } } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing; this just allows us to catch errors // } } DWORD pSetupWriteLogEntry( IN PSETUP_LOG_CONTEXT LogContext, OPTIONAL IN DWORD Level, IN DWORD MessageId, IN PCTSTR MessageStr, OPTIONAL ... OPTIONAL ) /*++ Routine Description: Write a log entry to a file or debugger. If MessageId is 0 and MessageStr is NULL, the LogContext's buffer will be flushed. Arguments: LogContext - optionally supplies a pointer to the SETUP_LOG_CONTEXT to be used for logging. If not supplied, a temporary one is created just for a single use. Level - bitmask indicating logging flags. See SETUP_LOG_* and DRIVER_LOG_* at the beginning of cntxtlog.h for details. It may also be a slot returned by AllocLogInfoSlot, or 0 (no logging) MessageId - ID of string from string table. Ignored if MessageStr is supplied. The string may contain formatting codes for FormatMessage. MessageStr - optionally supplies string to be formatted with FormatMessage. If not supplied, MessageId is used instead. ... - supply optional parameters based on string to be formatted. Return Value: Win32 error code. --*/ { PSETUP_LOG_CONTEXT lc = NULL; DWORD retval = NO_ERROR; DWORD error; DWORD flags; DWORD context = 0; DWORD logmask; DWORD count; LPVOID source = NULL; PTSTR buffer = NULL; PTSTR locbuffer = NULL; PTSTR buffer2 = NULL; va_list arglist; BOOL logit = FALSE; BOOL timestamp = FALSE; BOOL endsync = FALSE; SYSTEMTIME now; TCHAR scratch[1024]; int logindex; int thisindex; int numeric=0; try { // // return immediately if we know we'll never log // if (_WouldNeverLog(Level)) { retval = NO_ERROR; leave; } if ((Level & SETUP_LOG_IS_CONTEXT)!=0) { // // write to context slot // if(Level & ~SETUP_LOG_VALIDCONTEXTBITS) { MYASSERT((Level & ~SETUP_LOG_VALIDCONTEXTBITS)==0); retval = ERROR_INVALID_PARAMETER; leave; } if ((GlobalLogData.Flags & SETUP_LOG_ALL_CONTEXT)!=0) { // // don't treat as context - log it anyway // Level = 0; logit = TRUE; } else if (LogContext) { // // determine which slot // context = Level & SETUP_LOG_CONTEXTMASK; Level = SETUP_LOG_IS_CONTEXT; // effective log level, we've stripped out log context logit = TRUE; } else { // // can't write something to slot if there's no LogContext // leave; } } if(!logit) { // // we're still not sure if we'll end up logging this, let's see if we should log this based on level rules // logit = _WouldLog(Level); if (!logit) { leave; } } if (LogContext == NULL) { // // if they pass no LogContext and they want buffering, this call's a nop // if (Level & SETUP_LOG_BUFFER) { retval = NO_ERROR; leave; } // // now make a temporary context // error = CreateLogContext(NULL, TRUE, &lc); if (error != NO_ERROR) { lc = NULL; retval = error; leave; } LogContext = lc; } // // after this point, we know we're going to log something, and we know we have a LogContext // note that going down this path is a perf hit. // anything we can do in reducing number of times we go down here for "context" information is good // // hold the lock through to cleanup. It is needed for ReleaseLogInfoList, // LogContext modifications and will reduce conflicts when actually writing to the log file // LogLock(); endsync = TRUE; // indicate we need to release later timestamp = (GlobalLogData.Flags & SETUP_LOG_TIMESTAMP) || ((Level & DRIVER_LOG_LEVELMASK) >= DRIVER_LOG_TIME) || ((Level & SETUP_LOG_LEVELMASK) >= SETUP_LOG_TIME) || (((Level & SETUP_LOG_LEVELMASK) > 0) && (SETUP_LOG_TIMEALL <= (GlobalLogData.Flags & SETUP_LOG_LEVELMASK))) || (((Level & DRIVER_LOG_LEVELMASK) > 0) && (DRIVER_LOG_TIMEALL <= (GlobalLogData.Flags & DRIVER_LOG_LEVELMASK))); if ((Level & SETUP_LOG_IS_CONTEXT) == FALSE) { // // only do this if we're about to do REAL logging // // if this is the first log output in the section, we will give the // command line and module to help the user see what's going on // if (LogContext->LoggedEntries==0) { // // recursively call ourselves to log what the command line is // note that some apps (eg rundll32) will go and trash command line // if this is the case, try and do the right thing // we're willing to spend a little extra time in this case, since we know we're going to // log something to the section, and we'll only do this once per section // PTSTR CmdLine = GetCommandLine(); LogContext->LoggedEntries++; // stop calling this code when we do the pSetupWriteLogEntry's below if (CmdLine[0] == TEXT('\"')) { CmdLine++; } if(_tcsnicmp(ProcessFileName,CmdLine,_tcslen(ProcessFileName))==0) { // // commandline is prefixed with process file name // chance is it's good // pSetupWriteLogEntry( LogContext, AllocLogInfoSlot(LogContext,TRUE), // delayed slot MSG_LOG_COMMAND_LINE, NULL, GetCommandLine()); } else { // // it appears that the command line has been modified somewhat // so show what we have // pSetupWriteLogEntry( LogContext, AllocLogInfoSlot(LogContext,TRUE), // delayed slot MSG_LOG_BAD_COMMAND_LINE, NULL, ProcessFileName, GetCommandLine()); #ifdef UNICODE { // // UNICODE only // // now see if we can get something more useful by looking at the ANSI command line buffer // PSTR AnsiProcessFileName = pSetupUnicodeToMultiByte(ProcessFileName,CP_ACP); PSTR AnsiCmdLine = GetCommandLineA(); if (AnsiCmdLine[0] == '\"') { AnsiCmdLine++; } if(AnsiProcessFileName && _mbsnicmp(AnsiProcessFileName,AnsiCmdLine,_mbslen(AnsiProcessFileName))==0) { // // well, the Ansi version appears ok, let's use that // pSetupWriteLogEntry( LogContext, AllocLogInfoSlot(LogContext,TRUE), // delayed slot MSG_LOG_COMMAND_LINE_ANSI, NULL, GetCommandLineA()); } else { // // appears that both Unicode and Ansi might be bad // AnsiCmdLine = pSetupUnicodeToMultiByte(GetCommandLine(),CP_ACP); if (AnsiCmdLine && _mbsicmp(AnsiCmdLine,GetCommandLineA())!=0) { // // also log ansi as reference, since it's different // pSetupWriteLogEntry( LogContext, AllocLogInfoSlot(LogContext,TRUE), // delayed slot MSG_LOG_BAD_COMMAND_LINE_ANSI, NULL, GetCommandLineA()); } if (AnsiCmdLine) { MyFree(AnsiCmdLine); } } if (AnsiProcessFileName) { MyFree(AnsiProcessFileName); } } #endif // UNICODE } #ifdef UNICODE #ifndef _WIN64 // // we're running 32-bit setupapi // if (IsWow64) { // // we're running it under WOW64 // pSetupWriteLogEntry( LogContext, AllocLogInfoSlot(LogContext,TRUE), // delayed slot MSG_LOG_WOW64, NULL, GetCommandLine()); } #endif #endif // UNICODE } } flags = FORMAT_MESSAGE_ALLOCATE_BUFFER; // // if MessageStr is supplied, we use that; otherwise use a // string from a string table // if (MessageStr) { flags |= FORMAT_MESSAGE_FROM_STRING; source = (PTSTR) MessageStr; // cast away const } else if (MessageId) { // // the message ID may be an HRESULT error code // if (MessageId & 0xC0000000) { flags |= FORMAT_MESSAGE_FROM_SYSTEM; // // Some system messages contain inserts, but whomever is calling // will not supply them, so this flag prevents us from // tripping over those cases. // flags |= FORMAT_MESSAGE_IGNORE_INSERTS; } else { flags |= FORMAT_MESSAGE_FROM_HMODULE; source = MyDllModuleHandle; numeric = (int)(MessageId-MSG_LOG_FIRST); } } if (MessageStr || MessageId) { va_start(arglist, MessageStr); count = FormatMessage( flags, source, MessageId, 0, // LANGID (LPTSTR) &locbuffer, 0, // minimum size of buffer &arglist); } else { // // There is no string to format, so we are probably just // flushing the buffer. // count = 1; } if (count > 0) { // // no error; prefix string with a code and place into a MyMalloc allocated buffer // we don't want to prefix string with a code if we're appending to an existing message // if (locbuffer) { if ((numeric > 0) && (LogContext->Buffer==NULL)) { // // determine level code, which indicates severity for why we logged this // and machine readable ID // if (Level & SETUP_LOG_IS_CONTEXT) { // // if this is context information, use #-xxxx // _stprintf(scratch,TEXT("#-%03d "),numeric); } else { logindex = LOGLEVELSHORT_INIT; // maps to 0. after >>4&0x0f. if ((Level & SETUP_LOG_LEVELMASK) > 0 && (Level & SETUP_LOG_LEVELMASK) <= (GlobalLogData.Flags & SETUP_LOG_LEVELMASK)) { thisindex = (Level & SETUP_LOG_LEVELMASK) >> SETUP_LOG_SHIFT; if (thisindex < logindex) { logindex = thisindex; } } if ((Level & DRIVER_LOG_LEVELMASK) > 0 && (Level & DRIVER_LOG_LEVELMASK) <= (GlobalLogData.Flags & DRIVER_LOG_LEVELMASK)) { thisindex = (Level & DRIVER_LOG_LEVELMASK) >> DRIVER_LOG_SHIFT; if (thisindex < logindex) { logindex = thisindex; } } // // #Cxxxx #Vxxxx etc // _stprintf(scratch,TEXT("#%c%03d "),LogLevelShort[(logindex>>LOGLEVELSHORT_SHIFT)&LOGLEVELSHORT_MASK],numeric); } } else { scratch[0] = TEXT('\0'); } buffer = (PTSTR)MyTaggedMalloc((lstrlen(scratch)+lstrlen(locbuffer)+1)*sizeof(TCHAR),MEMTAG_LCBUFFER); if (buffer) { lstrcpy(buffer,scratch); lstrcat(buffer,locbuffer); } LocalFree(locbuffer); } else { buffer = NULL; } // // Check to see if the buffer has anything in it. If so, the newest // string needs to be appended to it. // if (LogContext->Buffer) { // // in case of a flush, buffer == NULL // if (buffer!=NULL) { int blen = lstrlen(LogContext->Buffer); int pad = 0; TCHAR lastchr = *CharPrev(LogContext->Buffer,LogContext->Buffer+blen); if (lastchr != TEXT(' ')) { // // silently correct any errors in the message text (which should end in a space) // while((lastchr == TEXT('\t')) || (lastchr == TEXT('\r')) || (lastchr == TEXT('\n'))) { blen--; // these characters are always sizeof(TCHAR) lastchr = *CharPrev(LogContext->Buffer,LogContext->Buffer+blen); } LogContext->Buffer[blen] = TEXT('\0'); if (lastchr != TEXT(' ')) { // // we want to insert a space padding // pad++; } } buffer2 = MyTaggedRealloc(LogContext->Buffer, (blen + pad + lstrlen(buffer) + 1) * sizeof(TCHAR), MEMTAG_LCBUFFER ); // // if the realloc was successful, add the new data, otherwise // just drop it on the floor // if (buffer2) { if (pad) { lstrcat(buffer2,TEXT(" ")); } lstrcat(buffer2, buffer); LogContext->Buffer = buffer2; buffer2 = NULL; } MyTaggedFree(buffer,MEMTAG_LCBUFFER); buffer = NULL; } buffer = LogContext->Buffer; LogContext->Buffer = NULL; } if (Level & SETUP_LOG_BUFFER) { LogContext->Buffer = buffer; buffer = NULL; } else if (Level & SETUP_LOG_IS_CONTEXT) { PTSTR TempDupeString; // // replace the string indicated // if(buffer) { if (LogContext->ContextInfo[context]) { MyTaggedFree(LogContext->ContextInfo[context],MEMTAG_LCBUFFER); } LogContext->ContextInfo[context] = buffer; buffer = NULL; } } else { int item; // // actually do some logging // LogContext->LoggedEntries++; if (!LogContext->SectionName) { error = MakeUniqueName(NULL,&(LogContext->SectionName)); } // // first dump the auto-release context info // item = LogContext->ContextFirstAuto; while (item >= 0) { if (LogContext->ContextInfo[item]) { // // dump this string // SendLogString(LogContext, LogContext->ContextInfo[item]); MyTaggedFree (LogContext->ContextInfo[item],MEMTAG_LCBUFFER); LogContext->ContextInfo[item] = NULL; } item = LogContext->ContextIndexes[item]; } ReleaseLogInfoList(LogContext,&LogContext->ContextFirstAuto); // // now dump any strings set in currently allocated slots // item = LogContext->ContextFirstUsed; while (item >= 0) { if (LogContext->ContextInfo[item]) { // // dump this string // SendLogString(LogContext, LogContext->ContextInfo[item]); MyTaggedFree (LogContext->ContextInfo[item],MEMTAG_LCBUFFER); LogContext->ContextInfo[item] = NULL; } item = LogContext->ContextIndexes[item]; } // // we have built up a line to send // if (buffer != NULL) { if(timestamp) { // // this is the point we're interested in prefixing with timestamp // this allows us to build up a string, then emit it prefixed with stamp // GetLocalTime(&now); _stprintf(scratch, TEXT("@ %02d:%02d:%02d.%03d "), now.wHour, now.wMinute, now.wSecond, now.wMilliseconds); buffer2 = MyTaggedMalloc((lstrlen(scratch)+lstrlen(buffer)+1)*sizeof(TCHAR),MEMTAG_LCBUFFER); if (buffer2) { lstrcpy(buffer2,scratch); lstrcat(buffer2,buffer); MyTaggedFree(buffer,MEMTAG_LCBUFFER); buffer = buffer2; buffer2 = NULL; } } SendLogString(LogContext,buffer); } } } else { // // the FormatMessage failed // retval = GetLastError(); if(retval == NO_ERROR) { retval = ERROR_INVALID_DATA; } } } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing special; this just allows us to catch errors // retval = ERROR_INVALID_DATA; } // // cleanup // if (endsync) { LogUnlock(); } if (buffer) { MyTaggedFree(buffer,MEMTAG_LCBUFFER); } if (lc) { DeleteLogContext(lc); } return retval; } VOID SetLogSectionName( IN PSETUP_LOG_CONTEXT LogContext, IN PCTSTR SectionName ) /*++ Routine Description: Sets the section name for the log context if it hasn't been used. Arguments: LogContext - supplies pointer to SETUP_LOG_CONTEXT. SectionName - supplies a pointer to a string to be included in the section name. Return Value: NONE. --*/ { DWORD rc; PTSTR NewSectionName = NULL; BOOL locked = FALSE; MYASSERT(LogContext); MYASSERT(SectionName); try { LogLock(); locked = TRUE; // // make sure the entry has never been used before // if (LogContext->LoggedEntries==0 || LogContext->SectionName==NULL) { // // get rid of any previous name // rc = MakeUniqueName(SectionName,&NewSectionName); if (rc == NO_ERROR) { if (LogContext->SectionName) { MyTaggedFree(LogContext->SectionName,MEMTAG_LCSECTION); } LogContext->SectionName = NewSectionName; } } } except (EXCEPTION_EXECUTE_HANDLER) { } if(locked) { LogUnlock(); } } #if MEM_DBG #undef InheritLogContext // defined again below #endif DWORD InheritLogContext( IN TRACK_ARG_DECLARE TRACK_ARG_COMMA IN PSETUP_LOG_CONTEXT Source, OUT PSETUP_LOG_CONTEXT *Dest ) /*++ Routine Description: Copies a log context from one structure to another, deleting the one that gets overwritten. If Source and Dest are both NULL, a new log context is created for Dest. Arguments: Source - supplies pointer to source SETUP_LOG_CONTEXT. If NULL, this creates a new log context for Dest. Dest - supplies the location to receive a pointer to the log context. Return Value: NONE. --*/ { DWORD status = ERROR_INVALID_DATA; DWORD rc; PSETUP_LOG_CONTEXT Old = NULL; TRACK_PUSH try { MYASSERT(Dest); Old = *Dest; if (Old == NULL && Source == NULL) { // // this is a roundabout way of saying we want to create a context // used when the source logcontext is optional // rc = CreateLogContext(NULL, TRUE, Dest); if (rc != NO_ERROR) { status = rc; leave; } } else if (Source != NULL && (Old == NULL || Old->LoggedEntries == 0)) { // // We can replace Dest, since it hasn't been used yet // *Dest = Source; RefLogContext(Source); if (Old != NULL) { // // now delete old // DeleteLogContext(Old); } } status = NO_ERROR; } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing; this just allows us to catch errors // } TRACK_POP return status; } #if MEM_DBG #define InheritLogContext(a,b) InheritLogContext(TRACK_ARG_CALL,a,b) #endif DWORD ShareLogContext( IN OUT PSETUP_LOG_CONTEXT *Primary, IN OUT PSETUP_LOG_CONTEXT *Secondary ) /*++ Routine Description: Bidirectional inherit Arguments: Primary - preferred source Secondary - preferred target Return Value: any potential error --*/ { DWORD rc = ERROR_INVALID_DATA; try { MYASSERT(Primary); MYASSERT(*Primary); MYASSERT(Secondary); MYASSERT(*Secondary); if((*Secondary)->LoggedEntries) { // // secondary has already been used, so see if we can update primary // rc = InheritLogContext(*Secondary,Primary); } else { // // else behave exactly like InheritLogContext // rc = InheritLogContext(*Primary,Secondary); } } except (EXCEPTION_EXECUTE_HANDLER) { // // do nothing; this just allows us to catch errors // } return rc; } VOID pSetupWriteLogError( IN PSETUP_LOG_CONTEXT LogContext, OPTIONAL IN DWORD Level, IN DWORD Error ) /*++ Routine Description: Logs an error code and an error message on the same line. Arguments: LogContext - supplies a pointer to a valid SETUP_LOG_CONTEXT object. If NULL, this is a NOP. Level - supplies a log level as defined by pSetupWriteLogEntry. Error - supplies the Win32 error, HRESULT, or SETUPAPI error code to log. Return Value: NONE. --*/ { DWORD err; if (!LogContext) { // // error is meaningless without context // goto final; } if (Error == NO_ERROR) { pSetupWriteLogEntry( LogContext, Level, MSG_LOG_NO_ERROR, NULL); goto final; } pSetupWriteLogEntry( LogContext, Level | SETUP_LOG_BUFFER, // // print HRESULTs in hex, Win32 errors in decimal // (Error & 0xC0000000 ? MSG_LOG_HRESULT_ERROR : MSG_LOG_WIN32_ERROR), NULL, Error); // // If it's a Win32 error, we convert it to an HRESULT, because // pSetupWriteLogEntry only knows that it's an error code by the fact // that it's an HRESULT. However, we don't want the user to // get an HRESULT if we can help it, so just do the conversion // after converting to a string. Also, SETUPAPI errors are not // in proper HRESULT format without conversion // Error = HRESULT_FROM_SETUPAPI(Error); // // writing the error message may fail... // err = pSetupWriteLogEntry( LogContext, Level, Error, NULL); if (err != NO_ERROR) { pSetupWriteLogEntry( LogContext, Level, MSG_LOG_UNKNOWN_ERROR, NULL); } final: SetLastError(Error); } BOOL ContextLoggingTlsInit( IN BOOL Init ) /*++ Routine Description: Init = TRUE Initializes per-thread data for logging Init = FALSE releases memory on cleanup Arguments: Init - set to initialize Return Value: TRUE if initialized ok. --*/ { BOOL b = FALSE; PSETUP_TLS pTLS; PSETUP_LOG_TLS pLogTLS; pTLS = SetupGetTlsData(); MYASSERT(pTLS); pLogTLS = &pTLS->SetupLog; if (Init) { pLogTLS->ThreadLogContext = NULL; b = TRUE; } else { // // ISSUE-JamieHun-2001/05/01 ASSERT when thread terminated // thread might not have terminated cleanly // causing this assert to fire // // MYASSERT(!pLogTLS->ThreadLogContext); b = TRUE; } return b; } BOOL SetThreadLogContext( IN PSETUP_LOG_CONTEXT LogContext, OUT PSETUP_LOG_CONTEXT *PrevContext OPTIONAL ) /*++ Routine Description: Modify current thread log context Arguments: LogContext new log context (expected to be apropriately ref counted) PrevContext if set, filled with previous context Return Value: TRUE if set ok. --*/ { PSETUP_TLS pTLS; PSETUP_LOG_TLS pLogTLS; PSETUP_LOG_CONTEXT Top; pTLS = SetupGetTlsData(); if(!pTLS) { return FALSE; } pLogTLS = &pTLS->SetupLog; if (PrevContext) { *PrevContext = pLogTLS->ThreadLogContext; } pLogTLS->ThreadLogContext = LogContext; return TRUE; } PSETUP_LOG_CONTEXT GetThreadLogContext( ) /*++ Routine Description: Return thread's default log context Arguments: NONE Return Value: Current LogContext or NULL --*/ { PSETUP_TLS pTLS; pTLS = SetupGetTlsData(); if(!pTLS) { return NULL; } return pTLS->SetupLog.ThreadLogContext; } BOOL InitializeContextLogging( IN BOOL Attach ) /*++ Routine Description: Initializes structures/data for logging or releases allocated memory. Arguments: Attach - set when called at attach time as opposed to detach time Return Value: TRUE if initialized ok. --*/ { BOOL Successful = FALSE; if (Attach) { LONG error; HKEY key; HKEY loglevel; DWORD len; DWORD level = 0; DWORD type; PTSTR PathName = NULL; TCHAR testchar; BOOL isdir = FALSE; GlobalLogData.FileName = NULL; GlobalLogData.Flags = 0; GlobalLogData.UID = 0; GlobalLogData.DoneInitCritSec = FALSE; try { InitializeCriticalSection(&GlobalLogData.CritSec); GlobalLogData.DoneInitCritSec = TRUE; error = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SETUP REGSTR_KEY_SETUP, 0, // reserved KEY_QUERY_VALUE, &key); if (error == ERROR_SUCCESS) { if(QueryRegistryDwordValue(key,SP_REGKEY_LOGLEVEL,&level) != NO_ERROR) { level = 0; } if(QueryRegistryValue(key,SP_REGKEY_LOGPATH,&PathName,&type,&len) != NO_ERROR) { PathName = NULL; } // // Allow a user to override the log level for a particular program // error = RegOpenKeyEx( key, SP_REGKEY_APPLOGLEVEL, 0, // reserved KEY_QUERY_VALUE, &loglevel); if (error == ERROR_SUCCESS) { DWORD override; if(QueryRegistryDwordValue(key,SP_REGKEY_LOGLEVEL,&override) == NO_ERROR) { level = override; } RegCloseKey(loglevel); } RegCloseKey(key); } // // if they don't supply a valid name, we use the Windows dir // if (!(PathName && PathName[0])) { if(PathName) { MyFree(PathName); } PathName = DuplicateString(WindowsDirectory); if(!PathName) { leave; } isdir = TRUE; // we know this should be a directory } else { // // see if we're pointing at a directory // testchar = CharPrev(PathName,PathName+lstrlen(PathName))[0]; if(testchar == TEXT('\\') || testchar == TEXT('/')) { // // explicit directiory // isdir = TRUE; } else { DWORD attr = GetFileAttributes(PathName); if (isdir || (attr != (DWORD)(-1) && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 )) { // // implicit directory // isdir = TRUE; } } } if (isdir) { // // if they gave a directory, add a filename // LPTSTR NewPath; if(!pSetupAppendPath(PathName,SP_LOG_FILENAME,&NewPath)) { MyFree(PathName); PathName = NULL; leave; } MyFree(PathName); PathName = NewPath; } pSetupMakeSurePathExists(PathName); // // validate level flags // level &= SETUP_LOG_VALIDREGBITS; // // handle defaults // if((level & SETUP_LOG_LEVELMASK) == 0) { // // level not explicitly set // level |= SETUP_LOG_DEFAULT; } if((level & DRIVER_LOG_LEVELMASK) == 0) { // // level not explicitly set // level |= DRIVER_LOG_DEFAULT; } GlobalLogData.Flags = level; GlobalLogData.FileName = PathName; PathName = NULL; if (GlobalLogData.FileName == NULL) { leave; } Successful = TRUE; } except (EXCEPTION_EXECUTE_HANDLER) { // // Successful remains FALSE // } } else { if (GlobalLogData.FileName) { MyFree(GlobalLogData.FileName); GlobalLogData.FileName = NULL; } if(GlobalLogData.DoneInitCritSec) { DeleteCriticalSection(&GlobalLogData.CritSec); } Successful = TRUE; } return Successful; }