851 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			851 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * This file is distributed as part of MaxScale from MariaDB Corporation.  It is free
 | 
						|
 * software: you can redistribute it and/or modify it under the terms of the
 | 
						|
 * GNU General Public License as published by the Free Software Foundation,
 | 
						|
 * version 2.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 | 
						|
 * details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License along with
 | 
						|
 * this program; if not, write to the Free Software Foundation, Inc., 51
 | 
						|
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
						|
 *
 | 
						|
 * Copyright MariaDB Corporation Ab 2014
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @file modutil.c  - Implementation of useful routines for modules
 | 
						|
 *
 | 
						|
 * @verbatim
 | 
						|
 * Revision History
 | 
						|
 *
 | 
						|
 * Date		Who			Description
 | 
						|
 * 04/06/14	Mark Riddoch		Initial implementation
 | 
						|
 * 24/10/14	Massimiliano Pinto	Added modutil_send_mysql_err_packet, modutil_create_mysql_err_msg
 | 
						|
 *
 | 
						|
 * @endverbatim
 | 
						|
 */
 | 
						|
#include <buffer.h>
 | 
						|
#include <string.h>
 | 
						|
#include <mysql_client_server_protocol.h>
 | 
						|
#include <modutil.h>
 | 
						|
 | 
						|
/** Defined in log_manager.cc */
 | 
						|
extern int            lm_enabled_logfiles_bitmask;
 | 
						|
extern size_t         log_ses_count[];
 | 
						|
extern __thread log_info_t tls_log_info;
 | 
						|
 | 
						|
static void modutil_reply_routing_error(
 | 
						|
	DCB*  	 backend_dcb,
 | 
						|
	int   	 error,
 | 
						|
	char* 	 state,
 | 
						|
	char* 	 errstr,
 | 
						|
	uint32_t flags);
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Check if a GWBUF structure is a MySQL COM_QUERY packet
 | 
						|
 *
 | 
						|
 * @param	buf	Buffer to check
 | 
						|
 * @return	True if GWBUF is a COM_QUERY packet
 | 
						|
 */
 | 
						|
int
 | 
						|
