Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1455 lines
42 KiB

/*++
Copyright (c) 1995 Microsoft Corporation
Module Name :
ilogsql.cxx
Abstract:
This file contains the member functions for INET_SQL_LOG
for SQL logging using ODBC.
Author:
Murali R. Krishnan ( MuraliK ) 15-Feb-1995
Environment:
User Mode -- Win32
Project:
Internet Services Common DLL
Functions Exported:
INET_SQL_LOG::INET_SQL_LOG()
INET_SQL_LOG::~INET_SQL_LOG()
INET_SQL_LOG::Open()
INET_SQL_LOG::Close()
INET_SQL_LOG::Print()
INET_SQL_LOG::LogInformation()
Revision History:
MuraliK 15-May-1994 Extended the LogInformation ==>
modified sql output
MuraliK 28-Jun-1995 Added ANSI API for LogInformation()
MuraliK 08-Jan-1996 Use Quote chars for SQL identifiers
--*/
/*********
**********
A BIG NOTE
ODBC APIs are ANSI based. Atleast the version available for public release
as of Feb 12, 1995. ==> We do UNICODE to ANSI conversions to access
ODBC APIs.
This can potentially be a problem later. Will be investigated later.
The interface from INET_SQL_LOG is UNICODE, so that later if ODBC supports
UNICODE we can achieve the desired action easily.
Till then live with UNICODE/ANSI conflicts.
-MuraliK ( 18-Feb-1995)
**********
**********/
/*++
Implementation note on writing log records to a database using ODBC.
Log records consist of information obtained from INETLOG_INFORMATION
structure.
They are inserted into an SQL database using ODBC gateway.
Insertions can be done in 2 ways:
1) construct a complete sql command with the values to be
inserted, in a buffer.
Call SQLExecDirect() ( or ODBC_STATEMENT::ExecDirect())
and insert the statement.
+ Easy to form the buffer
+ No State needs to be maintained after insertions.
- Too much overhead to create the buffer and destroy it later
- Each ExecDirect() call results in parsing the SQL command
==> Inefficient
2) construct an incomplete SQL command with '?' for unknown values.
Use SQLPrepare() ( or ODBC_STATEMENT::PrepareStatement())
to prepare the statement.
Also use SQLBindParameter ( or ODBC_STATEMENT::BindParameter())
to create parameter markers and bind them to the statement.
Each parameter marker state is maintained in ODBC_PARAMETER object.
Each time when a new value comes in,
copy the new value to the buffer in ODBC_PARAMETER.
call ODBC_STATEMENT::Execute() which executes the prepared statement
+ No need to parse a statement once prepared.
+ Just copy parameters and execute the statement ==> simple!
- need to maintain some state about prepared statment and
parameter markers.
Of these I chose option 2, because maintaining the minimal state
about statement is done by ODBC_STATEMENT object.
about parameter markers is maintained in INET_SQL_LOG object.
The cost of parsing an SQL statement as required by option
1 is VERY high. But 2 avoids this cost.
Let us simplify the life of database people also ! as well as
we should perform better.
In addition, I made a few optimizations to the form of the command
generated for Preparation.
1) The Service Name and Server Name for the logging service are fixed
once the INET_SQL_LOG object is created. This information
in encoded in the statement used for preparation. ==> One time
charge. We dont need to keep track of these parameters or bind them
each time when we log a record.
2) There is another possible optimization by generating the date
string only once per change in day and not to generate the same multiple
times. But this requires state information to be maintained about
the current system time. ==> Not done now.
Possible to be added later on.
-MuraliK ( 3/2/95)
--*/
/************************************************************
* Include Headers
************************************************************/
# include <tcpdllp.hxx>
# include "inetlog.h"
# include "ilogcls.hxx"
# include "odbcconn.hxx"
/************************************************************
* Symbolic Constants and Data
************************************************************/
# define MAX_SQL_FIELD_NAMES_LEN ( 400)
# define MAX_SQL_FIELD_VALUES_LEN ( 200)
# define MAX_SQL_IDENTIFIER_QUOTE_CHAR ( 50)
# define PSZ_UNKNOWN_FIELD_W L"-"
# define PSZ_UNKNOWN_FIELD_A "-"
# define PSZ_GET_ERROR_FAILED_A "ODBC:GetLastError() Failed"
# define LEN_PSZ_GET_ERROR_FAILED_A sizeof(PSZ_GET_ERROR_FAILED_A)
# define PSZ_GET_ERROR_FAILED_W L"ODBC:GetLastError() Failed"
# define LEN_PSZ_GET_ERROR_FAILED_W sizeof(PSZ_GET_ERROR_FAILED_W)
//
// The template of SQL command has 3 arguments.
// 1. table name
// 2. field names
// 3. field values
// 1,2 and 3 are obained during the first wsprintf
//
static const WCHAR sg_rgchSqlInsertCmdTemplate[] =
L"insert into %ws ( %ws) values ( %ws)";
# define PSZ_SQL_INSERT_CMD_TEMPLATE ( sg_rgchSqlInsertCmdTemplate)
# define LEN_PSZ_SQL_INSERT_CMD_TEMPLATE \
( lstrlenW( PSZ_SQL_INSERT_CMD_TEMPLATE))
//
// Leave %ws so that we can print the service and server name when this
// string is used to generate an SQL statement.
//
static const WCHAR sg_rgchStdLogFieldValues[] =
L" ?, ?, ?, '%ws', '%ws', ?, ?, ?, ?, ?, ?, ?, ?, ?";
# define PSZ_INTERNET_STD_LOG_FORMAT_FIELD_NAMES ( sg_rgchStdLogFieldNames)
# define PSZ_INTERNET_STD_LOG_FORMAT_FIELD_VALUES ( sg_rgchStdLogFieldValues)
//
// AllFieldInfo()
// Defines all the fields required for SQL logging of the information
// to the database using ODBC interfaces.
// C arrays are numbered from offset 0.
// SQL columns are numbered from 1.
// field index values start from 0 and we adjust it when we talk of SQL col.
// FieldInfo( symbolic-name, field-name,
// field-index/column-number,
// field-C-type, field-Sql-type,
// field-precision, field-max-size, field-cb-value)
//
# define StringField( symName, fldName, fldIndex, prec) \
FieldInfo( symName, fldName, fldIndex, SQL_C_CHAR, SQL_CHAR, \
(prec), (prec), SQL_NTS)
# define NumericField( symName, fldName, fldIndex) \
FieldInfo( symName, fldName, fldIndex, SQL_C_LONG, SQL_INTEGER, \
0, sizeof( DWORD), 0)
# define TimeStampField( symName, fldName, fldIndex) \
FieldInfo( symName, fldName, fldIndex, SQL_C_TIMESTAMP, SQL_TIMESTAMP, \
0, sizeof( TIMESTAMP_STRUCT), 0)
//
// fields that have constant value. we are interested in names of such fields.
// they have negative field indexes.
// These fields need not be generated as parameter markers.
// ( Since they are invariants during lifetime of an INET_SQL_LOG oject)
// Hence the field values will go into the command generated.
// Left here as a documentation aid and field-generation purposes.
//
# define ConstantValueField( synName, fldName) \
FieldInfo( synName, fldName, -1, SQL_C_CHAR, SQL_CHAR, 0, 0, SQL_NTS)
//
// Ideally the "username" field should have MAX_USER_NAME_LEN as max size.
// However, Access 7.0 limits varchar() size to be 255 (8 bits) :-(
// So, we limit the size to be the least of the two ...
//
// FieldNames used are reserved. They are same as the names distributed
// in the template log file. Do not change them at free will.
//
//
# define AllFieldInfo() \
StringField( CLIENT_HOST, "ClientHost", 0, 255) \
StringField( USER_NAME, "username", 1, 255) \
TimeStampField( REQUEST_TIME, "LogTime", 2) \
ConstantValueField( SERVICE_NAME, "service") \
ConstantValueField( SERVER_NAME, "machine") \
StringField( SERVER_IPADDR, "serverip", 3, 50) \
NumericField( PROCESSING_TIME, "processingtime", 4) \
NumericField( BYTES_RECVD, "bytesrecvd", 5) \
NumericField( BYTES_SENT, "bytessent", 6) \
NumericField( SERVICE_STATUS, "servicestatus", 7) \
NumericField( WIN32_STATUS, "win32status", 8) \
StringField( SERVICE_OPERATION, "operation", 9, 255) \
StringField( SERVICE_TARGET, "target", 10, 255) \
StringField( SERVICE_PARAMS, "parameters", 11, 255) \
/************************************************************
* Type Definitions
************************************************************/
//
// Define the FieldInfo macro to generate a list of enumerations for
// the indexes to be used in the array of field parameters.
//
# define FieldInfo(symName, field, index, cType, sqlType, prec, maxSz, cbVal) \
i ## symName = (index),
enum LOGGING_VALID_COLUMNS {
// fields run from 0 through iMaxFields
AllFieldInfo()
iMaxFields
}; // enum LOGGING_VALID_COLUMNS
# undef FieldInfo
# define FieldInfo(symName, field, index, cType, sqlType, prec, maxSz, cbVal) \
fi ## symName,
enum LOGGING_FIELD_INDEXES {
fiMinFields = -1,
// fields run from 0 through fiMaxFields
AllFieldInfo()
fiMaxFields
}; // enum LOGGING_FIELD_INDEXES
# undef FieldInfo
struct FIELD_INFO {
int iParam;
CHAR * pszName;
SWORD paramType;
SWORD cType;
SWORD sqlType;
UDWORD cbColPrecision;
SWORD ibScale;
SDWORD cbMaxSize;
SDWORD cbValue;
}; // struct FIELD_INFO
//
// Define the FieldInfo macro to generate a list of data to be generated
// for entering the data values in an array for parameter information.
// Note the terminating ',' used here.
//
# define FieldInfo(symName, field, index, cType, sqlType, prec, maxSz, cbVal) \
{ ((index) + 1), field, SQL_PARAM_INPUT, cType, sqlType, \
( prec), 0, ( maxSz), ( cbVal) },
/*
The array of Fields: sg_rgFields contain the field information
for logging to SQL database for the log-record of
the services. The values are defined using the macros FieldInfo()
defined above.
If there is any need to add/delete/modify the parameters bound,
one should modify the above table "AllFieldInfo" macro.
*/
static FIELD_INFO sg_rgFields[] = {
AllFieldInfo()
//
// The above macro after expansion terminates with a comma.
// Add dummy entry to complete initialization of array.
//
{ 0, "dummy", SQL_PARAM_INPUT, 0, 0, 0, 0, 0, 0}
};
# undef FieldInfo
/************************************************************
* Functions
************************************************************/
BOOL
GenerateFieldNames(IN PODBC_CONNECTION poc,
OUT WCHAR * pchFieldNames,
IN DWORD cchFieldNames);
inline BOOL
IsEmptyStr( IN LPCWSTR psz)
{ return ( psz == NULL || *psz == L'\0'); }
inline BOOL
IsEmptyStr( IN LPCSTR psz)
{ return ( psz == NULL || *psz == '\0'); }
extern VOID
CopyUnicodeStringToBuffer(
OUT WCHAR * pwchBuffer,
IN DWORD cchMaxSize,
IN LPCWSTR pwszSource);
/**************************************************
* Member Functions of class INET_SQL_LOG
**************************************************/
INET_SQL_LOG::INET_SQL_LOG(
IN LPCWSTR pszServiceName,
IN EVENT_LOG * pEventLog,
IN LPCWSTR pszSqlDataSource, // or data source name
IN LPCWSTR pszSqlTableName)
/*++
This function constructs a new SQL logging object. The SQL logging is done
using ODBC gateway, which requires the data source name and the table
to be used for inserting the log records.
Arguments:
pszServiceName pointer to null terminated string containing
the name of the service
pszSqlDataSource pointer to null terminated string containing
database name. For ODBC purposes this is
the name of the data source.
pszSqlTableName pointer to null terminated string containing
the name of the table.
Returns:
newly constructed INET_SQL_LOG object.
This object is not valid until OpenConnection is called.
--*/
: INET_BASIC_LOG ( pszServiceName, pEventLog),
m_poc ( NULL), // to be set by OpenConnection()
m_poStmt ( NULL), // to be set by OpenConnection()
m_ppParams ( NULL), // to be set up on first logging
m_cOdbcParams ( NULL)
{
DBG_ASSERT( pszSqlDataSource != NULL);
CopyUnicodeStringToBuffer( m_rgchDataSource,
MAX_DATABASE_NAME_LEN,
pszSqlDataSource);
DBG_ASSERT( pszSqlTableName != NULL);
CopyUnicodeStringToBuffer( m_rgchTableName,
MAX_TABLE_NAME_LEN,
pszSqlTableName );
RtlZeroMemory( m_rgchUserName, UNLEN + 1);
InitializeCriticalSection( &m_csLock);
} // INET_SQL_LOG::INET_SQL_LOG()
INET_SQL_LOG::~INET_SQL_LOG( VOID)
/*++
Destroys the SQL log connection object.
Should be called after all active calls to the LogInformation() is
completed.
Note:
As of 2/15/95 the logging object does not count the number of threads
simultaneously active. This may need to be done, if there
is no discpline used to free the object. Always free the object
only after there is no active thread in any member function of
logging object.
--*/
{
DBG_REQUIRE( Close() == NO_ERROR);
DeleteCriticalSection( &m_csLock);
} // INET_SQL_LOG::~INET_SQL_LOG()
DWORD
INET_SQL_LOG::Open( IN LPCWSTR pwszDataSource,
IN LPCWSTR pwszUserName,
IN LPCWSTR pwszPassword)
/*++
This function opens a new connection ( using ODBC) to the data source.
It uses the username, password and the data source name to establish the
connection.
Arguments:
pwszDataSource pointer to null-terminated string containing data source.
pwszUserName pointer to null-terminated string containing user name.
pwszPassword pointer to null-terminated string containingpassword.
Returns:
Win32 error code
--*/
{
DWORD dwError = NO_ERROR;
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"SQL_LOG( %08x)::Open( %ws, %ws, %ws) called.\n",
this, pwszDataSource, pwszUserName, pwszPassword));
}
if ( m_poc == NULL) {
Lock();
//
// 1. Create a new ODBC connection object.
// 2. Open Connection.
// 3. Create a statement for execution.
//
m_poc = new ODBC_CONNECTION();
// In ODBC terminology,
// a datasource specifies the following collectively.
// Database server name,
// Database Name,
// Language to be used for interface,
// backend driver ( viz. access or SQL server etc.)
//
if ( m_poc == NULL) {
dwError = ERROR_NOT_ENOUGH_MEMORY;
} else {
if (m_poc->Open( pwszDataSource, pwszUserName, pwszPassword) &&
PrepareStatement() &&
PrepareParameters()
) {
// Copy the valid user's name
CopyUnicodeStringToBuffer(m_rgchUserName, UNLEN+1,
pwszUserName);
} else {
dwError = GetLastError();
}
}
Unlock();
} else {
dwError = ERROR_INVALID_PARAMETER;
}
if ( dwError != NO_ERROR) {
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
" Opening ODBC connection failed. "
" SystemErrorCode = %d."
" m_poc = %08x. m_poStmt = %08x. "
" ODBC ErrorCode = %d.\n",
GetLastError(),
m_poc,
m_poStmt,
(( m_poc != NULL) ?
m_poc->QueryErrorCode(): 0)));
}
}
return ( dwError);
} // INET_SQL_LOG::Open()
DWORD
INET_SQL_LOG::Close( VOID)
/*++
This function closes an active ODBC connection, if one exists.
Arguments:
None
Returns:
Win32 error code
--*/
{
DWORD dwError = NO_ERROR;
Lock();
//
// Free the ODBC_STATEMENT before freeing the ODBC_CONNECTION object.
//
if ( m_poStmt != NULL) {
DBGPRINTF( ( DBG_CONTEXT, " Deleting the Statement %08x\n", m_poStmt));
delete m_poStmt;
m_poStmt = NULL;
}
if ( m_poc != NULL) {
if ( !m_poc->Close()) {
dwError = GetLastError();
}
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
" Closing ODBC connection %08x returns Error=%d."
" Error = %d.\n",
m_poc, dwError,
m_poc->QueryErrorCode()));
}
delete m_poc;
m_poc = NULL;
}
//
// Free all the parameter markers used for Statement execution.
//
if ( m_ppParams != NULL) {
//
// Free all the parameter blocks also.
//
DWORD i;
for( i = 0; i < m_cOdbcParams; i++) {
if ( m_ppParams[ i] != NULL) {
delete m_ppParams[ i];
m_ppParams[ i] = NULL;
}
} // for
delete [] m_ppParams;
m_ppParams = NULL;
m_cOdbcParams = 0;
}
Unlock();
return ( dwError);
} // INET_SQL_LOG::Close()
DWORD
INET_SQL_LOG::LogInformation( IN const INETLOG_INFORMATIONA * pilInfo,
OUT LPSTR pszErrorMessage,
IN OUT LPDWORD lpcchErrorMessage
)
/*++
This function takes the information to be logged and writes the converts the
log record into an SQL record to be inserted in the SQL database.
SEE comments in INET_SQL_LOG::LogInformation(IN const INETLOG_INFORMATIONW *)
Both these functions are identical, except that this function deals with
CHARs (ANSI) and the later deals with UNICODE strings. If there is any
modifications, keep these two functions consistent.
--*/
{
DWORD dwError = NO_ERROR;
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"%s(%08x)::LogInformation( pilInfo = %08x) called.\n",
QueryClassIdString(),
this,
pilInfo));
}
//
// Check if we have cached SQL command and if not form a new cached SQL
// command
//
if ( IsValid()) {
BOOL fReturn;
SYSTEMTIME stNow;
LPCSTR pszUserName = pilInfo->pszClientUserName;
LPCSTR pszOperation = pilInfo->pszOperation;
LPCSTR pszTarget = pilInfo->pszTarget;
LPCSTR pszParameters= pilInfo->pszParameters;
LPCSTR pszServerAddr= pilInfo->pszServerIpAddress;
SDWORD cbParameters;
cbParameters = strlen( pszParameters ? pszParameters : "" ) + 1;
//
// Format the Date and Time for logging.
//
GetLocalTime( & stNow);
if ( IsEmptyStr(pszUserName)) { pszUserName = QueryDefaultUserNameA();}
if ( IsEmptyStr(pszOperation)) { pszOperation = PSZ_UNKNOWN_FIELD_A; }
if ( IsEmptyStr(pszParameters)) { pszParameters= PSZ_UNKNOWN_FIELD_A; }
if ( IsEmptyStr(pszTarget)) { pszTarget = PSZ_UNKNOWN_FIELD_A; }
if ( IsEmptyStr(pszServerAddr)) { pszServerAddr= PSZ_UNKNOWN_FIELD_A; }
Lock();
//
// Truncate the parameters field
//
if ( cbParameters > m_ppParams[ iSERVICE_PARAMS]->QueryMaxCbValue() )
{
pszParameters = "...";
}
//
// Copy data values into parameter markers.
// NYI: LARGE_INTEGERS are ignored. Only lowBytes used!
//
fReturn =
(
m_ppParams[ iCLIENT_HOST]->
CopyValue( pilInfo->pszClientHostName) &&
m_ppParams[ iUSER_NAME]->CopyValue( pszUserName) &&
m_ppParams[ iREQUEST_TIME]->CopyValue( &stNow) &&
m_ppParams[ iSERVER_IPADDR]->CopyValue( pszServerAddr) &&
m_ppParams[ iPROCESSING_TIME]->
CopyValue( pilInfo->msTimeForProcessing) &&
m_ppParams[ iBYTES_RECVD]->
CopyValue( pilInfo->liBytesRecvd.LowPart) &&
m_ppParams[ iBYTES_SENT]->
CopyValue( pilInfo->liBytesSent.LowPart) &&
m_ppParams[ iSERVICE_STATUS]->
CopyValue( pilInfo->dwServiceSpecificStatus) &&
m_ppParams[ iWIN32_STATUS]->CopyValue( pilInfo->dwWin32Status) &&
m_ppParams[ iSERVICE_OPERATION]->CopyValue( pszOperation) &&
m_ppParams[ iSERVICE_TARGET]->CopyValue( pszTarget) &&
m_ppParams[ iSERVICE_PARAMS]->CopyValue( pszParameters)
);
//
// Execute insertion if parameters got copied properly.
//
if ( fReturn ) {
if ( !m_poStmt->ExecuteStatement()) {
//
// Execution of SQL statement failed.
// Pass the error as genuine failure, indicating ODBC failed
// Obtain and store the error string in the proper return field
//
dwError = ERROR_GEN_FAILURE;
if ( pszErrorMessage != NULL && lpcchErrorMessage != NULL) {
STR strError;
LPSTR pszError;
DWORD cchLen;
if ( m_poStmt->GetLastErrorText(&strError)) {
pszError= strError.QueryStr();
cchLen = strError.QueryCCH();
} else {
pszError= PSZ_GET_ERROR_FAILED_A,
cchLen = LEN_PSZ_GET_ERROR_FAILED_A;
}
// copy only specified chars and send partial string
if ( cchLen >= *lpcchErrorMessage) {
cchLen = *lpcchErrorMessage;
pszError[cchLen - 1] = '\0';
}
lstrcpyA( pszErrorMessage, pszError);
*lpcchErrorMessage = cchLen;
} // if error message needs to be sent.
}
} else {
dwError = GetLastError();
}
Unlock();
} else {
dwError = ( ERROR_INVALID_PARAMETER);
}
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"%s::LogInformation() returns %d.\n",
QueryClassIdString(), dwError));
}
return ( dwError);
} // INET_SQL_LOG::LogInformation()
DWORD
INET_SQL_LOG::LogInformation( IN const INETLOG_INFORMATIONW * pilInfo,
OUT LPWSTR pszErrorMessage,
IN OUT LPDWORD lpcchErrorMessage)
/*++
This function takes the information to be logged and writes the converts the
log record into an SQL record to be inserted in the SQL database.
Arguments:
pilInfo pointer to Internet Log Information
Returns:
Win32 Error Code
Returns ERROR_GEN_FAILURE with detailed Error string for ODBC failures.
FALSE on failure.
Note:
There are two components to doing SQL inserts:
1) One-time setup for insertions.
2) Set values and execute for each new data to be inserted.
Repeated as many times there is data.
One-Time Setup:
This involves generating an SQL command for action desired. Here it
is insertion. Then prepare the statement ( with '?' for unknown value)
using ODBC_CONNECTION::PrepareStatement().
Create parameters required for the insertion. The parameters contain
information required for ODBC_CONNECTION::BindParameter().
Bind the parameters using using BindParameter() calls.
Never free the parameters till the end of the logging session ( till
all insertions are performed.
Insertion of Data:
Each time a new data ( here it is LogInformation) comes along,
copy the data values into the parameter marker buffers and
call ODBC_CONNECTION::ExecuteStatement() which executes the statement,
once for each insertion.
--*/
{
DWORD dwError = NO_ERROR;
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"%s(%08x)::LogInformation( pilInfo = %08x) called.\n",
QueryClassIdString(),
this,
pilInfo));
}
//
// Check if we have cached SQL command and if not form a new cached SQL
// command
//
if ( IsValid()) {
BOOL fReturn;
SYSTEMTIME stNow;
LPCWSTR pszUserName = pilInfo->pszClientUserName;
LPCWSTR pszOperation = pilInfo->pszOperation;
LPCWSTR pszTarget = pilInfo->pszTarget;
LPCWSTR pszParameters= pilInfo->pszParameters;
LPCWSTR pszServerAddr= pilInfo->pszServerIpAddress;
SDWORD cbParameters;
cbParameters = wcslen( pszParameters ? pszParameters : L"" ) + 1;
cbParameters *= sizeof(WCHAR);
//
// Format the Date and Time for logging.
//
GetLocalTime( & stNow);
if ( IsEmptyStr(pszUserName)) { pszUserName = QueryDefaultUserName(); }
if ( IsEmptyStr(pszOperation)) { pszOperation = PSZ_UNKNOWN_FIELD_W; }
if ( IsEmptyStr(pszParameters)) { pszParameters= PSZ_UNKNOWN_FIELD_W; }
if ( IsEmptyStr(pszTarget)) { pszTarget = PSZ_UNKNOWN_FIELD_W; }
if ( IsEmptyStr(pszServerAddr)) { pszServerAddr= PSZ_UNKNOWN_FIELD_W; }
Lock();
//
// Truncate the parameters field
//
if ( cbParameters > m_ppParams[ iSERVICE_PARAMS]->QueryMaxCbValue() )
{
pszParameters = L"...";
}
//
// Copy data values into parameter markers.
//
fReturn =
(
m_ppParams[ iCLIENT_HOST]->
CopyValue( pilInfo->pszClientHostName) &&
m_ppParams[ iUSER_NAME]->CopyValue( pszUserName) &&
m_ppParams[ iREQUEST_TIME]->CopyValue( &stNow) &&
m_ppParams[ iSERVER_IPADDR]->CopyValue( pszServerAddr) &&
m_ppParams[ iPROCESSING_TIME]->
CopyValue( pilInfo->msTimeForProcessing) &&
m_ppParams[ iBYTES_RECVD]->
CopyValue( pilInfo->liBytesRecvd.LowPart) &&
m_ppParams[ iBYTES_SENT]->
CopyValue( pilInfo->liBytesSent.LowPart) &&
m_ppParams[ iSERVICE_STATUS]->
CopyValue( pilInfo->dwServiceSpecificStatus) &&
m_ppParams[ iWIN32_STATUS]->CopyValue( pilInfo->dwWin32Status) &&
m_ppParams[ iSERVICE_OPERATION]->CopyValue( pszOperation) &&
m_ppParams[ iSERVICE_TARGET]->CopyValue( pszTarget) &&
m_ppParams[ iSERVICE_PARAMS]->CopyValue( pszParameters)
);
//
// Execute insertion if parameters got copied properly.
//
if ( fReturn ) {
if ( !m_poStmt->ExecuteStatement()) {
//
// Execution of SQL statement failed.
// Pass the error as genuine failure, indicating ODBC failed
// Obtain and store the error string in the proper return field
//
dwError = ERROR_GEN_FAILURE;
if ( pszErrorMessage != NULL && lpcchErrorMessage != NULL &&
*lpcchErrorMessage > 0) {
STR strError;
LPSTR pszError;
DWORD cchLen;
if ( m_poStmt->GetLastErrorText(&strError)) {
pszError= strError.QueryStr();
cchLen = strError.QueryCCH();
} else {
pszError= PSZ_GET_ERROR_FAILED_A,
cchLen = LEN_PSZ_GET_ERROR_FAILED_A;
}
// copy only specified chars and send partial string
if ( cchLen * sizeof(WCHAR)/sizeof(CHAR)
>= *lpcchErrorMessage) {
cchLen = *lpcchErrorMessage*sizeof(CHAR)/sizeof(WCHAR);
pszError[cchLen - 1] = '\0';
}
wsprintfW( pszErrorMessage, L"%S", pszError);
*lpcchErrorMessage = cchLen;
} // if error message needs to be sent.
}
} else {
dwError = GetLastError();
}
Unlock();
} else {
dwError = ( ERROR_INVALID_PARAMETER);
}
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"%s::LogInformation() returs %d.\n",
QueryClassIdString(), dwError));
}
return ( dwError);
} // INET_SQL_LOG::LogInformation()
BOOL
INET_SQL_LOG::PrepareStatement( VOID)
/*++
This command forms the template SQL command used for insertion
of log records. Then it prepares the SQL command( for later execution)
using ODBC_CONNECTION::PrepareStatement().
It should always be called after locking the INET_SQL_LOG object.
Arguments:
None
Returns:
TRUE on success and FALSE if there is any failure.
Note:
The template for insertion is:
insert into <table name> ( field names ...) values ( ?, ?, ...)
^^^^
Field values go here
Field names are generated on a per logging format basis.
--*/
{
BOOL fReturn = FALSE;
WCHAR rgchFieldNames[ MAX_SQL_FIELD_NAMES_LEN];
WCHAR rgchFieldValues[ MAX_SQL_FIELD_VALUES_LEN];
//
// Obtain field names and field values ( template) for various log formats.
// The order of field names should match the order of field values
// generated by FormatLogInformation() for the format specified.
//
rgchFieldNames[ 0] =
rgchFieldValues[ 0] = L'\0';
switch ( QueryInetLogFormat()) {
case InternetStdLogFormat:
{
DWORD cchFields;
fReturn = GenerateFieldNames(m_poc,
rgchFieldNames,
MAX_SQL_FIELD_NAMES_LEN);
if ( !fReturn) {
DBGPRINTF(( DBG_CONTEXT,
" Unable to generate field names. Error = %d\n",
GetLastError()));
break;
}
cchFields = wsprintfW( rgchFieldValues,
PSZ_INTERNET_STD_LOG_FORMAT_FIELD_VALUES,
QueryServiceName(),
QueryServerName());
fReturn = (fReturn && (cchFields < MAX_SQL_FIELD_VALUES_LEN));
DBG_ASSERT( cchFields < MAX_SQL_FIELD_VALUES_LEN);
fReturn = TRUE;
break;
}
default:
//
// Unsupported format.
//
DBGPRINTF( ( DBG_CONTEXT,
" %d Formatting of log records not implemented.\n",
QueryInetLogFormat()));
fReturn = FALSE;
break;
} // switch()
if ( fReturn) {
WCHAR * pwszSqlCommand;
DWORD cchReqd;
//
// The required number of chars include sql insert template command
// and field names and table name.
//
cchReqd = ( LEN_PSZ_SQL_INSERT_CMD_TEMPLATE +
lstrlenW( m_rgchTableName) +
lstrlenW( rgchFieldNames) +
lstrlenW( rgchFieldValues) + 20);
pwszSqlCommand = ( WCHAR *) LocalAlloc( LPTR, cchReqd * sizeof( WCHAR));
m_poStmt = m_poc->AllocStatement();
if ( ( fReturn = ( pwszSqlCommand != NULL) && ( m_poStmt != NULL))) {
DWORD cchUsed;
cchUsed = wsprintfW( pwszSqlCommand,
PSZ_SQL_INSERT_CMD_TEMPLATE,
m_rgchTableName,
rgchFieldNames,
rgchFieldValues);
DBG_ASSERT( cchUsed < cchReqd);
IF_DEBUG(INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
" Sqlcommand generated is: %ws.\n",
pwszSqlCommand));
}
fReturn = ((cchUsed < cchReqd) &&
m_poStmt->PrepareStatement( pwszSqlCommand)
);
LocalFree( pwszSqlCommand); // free allocated memory
}
} // valid field names and filed values.
IF_DEBUG( INETLOG) {
DBGPRINTF( ( DBG_CONTEXT,
"%s::PrepareStatement() returns %d.",
QueryClassIdString(), fReturn));
}
return ( fReturn);
} // INET_SQL_LOG::PrepareStatement()
BOOL
INET_SQL_LOG::PrepareParameters( VOID)
/*++
This function creates an array of ODBC_PARAMETER objects used for binding
parameters to an already prepared statement. These ODBC_PARAMETER objects
are then used for insertion of data values into the table specified,
through ODBC.
This function should always be called after locking the object.
Arguments:
None
Returns:
TRUE on success and FALSE if there is any failure.
--*/
{
BOOL fReturn = FALSE;
PODBC_PARAMETER * prgParams = NULL;
DWORD cParams = 0;
DWORD nParamsSeen = 0;
DWORD i;
DBG_ASSERT( m_poStmt != NULL && m_poStmt->IsValid() &&
m_ppParams == NULL && m_cOdbcParams == 0);
//
// create sufficient space for iMaxFields pointers to ODBC objects.
//
prgParams = new PODBC_PARAMETER[ iMaxFields];
if ( prgParams != NULL) {
fReturn = TRUE; // Assume everything will go on fine.
cParams = iMaxFields;
//
// Create all the ODBC parameters.
// Walk through all field indexes and pick up the valid columns
//
for( nParamsSeen = 0, i =0; i < fiMaxFields; i++) {
if ( sg_rgFields[i].iParam > 0) {
WORD colNum = (WORD ) sg_rgFields[i].iParam;
prgParams[nParamsSeen] =
new ODBC_PARAMETER(colNum,
sg_rgFields[i].paramType,
sg_rgFields[i].cType,
sg_rgFields[i].sqlType,
sg_rgFields[i].cbColPrecision
);
if ( prgParams[ nParamsSeen] == NULL) {
fReturn = FALSE;
DBGPRINTF( ( DBG_CONTEXT,
" Failed to create Parameter[%d] %s. \n",
i, sg_rgFields[i].pszName));
break;
}
nParamsSeen++;
DBG_ASSERT( nParamsSeen <= cParams);
}
} // for creation of all ODBC parameters
if ( fReturn) {
//
// Set buffers for values to be received during insertions.
// Bind parameters to the statement using ODBC_CONNECTION object.
//
DBG_ASSERT( nParamsSeen == cParams);
for( nParamsSeen = 0, i = 0; i < fiMaxFields; i++) {
if ( sg_rgFields[i].iParam > 0) {
if (!prgParams[nParamsSeen]->
SetValueBuffer(sg_rgFields[i].cbMaxSize,
sg_rgFields[i].cbValue) ||
!m_poStmt->BindParameter( prgParams[nParamsSeen])
) {
fReturn = FALSE;
DBGPRINTF( ( DBG_CONTEXT,
" Binding Parameter [%u] (%08x) failed.\n",
nParamsSeen, prgParams[nParamsSeen]));
DBG_CODE( prgParams[ i]->Print());
break;
}
nParamsSeen++;
}
} // for
} // if all ODBC params were created.
} // if array for pointers to ODBC params created successfully
if ( !fReturn) {
//
// Free up the space used, since we were unsuccessful.
//
for( i = 0; i < iMaxFields; i++) {
if ( prgParams[ i] != NULL) {
delete ( prgParams[ i]);
prgParams[i] = NULL;
}
} // for
delete [] prgParams;
prgParams = NULL;
cParams = 0;
}
//
// Set the values. Either invalid or valid ,depending on failure/success
//
m_ppParams = prgParams;
m_cOdbcParams = cParams;
return ( fReturn);
} // INET_SQL_LOG::PrepareParameters()
BOOL
INET_SQL_LOG::GetConfig(OUT PINETLOG_CONFIGURATIONW pLogConfig) const
/*++
The password for ODBC connection is not stored and hence is not available
when we do a get configuration.
--*/
{
BOOL fReturn;
DBG_ASSERT( pLogConfig != NULL);
fReturn = INET_BASIC_LOG::GetConfig(pLogConfig);
if (fReturn) {
// Store other file specific configuration informtaion
CopyUnicodeStringToBuffer(pLogConfig->u.logSql.rgchDataSource,
MAX_DATABASE_NAME_LEN,
m_rgchDataSource);
CopyUnicodeStringToBuffer(pLogConfig->u.logSql.rgchTableName,
MAX_DATABASE_NAME_LEN,
m_rgchTableName);
CopyUnicodeStringToBuffer(pLogConfig->u.logSql.rgchUserName,
MAX_DATABASE_NAME_LEN,
m_rgchUserName);
RtlZeroMemory(pLogConfig->u.logSql.rgchPassword,
PWLEN);
}
return (fReturn);
} // INET_FILE_LOG::GetConfig()
# if DBG
VOID
INET_SQL_LOG::Print( VOID) const
{
INET_BASIC_LOG::Print();
DBGPRINTF( ( DBG_CONTEXT,
" Critical Section at %08x"
" DataSource = %ws; TableName = %ws\n",
&m_csLock,
m_rgchDataSource, m_rgchTableName));
DBGPRINTF( ( DBG_CONTEXT, " ODBC_CONNECTION object = %08x\n", m_poc));
if ( m_poc != NULL) { m_poc->Print(); }
DBGPRINTF( ( DBG_CONTEXT, " ODBC_STATEMENT object = %08x\n", m_poStmt));
if ( m_poStmt != NULL) { m_poStmt->Print(); }
if ( m_ppParams != NULL) {
DWORD i;
DBGPRINTF( ( DBG_CONTEXT, "ODBC parameters ( %08x). Entries = %u\n",
m_ppParams, m_cOdbcParams));
for( i = 0; i < m_cOdbcParams; i++) {
DBGPRINTF( ( DBG_CONTEXT, " Parameter[ %u] = %08x\n",
i, m_ppParams[i]));
m_ppParams[ i]->Print();
} // for
}
return;
} // INET_SQL_LOG::Print()
# endif // DBG
BOOL
GenerateFieldNames(IN PODBC_CONNECTION poc,
OUT WCHAR * pchFieldNames,
IN DWORD cchFieldNames)
/*++
This function generates the field names string from the names of the fields
and identifier quote character for particular ODBC datasource in use.
--*/
{
BOOL fReturn = FALSE;
CHAR rgchQuote[MAX_SQL_IDENTIFIER_QUOTE_CHAR];
DWORD cchQuote;
DBG_ASSERT( poc != NULL && pchFieldNames != NULL);
pchFieldNames[0] = L'\0'; // initialize
//
// Inquire and obtain the SQL identifier quote char for ODBC data source.
//
fReturn = poc->GetInfo(SQL_IDENTIFIER_QUOTE_CHAR,
rgchQuote, MAX_SQL_IDENTIFIER_QUOTE_CHAR,
&cchQuote);
if ( !fReturn) {
DBG_CODE( {
STR strError;
poc->GetLastErrorText( &strError);
DBGPRINTF(( DBG_CONTEXT,
" ODBC_CONNECTION(%08x)::GetInfo(QuoteChar) failed."
" Error = %s\n",
poc, strError.QueryStr()));
});
} else {
DWORD i;
DWORD cchUsed = 0;
DWORD cchLen;
//
// ODBC returns " " (blank) if there is no special character
// for quoting identifiers. we need to identify and string the same.
// This needs to be done, other wise ODBC will complain when
// we give unwanted blanks before ","
//
if ( !strcmp( rgchQuote, " ")) {
rgchQuote[0] = '\0'; // string the quoted blank.
cchQuote = 0;
} else {
cchQuote = strlen( rgchQuote);
}
// for each column, generate the quoted literal string and concatenate.
for( i = 0; i < fiMaxFields; i++) {
DWORD cchLen1 =
(strlen(sg_rgFields[i].pszName) + 2 * cchQuote + 2);
if ( cchUsed + cchLen1 < cchFieldNames) {
// space available for copying the data.
cchLen = wsprintfW( pchFieldNames + cchUsed,
L" %S%S%S,",
rgchQuote,
sg_rgFields[i].pszName,
rgchQuote
);
DBG_ASSERT( cchLen == cchLen1);
}
cchUsed += cchLen1;
} // for
if ( cchUsed >= cchFieldNames) {
// buffer exceeded. return error.
SetLastError( ERROR_INSUFFICIENT_BUFFER);
fReturn = FALSE;
} else {
//
// Reset the last character from being a ","
//
cchLen = (cchUsed > 0) ? (cchUsed - 1) : 0;
pchFieldNames[cchLen] = L'\0';
fReturn = TRUE;
}
}
IF_DEBUG( INETLOG) {
DBGPRINTF(( DBG_CONTEXT,
" GenerateFieldNames() returns %d."
" Fields = %S\n",
fReturn, pchFieldNames));
}
return (fReturn);
} // GenerateFieldNames()
/************************ End of File ***********************/