diff --git a/include/maxscale/modutil.h b/include/maxscale/modutil.h index cb0be819a..05f3b1853 100644 --- a/include/maxscale/modutil.h +++ b/include/maxscale/modutil.h @@ -30,8 +30,6 @@ MXS_BEGIN_DECLS #define PTR_IS_ERR(b) (b[4] == 0xff) #define PTR_IS_LOCAL_INFILE(b) (b[4] == 0xfb) #define IS_FULL_RESPONSE(buf) (modutil_count_signal_packets(buf,0,0) == 2) -#define PTR_EOF_MORE_RESULTS(b) ((PTR_IS_EOF(b) && ptr[7] & 0x08)) - extern int modutil_is_SQL(GWBUF *); extern int modutil_is_SQL_prepare(GWBUF *); @@ -54,7 +52,23 @@ GWBUF* modutil_create_mysql_err_msg(int packet_number, const char *statemsg, const char *msg); -int modutil_count_signal_packets(GWBUF*, int, int, int*); +/** + * @brief Count the number of EOF and 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 ignored. + * The caller must handle the detection of partial packets in buffers. + * + * @param reply Buffer to use + * @param n_found Number of previous found packets + * @param more Set to true of more results exist + * @param offset_out Initial offset into the buffer. This offset is set to point + * to the first byte after the last packet in the buffer. + * + * @return Total number of EOF and ERR packets including the ones already found + */ +int modutil_count_signal_packets(GWBUF *reply, int n_found, bool* more, size_t* offset_out); + mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string); /** diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 020564a72..ccb35761d 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -114,6 +114,22 @@ MXS_BEGIN_DECLS #define MYSQL_DATABASE_MAXLEN 128 #define MYSQL_TABLE_MAXLEN 64 +/** Response packet status bits for OK and EOF packets */ +#define MXS_MYSQL_STATUS_IN_TRANS 0x0001 +#define MXS_MYSQL_STATUS_AUTOCOMMIT 0x0002 +#define MXS_MYSQL_MORE_RESULTS_EXISTS 0x0008 +#define MXS_MYSQL_STATUS_NO_GOOD_INDEX_USED 0x0010 +#define MXS_MYSQL_STATUS_NO_INDEX_USED 0x0020 +#define MXS_MYSQL_STATUS_CURSOR_EXISTS 0x0040 +#define MXS_MYSQL_STATUS_LAST_ROW_SENT 0x0080 +#define MXS_MYSQL_STATUS_DB_DROPPED 0x0100 +#define MXS_MYSQL_STATUS_NO_BACKSLASH_ESCAPES 0x0200 +#define MXS_MYSQL_STATUS_METADATA_CHANGED 0x0400 +#define MXS_MYSQL_QUERY_WAS_SLOW 0x0800 +#define MXS_MYSQL_PS_OUT_PARAMS 0x1000 +#define MXS_MYSQL_STATUS_IN_TRANS_READONLY 0x2000 +#define MXS_MYSQL_SESSION_STATE_CHANGED 0x4000 + #define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR) #define SMALL_CHUNK 1024 #define MAX_CHUNK SMALL_CHUNK * 8 * 4 @@ -427,8 +443,25 @@ int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* /** Check for OK packet */ bool mxs_mysql_is_ok_packet(GWBUF *buffer); -/** Check for result set */ -bool mxs_mysql_is_result_set(GWBUF *buffer); +/** + * @brief Check if a buffer contains a result set + * + * @param buffer Buffer to check + * @param offset Offset into the buffer + * + * @return True if the @c buffer at @c offset contains the start of a result set + */ +bool mxs_mysql_is_result_set(GWBUF *buffer, size_t offset); + +/** + * @brief Check if the OK packet is followed by another result + * + * @param buffer Buffer to check + * @param offset Offset into the buffer + * + * @return True if more results are expected + */ +bool mxs_mysql_more_results_after_ok(GWBUF *buffer, size_t extra_offset); /** Get current command for a session */ mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session); diff --git a/server/core/modutil.cc b/server/core/modutil.cc index 4472fe68f..f90cfdb20 100644 --- a/server/core/modutil.cc +++ b/server/core/modutil.cc @@ -633,83 +633,45 @@ GWBUF* modutil_get_complete_packets(GWBUF **p_readbuf) return complete; } -/** - * 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 ignored. 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, int* more) +int modutil_count_signal_packets(GWBUF *reply, int n_found, bool* more, size_t* offset) { - 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; - bool moreresults = false; - while (ptr < end) - { - pktlen = MYSQL_GET_PAYLOAD_LEN(ptr) + 4; + unsigned int len = gwbuf_length(reply); + int eof = 0; + int err = 0; - if ((iserr = PTR_IS_ERR(ptr)) || (iseof = PTR_IS_EOF(ptr))) + while (*offset < len) + { + uint8_t header[MYSQL_HEADER_LEN + 5]; // Maximum size of an EOF packet + + gwbuf_copy_data(reply, *offset, MYSQL_HEADER_LEN + 1, header); + + unsigned int pktlen = MYSQL_GET_PAYLOAD_LEN(header) + MYSQL_HEADER_LEN; + + if (MYSQL_GET_COMMAND(header) == MYSQL_REPLY_ERR) { - if (iserr) - { - err++; - errlen = pktlen; - } - else if (iseof) - { - eof++; - eoflen = pktlen; - } + err++; + } + else if (MYSQL_GET_COMMAND(header) == MYSQL_REPLY_EOF && + pktlen == 5 + MYSQL_HEADER_LEN) + { + eof++; } - if ((ptr + pktlen) > end || (eof + n_found) >= 2) + if (*offset + pktlen >= len || (eof + err + n_found) >= 2) { - moreresults = PTR_EOF_MORE_RESULTS(ptr); - ptr = prev; + gwbuf_copy_data(reply, *offset, sizeof(header), header); + uint16_t* status = (uint16_t*)(header + MYSQL_HEADER_LEN + 1 + 2); // Skip command and warning count + *more = ((*status) & MXS_MYSQL_MORE_RESULTS_EXISTS); + *offset += pktlen; break; } - prev = ptr; - ptr += pktlen; + *offset += pktlen; } + int total = err + eof + n_found; - /* - * 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; - } - } - } - - *more = moreresults; - - return (eof + err); + return total; } /** diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 6a7c70b78..4cdfc661b 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -700,8 +700,9 @@ gw_read_and_write(DCB *dcb) expecting_resultset(proto) && mxs_mysql_is_result_set(read_buffer)) { - int more = 0; - if (modutil_count_signal_packets(read_buffer, 0, 0, &more) != 2) + bool more = false; + size_t offset = 0; + if (modutil_count_signal_packets(read_buffer, 0, &more, &offset) != 2) { dcb->dcb_readqueue = read_buffer; return 0; diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 679182c8f..007c5fb18 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -48,6 +48,7 @@ #include #include #include +#include uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = ""; @@ -966,7 +967,7 @@ char *create_auth_fail_str(char *username, if (db_len > 0) { - sprintf(errstr, ferrstr, username, hostaddr, password ? "YES": "NO", db); + sprintf(errstr, ferrstr, username, hostaddr, password ? "YES" : "NO", db); } else if (errcode == MXS_AUTH_FAILED_SSL) { @@ -1537,12 +1538,12 @@ bool mxs_mysql_is_ok_packet(GWBUF *buffer) return rval; } -bool mxs_mysql_is_result_set(GWBUF *buffer) +bool mxs_mysql_is_result_set(GWBUF *buffer, size_t offset) { bool rval = false; uint8_t cmd; - if (gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, 1, &cmd)) + if (gwbuf_copy_data(buffer, offset + MYSQL_HEADER_LEN, 1, &cmd)) { switch (cmd) { @@ -1563,6 +1564,51 @@ bool mxs_mysql_is_result_set(GWBUF *buffer) return rval; } +bool mxs_mysql_more_results_after_ok(GWBUF *buffer, size_t extra_offset) +{ + bool rval = false; + size_t buflen = gwbuf_length(buffer); + size_t offset = extra_offset; + + while (offset < buflen) + { + // Copy the header + uint8_t header[MYSQL_HEADER_LEN + 1]; + + if (gwbuf_copy_data(buffer, offset, sizeof(header), header) != sizeof(header)) + { + break; + } + + size_t len = gw_mysql_get_byte3(header); + + if (header[4] == MYSQL_REPLY_OK) + { + // Copy the payload without the command byte + uint8_t data[len - 1]; + gwbuf_copy_data(buffer, offset + MYSQL_HEADER_LEN + 1, sizeof(data), data); + + uint8_t* ptr = data; + ptr += mxs_leint_bytes(ptr); + ptr += mxs_leint_bytes(ptr); + uint16_t* status = (uint16_t*)ptr; + rval = (*status) & MXS_MYSQL_MORE_RESULTS_EXISTS; + } + else + { + break; + } + + offset += len + MYSQL_HEADER_LEN; + + if (offset < buflen) + { + MXS_DEBUG("More data after an OK packet, expecting results: %s", rval ? "YES" : "NO"); + } + } + return rval; +} + mysql_server_cmd_t mxs_mysql_current_command(MXS_SESSION* session) { MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;