modutil_is_SQL(GWBUF *buf)
 | 
						|
{
 | 
						|
unsigned char	*ptr;
 | 
						|
 | 
						|
	if (GWBUF_LENGTH(buf) < 5)
 | 
						|
		return 0;
 | 
						|
	ptr = GWBUF_DATA(buf);
 | 
						|
	return ptr[4] == 0x03;		// COM_QUERY
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Extract the SQL portion of a COM_QUERY packet
 | 
						|
 *
 | 
						|
 * NB This sets *sql to point into the packet and does not
 | 
						|
 * allocate any new storage. The string pointed to by *sql is
 | 
						|
 * not NULL terminated.
 | 
						|
 *
 | 
						|
 * This routine is very simplistic and does not deal with SQL text
 | 
						|
 * that spans multiple buffers.
 | 
						|
 *
 | 
						|
 * The length returned is the complete length of the SQL, which may
 | 
						|
 * be larger than the amount of data in this packet.
 | 
						|
 *
 | 
						|
 * @param	buf	The packet buffer
 | 
						|
 * @param	sql	Pointer that is set to point at the SQL data
 | 
						|
 * @param	length	Length of the SQL query data
 | 
						|
 * @return	True if the packet is a COM_QUERY packet
 | 
						|
 */
 | 
						|
int
 | 
						|
modutil_extract_SQL(GWBUF *buf, char **sql, int *length)
 | 
						|
{
 | 
						|
unsigned char	*ptr;
 | 
						|
 | 
						|
	if (!modutil_is_SQL(buf))
 | 
						|
		return 0;
 | 
						|
	ptr = GWBUF_DATA(buf);
 | 
						|
	*length = *ptr++;
 | 
						|
	*length += (*ptr++ << 8);
 | 
						|
	*length += (*ptr++ << 16);
 | 
						|
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
 | 
						|
	*length = *length - 1;
 | 
						|
	*sql = (char *)ptr;
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Extract the SQL portion of a COM_QUERY packet
 | 
						|
 *
 | 
						|
 * NB This sets *sql to point into the packet and does not
 | 
						|
 * allocate any new storage. The string pointed to by *sql is
 | 
						|
 * not NULL terminated.
 | 
						|
 *
 | 
						|
 * The number of bytes pointed to *sql is returned in *length
 | 
						|
 *
 | 
						|
 * The remaining number of bytes required for the complete query string
 | 
						|
 * are returned in *residual
 | 
						|
 *
 | 
						|
 * @param	buf		The packet buffer
 | 
						|
 * @param	sql		Pointer that is set to point at the SQL data
 | 
						|
 * @param	length		Length of the SQL query data pointed to by sql
 | 
						|
 * @param	residual	Any remain part of the query in future packets
 | 
						|
 * @return	True if the packet is a COM_QUERY packet
 | 
						|
 */
 | 
						|
int
 | 
						|
modutil_MySQL_Query(GWBUF *buf, char **sql, int *length, int *residual)
 | 
						|
{
 | 
						|
unsigned char	*ptr;
 | 
						|
 | 
						|
	if (!modutil_is_SQL(buf))
 | 
						|
		return 0;
 | 
						|
	ptr = GWBUF_DATA(buf);
 | 
						|
	*residual = *ptr++;
 | 
						|
	*residual += (*ptr++ << 8);
 | 
						|
	*residual += (*ptr++ << 16);
 | 
						|
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
 | 
						|
	*residual = *residual - 1;
 | 
						|
	*length = GWBUF_LENGTH(buf) - 5;
 | 
						|
	*residual -= *length;
 | 
						|
	*sql = (char *)ptr;
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Calculate the length of MySQL packet and how much is missing from the GWBUF 
 | 
						|
 * passed as parameter.
 | 
						|
 * 
 | 
						|
 * This routine assumes that there is only one MySQL packet in the buffer.
 | 
						|
 * 
 | 
						|
 * @param buf			buffer list including the query, may consist of 
 | 
						|
 * 				multiple buffers
 | 
						|
 * @param nbytes_missing	pointer to missing bytecount 
 | 
						|
 * 
 | 
						|
 * @return the length of MySQL packet and writes missing bytecount to 
 | 
						|
 * nbytes_missing.
 | 
						|
 */
 | 
						|
int modutil_MySQL_query_len(
 | 
						|
	GWBUF* buf,
 | 
						|
	int*   nbytes_missing)
 | 
						|
{
 | 
						|
	int     len;
 | 
						|
	int     buflen;
 | 
						|
	
 | 
						|
	if (!modutil_is_SQL(buf))
 | 
						|
	{
 | 
						|
		len = 0;
 | 
						|
		goto retblock;
 | 
						|
	}
 | 
						|
	len = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf)); 
 | 
						|
	*nbytes_missing = len-1;
 | 
						|
	buflen = gwbuf_length(buf);
 | 
						|
	
 | 
						|
	*nbytes_missing -= buflen-5;	
 | 
						|
	
 | 
						|
retblock:
 | 
						|
	return len;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Replace the contents of a GWBUF with the new SQL statement passed as a text string.
 | 
						|
 * The routine takes care of the modification needed to the MySQL packet,
 | 
						|
 * returning a GWBUF chain that can be used to send the data to a MySQL server
 | 
						|
 *
 | 
						|
 * @param orig	The original request in a GWBUF
 | 
						|
 * @param sql	The SQL text to replace in the packet
 | 
						|
 * @return A newly formed GWBUF containing the MySQL packet.
 | 
						|
 */
 | 
						|
GWBUF *
 | 
						|
modutil_replace_SQL(GWBUF *orig, char *sql)
 | 
						|
{
 | 
						|
unsigned char	*ptr;
 | 
						|
int	length, newlength;
 | 
						|
GWBUF	*addition;
 | 
						|
 | 
						|
	if (!modutil_is_SQL(orig))
 | 
						|
		return NULL;
 | 
						|
	ptr = GWBUF_DATA(orig);
 | 
						|
	length = *ptr++;
 | 
						|
	length += (*ptr++ << 8);
 | 
						|
	length += (*ptr++ << 16);
 | 
						|
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
 | 
						|
 | 
						|
	newlength = strlen(sql);
 | 
						|
	if (length - 1 == newlength)
 | 
						|
	{
 | 
						|
		/* New SQL is the same length as old */
 | 
						|
		memcpy(ptr, sql, newlength);
 | 
						|
	}
 | 
						|
	else if (length - 1 > newlength)
 | 
						|
	{
 | 
						|
		/* New SQL is shorter */
 | 
						|
		memcpy(ptr, sql, newlength);
 | 
						|
		GWBUF_RTRIM(orig, (length - 1) - newlength);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		memcpy(ptr, sql, length - 1);
 | 
						|
		addition = gwbuf_alloc(newlength - (length - 1));
 | 
						|
		memcpy(GWBUF_DATA(addition), &sql[length - 1], newlength - (length - 1));
 | 
						|
		ptr = GWBUF_DATA(orig);
 | 
						|
		*ptr++ = (newlength + 1) & 0xff;
 | 
						|
		*ptr++ = ((newlength + 1) >> 8) & 0xff;
 | 
						|
		*ptr++ = ((newlength + 1) >> 16) & 0xff;
 | 
						|
		addition->gwbuf_type = orig->gwbuf_type;
 | 
						|
		orig->next = addition;
 | 
						|
	}
 | 
						|
 | 
						|
	return orig;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Extract the SQL from a COM_QUERY packet and return in a NULL terminated buffer.
 | 
						|
 * The buffer should be freed by the caller when it is no longer required.
 | 
						|
 *
 | 
						|
 * If the packet is not a COM_QUERY packet then the function will return NULL
 | 
						|
 *
 | 
						|
 * @param buf	The buffer chain
 | 
						|
 * @return Null terminated string containing query text or NULL on error
 | 
						|
 */
 | 
						|
char *
 | 
						|
modutil_get_SQL(GWBUF *buf)
 | 
						|
{
 | 
						|
unsigned int	len, length;
 | 
						|
unsigned char	*ptr, *dptr, *rval = NULL;
 | 
						|
 | 
						|
	if (!modutil_is_SQL(buf))
 | 
						|
		return rval;
 | 
						|
	ptr = GWBUF_DATA(buf);
 | 
						|
	length = *ptr++;
 | 
						|
	length += (*ptr++ << 8);
 | 
						|
	length += (*ptr++ << 16);
 | 
						|
 | 
						|
	if ((rval = (char *)malloc(length + 1)) == NULL)
 | 
						|
		return NULL;
 | 
						|
	dptr = rval;
 | 
						|
        ptr += 2;  // Skip sequence id	and COM_QUERY byte
 | 
						|
	len = GWBUF_LENGTH(buf) - 5;
 | 
						|
	while (buf && length > 0)
 | 
						|
	{
 | 
						|
		int clen = length > len ? len : length;
 | 
						|
		memcpy(dptr, ptr, clen);
 | 
						|
		dptr += clen;
 | 
						|
		length -= clen;
 | 
						|
		buf = buf->next;
 | 
						|
		if (buf)
 | 
						|
		{
 | 
						|
			ptr = GWBUF_DATA(buf);
 | 
						|
			len = GWBUF_LENGTH(buf);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*dptr = 0;
 | 
						|
	return rval;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Copy query string from GWBUF buffer to separate memory area.
 | 
						|
 * 
 | 
						|
 * @param buf   GWBUF buffer including the query
 | 
						|
 * 
 | 
						|
 * @return Plain text query if the packet type is COM_QUERY. Otherwise return 
 | 
						|
 * a string including the packet type.
 | 
						|
 */
 | 
						|
char *
 | 
						|
modutil_get_query(GWBUF *buf)
 | 
						|
{
 | 
						|
        uint8_t*           packet;
 | 
						|
        mysql_server_cmd_t packet_type;
 | 
						|
        size_t             len;
 | 
						|
        char*              query_str = NULL;
 | 
						|
        
 | 
						|
        packet = GWBUF_DATA(buf);
 | 
						|
        packet_type = packet[4];
 | 
						|
        
 | 
						|
        switch (packet_type) {
 | 
						|
                case MYSQL_COM_QUIT:
 | 
						|
                        len = strlen("[Quit msg]")+1;
 | 
						|
                        if ((query_str = (char *)malloc(len+1)) == NULL)
 | 
						|
                        {
 | 
						|
                                goto retblock;
 | 
						|
                        }
 | 
						|
                        memcpy(query_str, "[Quit msg]", len);
 | 
						|
                        memset(&query_str[len], 0, 1);
 | 
						|
                        break;
 | 
						|
                        
 | 
						|
                case MYSQL_COM_QUERY:
 | 
						|
                        len = MYSQL_GET_PACKET_LEN(packet)-1; /*< distract 1 for packet type byte */        
 | 
						|
                        if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
 | 
						|
                        {
 | 
						|
                                goto retblock;
 | 
						|
                        }
 | 
						|
                        memcpy(query_str, &packet[5], len);
 | 
						|
                        memset(&query_str[len], 0, 1);
 | 
						|
                        break;
 | 
						|
                        
 | 
						|
                default:
 | 
						|
                        len = strlen(STRPACKETTYPE(packet_type))+1;
 | 
						|
                        if (len < 1 || len > ~(size_t)0 - 1 || (query_str = (char *)malloc(len+1)) == NULL)
 | 
						|
                        {
 | 
						|
                                goto retblock;
 | 
						|
                        }
 | 
						|
                        memcpy(query_str, STRPACKETTYPE(packet_type), len);
 | 
						|
                        memset(&query_str[len], 0, 1);
 | 
						|
                        break;
 | 
						|
        } /*< switch */
 | 
						|
retblock:
 | 
						|
        return query_str;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * create a GWBUFF with a MySQL ERR packet
 | 
						|
 *
 | 
						|
 * @param packet_number         MySQL protocol sequence number in the packet
 | 
						|
 * @param in_affected_rows      MySQL affected rows
 | 
						|
 * @param mysql_errno           The MySQL errno
 | 
						|
 * @param sqlstate_msg          The MySQL State Message
 | 
						|
 * @param mysql_message         The Error Message
 | 
						|
 * @return      The allocated GWBUF or NULL on failure
 | 
						|
*/
 | 
						|
GWBUF *modutil_create_mysql_err_msg(
 | 
						|
	int		packet_number,
 | 
						|
	int		affected_rows,
 | 
						|
	int		merrno,
 | 
						|
	const char	*statemsg,
 | 
						|
	const char	*msg)
 | 
						|
{
 | 
						|
	uint8_t		*outbuf = NULL;
 | 
						|
	uint32_t	mysql_payload_size = 0;
 | 
						|
	uint8_t		mysql_packet_header[4];
 | 
						|
	uint8_t		*mysql_payload = NULL;
 | 
						|
	uint8_t		field_count = 0;
 | 
						|
	uint8_t		mysql_err[2];
 | 
						|
	uint8_t		mysql_statemsg[6];
 | 
						|
	unsigned int	mysql_errno = 0;
 | 
						|
	const char	*mysql_error_msg = NULL;
 | 
						|
	const char	*mysql_state = NULL;
 | 
						|
	GWBUF		*errbuf = NULL;
 | 
						|
 | 
						|
	if (statemsg == NULL || msg == NULL)
 | 
						|
	{
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
        mysql_errno = (unsigned int)merrno;
 | 
						|
        mysql_error_msg = msg;
 | 
						|
        mysql_state = statemsg;
 | 
						|
 | 
						|
        field_count = 0xff;
 | 
						|
 | 
						|
        gw_mysql_set_byte2(mysql_err, mysql_errno);
 | 
						|
 | 
						|
        mysql_statemsg[0]='#';
 | 
						|
        memcpy(mysql_statemsg+1, mysql_state, 5);
 | 
						|
 | 
						|
        mysql_payload_size = sizeof(field_count) +
 | 
						|
                                sizeof(mysql_err) +
 | 
						|
                                sizeof(mysql_statemsg) +
 | 
						|
                                strlen(mysql_error_msg);
 | 
						|
 | 
						|
        /* allocate memory for packet header + payload */
 | 
						|
        errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
 | 
						|
        ss_dassert(errbuf != NULL);
 | 
						|
 | 
						|
        if (errbuf == NULL)
 | 
						|
	{
 | 
						|
                return NULL;
 | 
						|
	}
 | 
						|
        outbuf = GWBUF_DATA(errbuf);
 | 
						|
 | 
						|
        /** write packet header and packet number */
 | 
						|
        gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
 | 
						|
        mysql_packet_header[3] = packet_number;
 | 
						|
 | 
						|
        /** write header */
 | 
						|
        memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
 | 
						|
 | 
						|
        mysql_payload = outbuf + sizeof(mysql_packet_header);
 | 
						|
 | 
						|
        /** write field */
 | 
						|
        memcpy(mysql_payload, &field_count, sizeof(field_count));
 | 
						|
        mysql_payload = mysql_payload + sizeof(field_count);
 | 
						|
 | 
						|
        /** write errno */
 | 
						|
        memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
 | 
						|
        mysql_payload = mysql_payload + sizeof(mysql_err);
 | 
						|
 | 
						|
        /** write sqlstate */
 | 
						|
        memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
 | 
						|
        mysql_payload = mysql_payload + sizeof(mysql_statemsg);
 | 
						|
 | 
						|
        /** write error message */
 | 
						|
        memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
 | 
						|
 | 
						|
        return errbuf;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * modutil_send_mysql_err_packet
 | 
						|
 *
 | 
						|
 * Send a MySQL protocol Generic ERR message, to the dcb
 | 
						|
 *
 | 
						|
 * @param dcb 			The DCB to send the packet
 | 
						|
 * @param packet_number 	MySQL protocol sequence number in the packet 
 | 
						|
 * @param in_affected_rows	MySQL affected rows
 | 
						|
 * @param mysql_errno		The MySQL errno
 | 
						|
 * @param sqlstate_msg		The MySQL State Message
 | 
						|
 * @param mysql_message		The Error Message
 | 
						|
 * @return	0 for successful dcb write or 1 on failure
 | 
						|
 *
 | 
						|
 */
 | 
						|
int modutil_send_mysql_err_packet (
 | 
						|
	DCB		*dcb,
 | 
						|
	int		packet_number,
 | 
						|
	int		in_affected_rows,
 | 
						|
	int		mysql_errno,
 | 
						|
	const char	*sqlstate_msg,	
 | 
						|
	const char	*mysql_message)
 | 
						|
{
 | 
						|
        GWBUF* buf;
 | 
						|
 | 
						|
        buf = modutil_create_mysql_err_msg(packet_number, in_affected_rows, mysql_errno, sqlstate_msg, mysql_message);
 | 
						|
   
 | 
						|
        return dcb->func.write(dcb, buf);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Buffer contains at least one of the following:
 | 
						|
 * complete [complete] [partial] mysql packet
 | 
						|
 * 
 | 
						|
 * return pointer to gwbuf containing a complete packet or
 | 
						|
 *   NULL if no complete packet was found.
 | 
						|
 */
 | 
						|
GWBUF* modutil_get_next_MySQL_packet(
 | 
						|
	GWBUF** p_readbuf)
 | 
						|
{
 | 
						|
	GWBUF*   packetbuf;
 | 
						|
	GWBUF*   readbuf;
 | 
						|
	size_t   buflen;
 | 
						|
	size_t   packetlen;
 | 
						|
	size_t   totalbuflen;
 | 
						|
	uint8_t* data;
 | 
						|
	size_t   nbytes_copied = 0;
 | 
						|
	uint8_t* target;
 | 
						|
	
 | 
						|
	readbuf = *p_readbuf;
 | 
						|
	
 | 
						|
	if (readbuf == NULL)
 | 
						|
	{
 | 
						|
		packetbuf = NULL;
 | 
						|
		goto return_packetbuf;
 | 
						|
	}                
 | 
						|
	CHK_GWBUF(readbuf);
 | 
						|
	
 | 
						|
	if (GWBUF_EMPTY(readbuf))
 | 
						|
	{
 | 
						|
		packetbuf = NULL;
 | 
						|
		goto return_packetbuf;
 | 
						|
	}        
 | 
						|
	totalbuflen = gwbuf_length(readbuf);
 | 
						|
	data        = (uint8_t *)GWBUF_DATA((readbuf));
 | 
						|
	packetlen   = MYSQL_GET_PACKET_LEN(data)+4;
 | 
						|
	
 | 
						|
	/** packet is incomplete */
 | 
						|
	if (packetlen > totalbuflen)
 | 
						|
	{
 | 
						|
		packetbuf = NULL;
 | 
						|
		goto return_packetbuf;
 | 
						|
	}
 | 
						|
	
 | 
						|
	packetbuf = gwbuf_alloc(packetlen);
 | 
						|
	target    = GWBUF_DATA(packetbuf);
 | 
						|
	packetbuf->gwbuf_type = readbuf->gwbuf_type; /*< Copy the type too */
 | 
						|
	/**
 | 
						|
	 * Copy first MySQL packet to packetbuf and leave posible other
 | 
						|
	 * packets to read buffer.
 | 
						|
	 */
 | 
						|
	while (nbytes_copied < packetlen && totalbuflen > 0)
 | 
						|
	{
 | 
						|
		uint8_t* src = GWBUF_DATA((*p_readbuf));
 | 
						|
		size_t   bytestocopy;
 | 
						|
		
 | 
						|
		buflen = GWBUF_LENGTH((*p_readbuf));
 | 
						|
		bytestocopy = MIN(buflen,packetlen-nbytes_copied);
 | 
						|
		
 | 
						|
		memcpy(target+nbytes_copied, src, bytestocopy);
 | 
						|
		*p_readbuf = gwbuf_consume((*p_readbuf), bytestocopy);
 | 
						|
		totalbuflen = gwbuf_length((*p_readbuf));
 | 
						|
		nbytes_copied += bytestocopy;
 | 
						|
	}
 | 
						|
	ss_dassert(buflen == 0 || nbytes_copied == packetlen);
 | 
						|
	
 | 
						|
return_packetbuf:
 | 
						|
	return packetbuf;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Parse the buffer and split complete packets into individual buffers.
 | 
						|
 * Any partial packets are left in the old buffer.
 | 
						|
 * @param p_readbuf Buffer to split
 | 
						|
 * @return Head of the chain of complete packets
 | 
						|
 */
 | 
						|
GWBUF* modutil_get_complete_packets(GWBUF** p_readbuf)
 | 
						|
{
 | 
						|
    GWBUF *buff = NULL, *packet = NULL;
 | 
						|
    
 | 
						|
    while((packet = modutil_get_next_MySQL_packet(p_readbuf)) != NULL)
 | 
						|
    {
 | 
						|
        buff = gwbuf_append(buff,packet);
 | 
						|
    }
 | 
						|
    
 | 
						|
    return buff;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Count the number of EOF, OK or ERR packets in the buffer. Only complete
 | 
						|
 * packets are inspected and the buffer is assumed to only contain whole packets.
 | 
						|
 * If partial packets are in the buffer, they are ingnored. The caller must handle the
 | 
						|
 * detection of partial packets in buffers.
 | 
						|
 * @param reply Buffer to use
 | 
						|
 * @param use_ok Whether the DEPRECATE_EOF flag is set
 | 
						|
 * @param n_found If there were previous packets found 
 | 
						|
 * @return Number of EOF packets
 | 
						|
 */
 | 
						|
int
 | 
						|
modutil_count_signal_packets(GWBUF *reply, int use_ok, int n_found)
 | 
						|
{
 | 
						|
    unsigned char* ptr = (unsigned char*) reply->start;
 | 
						|
    unsigned char* end = (unsigned char*) reply->end;
 | 
						|
    unsigned char* prev = ptr;
 | 
						|
    int pktlen, eof = 0, err = 0;
 | 
						|
    int errlen = 0, eoflen = 0;
 | 
						|
    int iserr = 0, iseof = 0;
 | 
						|
    while(ptr < end)
 | 
						|
    {
 | 
						|
 | 
						|
        pktlen = MYSQL_GET_PACKET_LEN(ptr) + 4;
 | 
						|
 | 
						|
        if((iserr = PTR_IS_ERR(ptr)) || (iseof = PTR_IS_EOF(ptr)))
 | 
						|
        {
 | 
						|
            if(iserr)
 | 
						|
            {
 | 
						|
                err++;
 | 
						|
                errlen = pktlen;
 | 
						|
            }
 | 
						|
            else if(iseof)
 | 
						|
            {
 | 
						|
                eof++;
 | 
						|
                eoflen = pktlen;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        if((ptr + pktlen) > end)
 | 
						|
        {
 | 
						|
            ptr = prev;    
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        
 | 
						|
        prev = ptr;
 | 
						|
        ptr += pktlen;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    /*
 | 
						|
     * If there were new EOF/ERR packets found, make sure that they are the last
 | 
						|
     * packet in the buffer.
 | 
						|
     */
 | 
						|
    if((eof || err) && n_found)
 | 
						|
    {
 | 
						|
        if(err)
 | 
						|
        {
 | 
						|
            ptr -= errlen;
 | 
						|
            if(!PTR_IS_ERR(ptr))
 | 
						|
                err = 0;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            ptr -= eoflen;
 | 
						|
            if(!PTR_IS_EOF(ptr))
 | 
						|
                eof = 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return(eof + err);
 | 
						|
}
 | 
						|
 | 
						|
void resultset_free(RESULTSET* rset)
 | 
						|
{
 | 
						|
    RSET_ROW *row,*tmp;
 | 
						|
    int i;
 | 
						|
    
 | 
						|
    row = rset->head;
 | 
						|
    
 | 
						|
    while(row)
 | 
						|
    {
 | 
						|
        tmp = row;
 | 
						|
        row = row->next;
 | 
						|
        for(i = 0;i<rset->columns;i++)
 | 
						|
        {
 | 
						|
            free(tmp->data[i]);
 | 
						|
        }
 | 
						|
        free(tmp);
 | 
						|
    }
 | 
						|
    free(rset);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
char* get_lenenc_str(void* data, int* len)
 | 
						|
{
 | 
						|
    unsigned char* ptr = data;
 | 
						|
    char* rval;    
 | 
						|
    long size, offset;
 | 
						|
    
 | 
						|
    if(data == NULL || len == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    
 | 
						|
    if(*ptr < 251)
 | 
						|
    {
 | 
						|
        size = *ptr;
 | 
						|
        offset = 1;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        switch(*(ptr))
 | 
						|
        {
 | 
						|
        case 0xfb:
 | 
						|
            *len = 1;
 | 
						|
            return NULL;
 | 
						|
        case 0xfc:
 | 
						|
            size = *(ptr + 1) + (*(ptr + 2) << 8);
 | 
						|
            offset = 2;
 | 
						|
            break;
 | 
						|
        case 0xfd:
 | 
						|
            size = *ptr + (*(ptr + 2) << 8) + (*(ptr + 3) << 16);
 | 
						|
            offset = 3;
 | 
						|
            break;
 | 
						|
        case 0xfe:
 | 
						|
            size = *ptr + ((*(ptr + 2) << 8)) + (*(ptr + 3) << 16) +
 | 
						|
                    (*(ptr + 4) << 24) + (*(ptr + 5) << 32) + (*(ptr + 6) << 40) + 
 | 
						|
                    (*(ptr + 7) << 48) + (*(ptr + 8) << 56);
 | 
						|
            offset = 8;
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            
 | 
						|
            return NULL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    rval = malloc(sizeof(char)*(size+1));
 | 
						|
    if(rval)
 | 
						|
    {
 | 
						|
        memcpy(rval,ptr + offset,size);
 | 
						|
        memset(rval + size,0,1);
 | 
						|
        
 | 
						|
    }
 | 
						|
    *len = size + offset;
 | 
						|
    return rval;
 | 
						|
}
 | 
						|
 | 
						|
RESULTSET*
 | 
						|
modutil_get_rows(GWBUF* buffer)
 | 
						|
{
 | 
						|
    RESULTSET* result;
 | 
						|
    RSET_ROW* row;
 | 
						|
    int columns,plen,slen,i,offset;
 | 
						|
    unsigned char *ptr,*end;
 | 
						|
 | 
						|
    if(buffer == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    ptr = (unsigned char*) buffer->start;
 | 
						|
    end = (unsigned char*) buffer->end;
 | 
						|
    
 | 
						|
    if(!PTR_IS_RESULTSET(ptr))
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    columns = *(ptr + 4);
 | 
						|
 | 
						|
    if((result = calloc(1, sizeof(RESULTSET))) == NULL)
 | 
						|
    {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    
 | 
						|
    result->columns = columns;    
 | 
						|
    
 | 
						|
    while(ptr < end)
 | 
						|
    {
 | 
						|
        plen = MYSQL_GET_PACKET_LEN(ptr) + 4;
 | 
						|
        if(PTR_IS_EOF(ptr))
 | 
						|
        {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        ptr += plen;
 | 
						|
    }
 | 
						|
    
 | 
						|
    ptr += plen;
 | 
						|
    
 | 
						|
    while(ptr < end)
 | 
						|
    {
 | 
						|
        
 | 
						|
        plen = MYSQL_GET_PACKET_LEN(ptr) + 4;
 | 
						|
        
 | 
						|
        if(PTR_IS_EOF(ptr) || PTR_IS_ERR(ptr))
 | 
						|
        {
 | 
						|
            /*
 | 
						|
             * The final EOF/ERR packet was found so the result set is parsed.
 | 
						|
             */
 | 
						|
            return result;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if(ptr + plen > end)
 | 
						|
        {
 | 
						|
            /*
 | 
						|
             * There is a partial packet in the buffer and this is not a complete
 | 
						|
             * result set. This is considered as an error and will cause the 
 | 
						|
             * deallocation of the resultset.
 | 
						|
             */
 | 
						|
            
 | 
						|
            resultset_free(result);
 | 
						|
            result = NULL;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if((row = calloc(1,sizeof(RSET_ROW))) == NULL)
 | 
						|
        {
 | 
						|
            resultset_free(result);
 | 
						|
            result = NULL;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if((row->data = malloc(sizeof(char*)*columns)) == NULL)
 | 
						|
        {
 | 
						|
            free(row);
 | 
						|
            resultset_free(result);
 | 
						|
            result = NULL;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        
 | 
						|
        offset = 4;
 | 
						|
        
 | 
						|
        for(i = 0;i<columns;i++)
 | 
						|
        {
 | 
						|
            row->data[i] = get_lenenc_str(ptr + offset,&slen);
 | 
						|
            offset += slen;
 | 
						|
        }
 | 
						|
        
 | 
						|
        row->next = result->head;
 | 
						|
        result->rows++;
 | 
						|
        result->head = row;
 | 
						|
        
 | 
						|
        ptr += plen;
 | 
						|
    }
 | 
						|
    
 | 
						|
return result;    
 | 
						|
}
 | 
						|
/**
 | 
						|
 * Create parse error and EPOLLIN event to event queue of the backend DCB.
 | 
						|
 * When event is notified the error message is processed as error reply and routed 
 | 
						|
 * upstream to client.
 | 
						|
 * 
 | 
						|
 * @param backend_dcb	DCB where event is added
 | 
						|
 * @param errstr	Plain-text string error
 | 
						|
 * @param flags		GWBUF type flags
 | 
						|
 */
 | 
						|
void modutil_reply_parse_error(
 | 
						|
	DCB*  	 backend_dcb,
 | 
						|
	char* 	 errstr,
 | 
						|
	uint32_t flags)
 | 
						|
{
 | 
						|
	CHK_DCB(backend_dcb);
 | 
						|
	modutil_reply_routing_error(backend_dcb, 1064, "42000", errstr, flags);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create authentication error and EPOLLIN event to event queue of the backend DCB.
 | 
						|
 * When event is notified the error message is processed as error reply and routed 
 | 
						|
 * upstream to client.
 | 
						|
 * 
 | 
						|
 * @param backend_dcb	DCB where event is added
 | 
						|
 * @param errstr	Plain-text string error
 | 
						|
 * @param flags		GWBUF type flags
 | 
						|
 */
 | 
						|
void modutil_reply_auth_error(
 | 
						|
	DCB*  	 backend_dcb,
 | 
						|
	char* 	 errstr,
 | 
						|
	uint32_t flags)
 | 
						|
{
 | 
						|
	CHK_DCB(backend_dcb);
 | 
						|
	modutil_reply_routing_error(backend_dcb, 1045, "28000", errstr, flags);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Create error message and EPOLLIN event to event queue of the backend DCB.
 | 
						|
 * When event is notified the message is processed as error reply and routed 
 | 
						|
 * upstream to client.
 | 
						|
 * 
 | 
						|
 * @param backend_dcb	DCB where event is added
 | 
						|
 * @param error		SQL error number
 | 
						|
 * @param state		SQL state
 | 
						|
 * @param errstr	Plain-text string error
 | 
						|
 * @param flags		GWBUF type flags
 | 
						|
 */
 | 
						|
static void modutil_reply_routing_error(
 | 
						|
	DCB*  	 backend_dcb,
 | 
						|
	int   	 error,
 | 
						|
	char* 	 state,
 | 
						|
	char* 	 errstr,
 | 
						|
	uint32_t flags)
 | 
						|
{
 | 
						|
	GWBUF* buf;
 | 
						|
	CHK_DCB(backend_dcb);
 | 
						|
	
 | 
						|
	buf = modutil_create_mysql_err_msg(1, 0, error, state, errstr);
 | 
						|
	free(errstr);
 | 
						|
	
 | 
						|
	if (buf == NULL)
 | 
						|
	{
 | 
						|
		LOGIF(LE, (skygw_log_write_flush(
 | 
						|
			LOGFILE_ERROR,
 | 
						|
			"Error : Creating routing error message failed."))); 
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	/** Set flags that help router to process reply correctly */
 | 
						|
	gwbuf_set_type(buf, flags);
 | 
						|
	/** Create an incoming event for backend DCB */
 | 
						|
	poll_add_epollin_event_to_dcb(backend_dcb, buf);
 | 
						|
	return;
 | 
						|
}
 |