496 lines
14 KiB
C
496 lines
14 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1995-1997 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
autostart.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Autostart wmi loggers.
|
||
|
Takes arguments from tracing registry
|
||
|
(This code may end up in wpp framework, hence Wpp prefix)
|
||
|
|
||
|
Author:
|
||
|
|
||
|
Gor Nishanov (gorn) 29-Oct-2000
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "clusrtlp.h"
|
||
|
#include <wmistr.h>
|
||
|
#include <evntrace.h>
|
||
|
|
||
|
#define WppDebug(x,y)
|
||
|
|
||
|
#define WPPINIT_STATIC
|
||
|
|
||
|
#define WPP_REG_TRACE_REGKEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Tracing"
|
||
|
|
||
|
#define WPP_TEXTGUID_LEN 37
|
||
|
|
||
|
static TRACEHANDLE WppQueryLogger(PCWSTR LoggerName)
|
||
|
{
|
||
|
ULONG status;
|
||
|
EVENT_TRACE_PROPERTIES LoggerInfo;
|
||
|
|
||
|
ZeroMemory(&LoggerInfo, sizeof(LoggerInfo));
|
||
|
LoggerInfo.Wnode.BufferSize = sizeof(LoggerInfo);
|
||
|
LoggerInfo.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
|
||
|
|
||
|
status = QueryTraceW(0, LoggerName, &LoggerInfo);
|
||
|
WppDebug(4, ("QueryLogger(%ws) => %x:%x %d\n",
|
||
|
LoggerName, LoggerInfo.Wnode.HistoricalContext, status) );
|
||
|
if (status == ERROR_SUCCESS || status == ERROR_MORE_DATA) {
|
||
|
return (TRACEHANDLE) LoggerInfo.Wnode.HistoricalContext;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
__inline UINT WppHexVal(int ch) {
|
||
|
return isdigit(ch) ? ch - '0' : ch - 'a' + 10;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
UINT WppHex(LPCWSTR s, int n)
|
||
|
{
|
||
|
UINT res = 0;
|
||
|
while(n--) { res = res * 16 + WppHexVal(*s++); }
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
VOID
|
||
|
WppGuidFromStr(
|
||
|
IN LPCWSTR str,
|
||
|
OUT LPGUID guid)
|
||
|
{
|
||
|
guid->Data1 = WppHex(str + 0, 8);
|
||
|
guid->Data2 = (USHORT)WppHex(str + 9, 4);
|
||
|
guid->Data3 = (USHORT)WppHex(str + 14, 4);
|
||
|
guid->Data4[0] = (UCHAR) WppHex(str + 19, 2);
|
||
|
guid->Data4[1] = (UCHAR) WppHex(str + 21, 2);
|
||
|
guid->Data4[2] = (UCHAR) WppHex(str + 24, 2);
|
||
|
guid->Data4[3] = (UCHAR) WppHex(str + 26, 2);
|
||
|
guid->Data4[4] = (UCHAR) WppHex(str + 28, 2);
|
||
|
guid->Data4[5] = (UCHAR) WppHex(str + 30, 2);
|
||
|
guid->Data4[6] = (UCHAR) WppHex(str + 32, 2);
|
||
|
guid->Data4[7] = (UCHAR) WppHex(str + 34, 2);
|
||
|
}
|
||
|
|
||
|
#define WPP_BUF_SIZE(hmem) ((hmem) ? (ULONG)LocalSize(hmem) : 0)
|
||
|
|
||
|
// Make sure that the buffer is at least of size dwSize
|
||
|
WPPINIT_STATIC
|
||
|
DWORD WppGrowBuf(PVOID *Buf, DWORD dwSize)
|
||
|
{
|
||
|
DWORD status = ERROR_SUCCESS;
|
||
|
WppDebug(4, ("WppGrowBuf(%x, %d (%d)) => ", *Buf, dwSize, WPP_BUF_SIZE(*Buf)) );
|
||
|
if (*Buf == 0) {
|
||
|
*Buf = LocalAlloc(LMEM_FIXED, dwSize);
|
||
|
if (*Buf == 0) {
|
||
|
status = GetLastError();
|
||
|
}
|
||
|
} else if (LocalSize(*Buf) < dwSize) {
|
||
|
PVOID newBuf = LocalReAlloc(*Buf, dwSize, LMEM_MOVEABLE);
|
||
|
if (newBuf) {
|
||
|
*Buf = newBuf;
|
||
|
} else {
|
||
|
status = GetLastError();
|
||
|
}
|
||
|
}
|
||
|
WppDebug(4, ("(%x (%d), %d)\n", *Buf, WPP_BUF_SIZE(*Buf), status) );
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
DWORD WppRegQueryGuid(
|
||
|
IN HKEY hKey,
|
||
|
IN LPCWSTR ValueName,
|
||
|
OUT LPGUID pGuid
|
||
|
)
|
||
|
{
|
||
|
WCHAR GuidTxt[WPP_TEXTGUID_LEN];
|
||
|
DWORD status;
|
||
|
DWORD dwLen = sizeof(GuidTxt);
|
||
|
DWORD Type;
|
||
|
|
||
|
status = RegQueryValueExW(
|
||
|
hKey, // handle to key
|
||
|
ValueName, // value name
|
||
|
0, // reserved
|
||
|
&Type, // type buffer
|
||
|
(LPBYTE)GuidTxt, // data buffer //
|
||
|
&dwLen // size of data buffer
|
||
|
);
|
||
|
|
||
|
if (status != ERROR_SUCCESS || Type != REG_SZ || dwLen < 35) {
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
WppGuidFromStr(GuidTxt, pGuid);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
DWORD WppRegQueryDword(
|
||
|
IN HKEY hKey,
|
||
|
IN LPCWSTR ValueName,
|
||
|
IN DWORD Default,
|
||
|
IN DWORD MinVal,
|
||
|
IN DWORD MaxVal
|
||
|
)
|
||
|
{
|
||
|
DWORD Result = Default;
|
||
|
DWORD dwLen = sizeof(DWORD);
|
||
|
|
||
|
RegQueryValueExW(hKey, ValueName,
|
||
|
0, NULL, // lpReserved, lpType,
|
||
|
(LPBYTE)&Result, &dwLen);
|
||
|
|
||
|
if (Result < MinVal || Result > MaxVal) {
|
||
|
Result = Default;
|
||
|
}
|
||
|
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
DWORD WppRegQueryString(
|
||
|
IN HKEY hKey,
|
||
|
IN LPCWSTR ValueName,
|
||
|
IN OUT PWCHAR *Buf,
|
||
|
IN DWORD ExtraPadding // Add this amount whenever we need to alloc more memory
|
||
|
)
|
||
|
{
|
||
|
DWORD ExpandSize;
|
||
|
DWORD BufSize;
|
||
|
DWORD ValueSize = WPP_BUF_SIZE(*Buf);
|
||
|
DWORD status;
|
||
|
DWORD Type = 0;
|
||
|
|
||
|
status = RegQueryValueExW(
|
||
|
hKey, // handle to key
|
||
|
ValueName, // value name
|
||
|
0, // reserved
|
||
|
&Type, // type buffer
|
||
|
(LPBYTE)(ValueSize?*Buf:ValueName), // data buffer //
|
||
|
&ValueSize // size of data buffer
|
||
|
);
|
||
|
if (status == ERROR_MORE_DATA) {
|
||
|
if (Type == REG_EXPAND_SZ) {
|
||
|
ExtraPadding += ValueSize + 100; // Room for ExpandEnvStrings
|
||
|
}
|
||
|
status = WppGrowBuf(Buf, ValueSize + ExtraPadding);
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
return status;
|
||
|
}
|
||
|
status = RegQueryValueExW(
|
||
|
hKey, // handle to key
|
||
|
ValueName, // value name
|
||
|
0, // reserved
|
||
|
&Type, // type buffer
|
||
|
(LPBYTE)*Buf, // data buffer
|
||
|
&ValueSize // size of data buffer
|
||
|
);
|
||
|
}
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
return status;
|
||
|
}
|
||
|
if (Type == REG_SZ) {
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
if (Type != REG_EXPAND_SZ) {
|
||
|
return ERROR_DATATYPE_MISMATCH;
|
||
|
}
|
||
|
if (wcschr(*Buf, '%') == 0) {
|
||
|
// nothing to expand
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
BufSize = (ULONG)LocalSize(*Buf);
|
||
|
ExpandSize = sizeof(WCHAR) * ExpandEnvironmentStringsW(
|
||
|
*Buf, (LPWSTR)((LPBYTE)*Buf + ValueSize), (BufSize - ValueSize) / sizeof(WCHAR) ) ;
|
||
|
if (ExpandSize + ValueSize > BufSize) {
|
||
|
status = WppGrowBuf(Buf, ExpandSize + max(ExpandSize, ValueSize) + ExtraPadding );
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
return status;
|
||
|
}
|
||
|
ExpandSize = ExpandEnvironmentStringsW(*Buf, (LPWSTR)((LPBYTE)*Buf + ValueSize), ExpandSize / sizeof(WCHAR));
|
||
|
}
|
||
|
if (ExpandSize == 0) {
|
||
|
return GetLastError();
|
||
|
}
|
||
|
// Copy expanded string on top of the original one
|
||
|
MoveMemory(*Buf, (LPBYTE)*Buf + ValueSize, ExpandSize);
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
void
|
||
|
WppSetExt(LPWSTR buf, int i)
|
||
|
{
|
||
|
buf[0] = '.';
|
||
|
buf[4] = 0;
|
||
|
buf[3] = (WCHAR)('0' + i % 10); i = i / 10;
|
||
|
buf[2] = (WCHAR)('0' + i % 10); i = i / 10;
|
||
|
buf[1] = (WCHAR)('0' + i % 10);
|
||
|
}
|
||
|
|
||
|
#if !defined(WPP_DEFAULT_LOGGER_FLAGS)
|
||
|
# define WPP_DEFAULT_LOGGER_FLAGS (EVENT_TRACE_FILE_MODE_CIRCULAR | EVENT_TRACE_USE_GLOBAL_SEQUENCE)
|
||
|
#endif
|
||
|
|
||
|
// A set of buffers used by an autostart
|
||
|
// Buffers are reused between iterations and recursive invocations
|
||
|
// to minimize number of allocations
|
||
|
|
||
|
typedef struct _WPP_AUTO_START_BUFFERS {
|
||
|
PWCHAR LogSessionName;
|
||
|
PWCHAR Buf;
|
||
|
} WPP_AUTO_START_BUFFERS, *PWPP_AUTO_START_BUFFERS;
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
DWORD
|
||
|
WppReadLoggerInfo(
|
||
|
IN HKEY LoggerKey,
|
||
|
IN OUT PWPP_AUTO_START_BUFFERS x,
|
||
|
OUT TRACEHANDLE* Logger)
|
||
|
{
|
||
|
DWORD status;
|
||
|
PEVENT_TRACE_PROPERTIES Trace;
|
||
|
DWORD len, sessionNameLen;
|
||
|
|
||
|
DWORD MaxBackups = 0;
|
||
|
DWORD ExtraPadding; // add this amount when we need to allocate
|
||
|
|
||
|
status = WppRegQueryString(LoggerKey, L"LogSessionName", &x->LogSessionName, 0);
|
||
|
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
// this registry node doesn't contain a logger
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
sessionNameLen = wcslen(x->LogSessionName);
|
||
|
*Logger = WppQueryLogger(x->LogSessionName);
|
||
|
|
||
|
if (*Logger) {
|
||
|
WppDebug(1,("[WppInit] Logger %ls is already running\n", x->LogSessionName) );
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// The TraceProperties property buffer that we need to give to StartTrace
|
||
|
// should be of size EVENT_TRACE_PROPERTIES + len(sessionName) + len(logFileName)
|
||
|
// However, we don't know the length of logFileName at the moment. To eliminate
|
||
|
// extra allocations we will add ExtraPadding to an any allocation, so that the final
|
||
|
// buffer will be of required size
|
||
|
|
||
|
ExtraPadding = sizeof(EVENT_TRACE_PROPERTIES) + (sessionNameLen + 1) * sizeof(WCHAR);
|
||
|
|
||
|
status = WppRegQueryString(LoggerKey, L"LogFileName", &x->Buf, ExtraPadding);
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
WppDebug(1,("[WppInit] Read %ls\\LogFileName failed, %d\n", x->LogSessionName, status) );
|
||
|
return status;
|
||
|
}
|
||
|
len = wcslen(x->Buf);
|
||
|
|
||
|
MaxBackups = WppRegQueryDword(LoggerKey, L"MaxBackups", 0, 0, 999);
|
||
|
|
||
|
if (MaxBackups) {
|
||
|
int i, success;
|
||
|
LPWSTR FromExt, ToExt, From, To;
|
||
|
// Copy current.evm => current.evm.001, 001 => 002, etc
|
||
|
|
||
|
// MakeSure, Buffer is big enought for two file names + .000 extensions
|
||
|
WppGrowBuf(&x->Buf, (len + 5) * 2 * sizeof(WCHAR) + ExtraPadding); // .xxx\0 (5)
|
||
|
|
||
|
From = x->Buf; // MyFileName.evm MyFileName.evm.001
|
||
|
FromExt = From + len ; // ^ ^ ^ ^
|
||
|
To = FromExt + 5; // .xxx0 // From Ext1 To Ext2
|
||
|
ToExt = To + len;
|
||
|
|
||
|
memcpy(To, From, (len + 1) * sizeof(WCHAR) );
|
||
|
|
||
|
for (i = MaxBackups; i >= 1; --i) {
|
||
|
WppSetExt(ToExt, i);
|
||
|
if (i == 1) {
|
||
|
*FromExt = 0; // remove extension
|
||
|
} else {
|
||
|
WppSetExt(FromExt, i-1);
|
||
|
}
|
||
|
success = MoveFileExW(From, To, MOVEFILE_REPLACE_EXISTING);
|
||
|
if (!success) {
|
||
|
status = GetLastError();
|
||
|
} else {
|
||
|
status = ERROR_SUCCESS;
|
||
|
}
|
||
|
WppDebug(3, ("[WppInit] Rename %ls => %ls, status %d\n",
|
||
|
From, To, status) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
status = WppGrowBuf(&x->Buf, ExtraPadding + (len + 1) * sizeof(WCHAR) );
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
return status;
|
||
|
}
|
||
|
MoveMemory((LPBYTE)x->Buf + sizeof(EVENT_TRACE_PROPERTIES), x->Buf, (len + 1) * sizeof(WCHAR) ); // Free room for the header
|
||
|
|
||
|
Trace = (PEVENT_TRACE_PROPERTIES)x->Buf;
|
||
|
ZeroMemory(Trace, sizeof(EVENT_TRACE_PROPERTIES) );
|
||
|
|
||
|
Trace->Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + (len + sessionNameLen + 2) * sizeof(WCHAR);
|
||
|
Trace->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
|
||
|
|
||
|
Trace->BufferSize = WppRegQueryDword(LoggerKey, L"BufferSize", 0, 0, ~0u);
|
||
|
Trace->MinimumBuffers = WppRegQueryDword(LoggerKey, L"MinimumBuffers", 0, 0, ~0u);
|
||
|
Trace->MaximumBuffers = WppRegQueryDword(LoggerKey, L"MaximumBuffers", 0, 0, ~0u);
|
||
|
Trace->MaximumFileSize = WppRegQueryDword(LoggerKey, L"MaximumFileSize", 0, 0, ~0u);
|
||
|
Trace->LogFileMode = WppRegQueryDword(LoggerKey, L"LogFileMode", WPP_DEFAULT_LOGGER_FLAGS, 0, ~0u);
|
||
|
Trace->FlushTimer = WppRegQueryDword(LoggerKey, L"FlushTimer", 0, 0, ~0u);
|
||
|
Trace->EnableFlags = WppRegQueryDword(LoggerKey, L"EnableFlags", 0, 0, ~0u);
|
||
|
Trace->AgeLimit = WppRegQueryDword(LoggerKey, L"AgeLimit", 0, 0, ~0u);
|
||
|
|
||
|
Trace->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
|
||
|
Trace->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + (len + 1) * sizeof(WCHAR);
|
||
|
|
||
|
wcscpy((LPWSTR)((LPBYTE)x->Buf + Trace->LoggerNameOffset), x->LogSessionName);
|
||
|
|
||
|
status = StartTraceW(Logger, x->LogSessionName, Trace);
|
||
|
WppDebug(1, ("[WppInit] Logger %ls started %x:%x %d\n", x->LogSessionName, *Logger, status) );
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
typedef struct _WPP_INHERITED_DATA {
|
||
|
TRACEHANDLE Logger;
|
||
|
ULONG ControlFlags;
|
||
|
ULONG ControlLevel;
|
||
|
} WPP_INHERITED_DATA, *PWPP_INHERITED_DATA;
|
||
|
|
||
|
WPPINIT_STATIC
|
||
|
ULONG
|
||
|
WppAutoStartInternal(
|
||
|
IN HKEY Dir OPTIONAL, // if 0, use TracingKey ...
|
||
|
IN LPCWSTR ProductName,
|
||
|
IN PWPP_INHERITED_DATA InheritedData OPTIONAL,
|
||
|
IN OUT PWPP_AUTO_START_BUFFERS x // to minimize data allocations, the buffers are reused
|
||
|
)
|
||
|
{
|
||
|
ULONG status;
|
||
|
WPP_INHERITED_DATA data;
|
||
|
HKEY CloseMe = 0;
|
||
|
HKEY hk = 0;
|
||
|
DWORD dwSizeOfModuleName;
|
||
|
DWORD dwIndex;
|
||
|
GUID Guid;
|
||
|
|
||
|
WppDebug(2, ("[WppInit] Init %ls\n", ProductName) );
|
||
|
|
||
|
if (InheritedData) {
|
||
|
data = *InheritedData;
|
||
|
} else {
|
||
|
ZeroMemory(&data, sizeof(data));
|
||
|
}
|
||
|
|
||
|
if (!Dir) {
|
||
|
status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, WPP_REG_TRACE_REGKEY, 0, KEY_READ, &Dir);
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
WppDebug(1, ("[WppInit] Failed to open Trace Key, %d\n", status) );
|
||
|
goto exit_gracefully;
|
||
|
}
|
||
|
CloseMe = Dir;
|
||
|
if (WppRegQueryDword(Dir, L"NoAutoStart", 0, 0, 1) == 1) {
|
||
|
WppDebug(1, ("[WppInit] Auto-start vetoed\n") );
|
||
|
goto exit_gracefully;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
status = RegOpenKeyExW(Dir, ProductName, 0, KEY_READ, &hk);
|
||
|
if (status != ERROR_SUCCESS) {
|
||
|
WppDebug(1, ("[WppInit] Failed to open %ls subkey, %d\n", ProductName, status) );
|
||
|
goto exit_gracefully;
|
||
|
}
|
||
|
|
||
|
if (WppRegQueryDword(Dir, L"Active", 1, 0, 1) == 0) {
|
||
|
WppDebug(1, ("[WppInit] Tracing is not active for %ls\n", ProductName) );
|
||
|
goto exit_gracefully;
|
||
|
}
|
||
|
|
||
|
WppReadLoggerInfo(hk, x, &data.Logger);
|
||
|
|
||
|
data.ControlLevel = WppRegQueryDword(hk, L"ControlLevel", data.ControlLevel, 0, ~0u);
|
||
|
data.ControlFlags = WppRegQueryDword(hk, L"ControlFlags", data.ControlFlags, 0, ~0u);
|
||
|
|
||
|
if (WppRegQueryGuid(hk, L"Guid", &Guid) == ERROR_SUCCESS) {
|
||
|
|
||
|
// We can try to start tracing //
|
||
|
if (data.Logger) {
|
||
|
status = EnableTrace(1, data.ControlFlags, data.ControlLevel,
|
||
|
&Guid, data.Logger);
|
||
|
WppDebug(1, ("[WppInit] Enable %ls, status %d\n", ProductName, status) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dwSizeOfModuleName = WPP_BUF_SIZE(x->Buf);
|
||
|
dwIndex = 0;
|
||
|
while (ERROR_SUCCESS == (status = RegEnumKeyExW(hk, dwIndex,
|
||
|
x->Buf, &dwSizeOfModuleName,
|
||
|
NULL, NULL, NULL, NULL)))
|
||
|
{
|
||
|
status = WppAutoStartInternal(hk, x->Buf, &data, x);
|
||
|
|
||
|
dwSizeOfModuleName = WPP_BUF_SIZE(x->Buf);
|
||
|
++dwIndex;
|
||
|
}
|
||
|
|
||
|
if (ERROR_NO_MORE_ITEMS == status) {
|
||
|
status = ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
exit_gracefully:
|
||
|
if (CloseMe) {
|
||
|
RegCloseKey(CloseMe);
|
||
|
}
|
||
|
if (hk) {
|
||
|
RegCloseKey(hk);
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
ULONG
|
||
|
WppAutoStart(
|
||
|
IN LPCWSTR ProductName
|
||
|
)
|
||
|
{
|
||
|
WPP_AUTO_START_BUFFERS x;
|
||
|
ULONG status;
|
||
|
x.LogSessionName = 0;
|
||
|
x.Buf = 0;
|
||
|
|
||
|
if (ProductName == NULL) {
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
if( WppGrowBuf(&x.Buf, 1024) == ERROR_SUCCESS &&
|
||
|
WppGrowBuf(&x.LogSessionName, 64) == ERROR_SUCCESS )
|
||
|
{
|
||
|
|
||
|
WppDebug(1, ("[WppInit] Initialize %ls\n", ProductName) );
|
||
|
status = WppAutoStartInternal(0, ProductName, 0, &x);
|
||
|
|
||
|
} else {
|
||
|
WppDebug(1, ("[WppInit] Allocation failure\n") );
|
||
|
status = ERROR_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
LocalFree(x.Buf);
|
||
|
LocalFree(x.LogSessionName);
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|