summaryrefslogtreecommitdiff
path: root/ext/mysqlnd/mysqlnd_net.c
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2013-03-14 05:42:27 +0000
committer <>2013-04-03 16:25:08 +0000
commitc4dd7a1a684490673e25aaf4fabec5df138854c4 (patch)
tree4d57c44caae4480efff02b90b9be86f44bf25409 /ext/mysqlnd/mysqlnd_net.c
downloadphp2-master.tar.gz
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'ext/mysqlnd/mysqlnd_net.c')
-rw-r--r--ext/mysqlnd/mysqlnd_net.c1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/ext/mysqlnd/mysqlnd_net.c b/ext/mysqlnd/mysqlnd_net.c
new file mode 100644
index 0000000..21241b3
--- /dev/null
+++ b/ext/mysqlnd/mysqlnd_net.c
@@ -0,0 +1,1069 @@
+/*
+ +----------------------------------------------------------------------+
+ | PHP Version 5 |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 2006-2013 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Andrey Hristov <andrey@mysql.com> |
+ | Ulf Wendel <uwendel@mysql.com> |
+ | Georg Richter <georg@mysql.com> |
+ +----------------------------------------------------------------------+
+*/
+
+/* $Id: mysqlnd_ps.c 316906 2011-09-17 10:24:18Z pajoye $ */
+#include "php.h"
+#include "php_globals.h"
+#include "mysqlnd.h"
+#include "mysqlnd_priv.h"
+#include "mysqlnd_wireprotocol.h"
+#include "mysqlnd_statistics.h"
+#include "mysqlnd_debug.h"
+#include "mysqlnd_ext_plugin.h"
+#include "php_network.h"
+#include "zend_ini.h"
+#ifdef MYSQLND_COMPRESSION_ENABLED
+#include <zlib.h>
+#endif
+
+#ifndef PHP_WIN32
+#include <netinet/tcp.h>
+#else
+#include <winsock.h>
+#endif
+
+
+/* {{{ mysqlnd_set_sock_no_delay */
+static int
+mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC)
+{
+
+ int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
+ int ret = SUCCESS;
+ int flag = 1;
+ int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
+
+ DBG_ENTER("mysqlnd_set_sock_no_delay");
+
+ if (result == -1) {
+ ret = FAILURE;
+ }
+
+ DBG_RETURN(ret);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::network_read_ex */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, network_read_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
+ MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ enum_func_status return_value = PASS;
+ size_t to_read = count, ret;
+ size_t old_chunk_size = net->stream->chunk_size;
+ zend_uchar * p = buffer;
+
+ DBG_ENTER("mysqlnd_net::network_read_ex");
+ DBG_INF_FMT("count="MYSQLND_SZ_T_SPEC, count);
+
+ net->stream->chunk_size = MIN(to_read, net->options.net_read_buffer_size);
+ while (to_read) {
+ if (!(ret = php_stream_read(net->stream, (char *) p, to_read))) {
+ DBG_ERR_FMT("Error while reading header from socket");
+ return_value = FAIL;
+ break;
+ }
+ p += ret;
+ to_read -= ret;
+ }
+ MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read);
+ net->stream->chunk_size = old_chunk_size;
+ DBG_RETURN(return_value);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::network_write_ex */
+static size_t
+MYSQLND_METHOD(mysqlnd_net, network_write_ex)(MYSQLND_NET * const net, const zend_uchar * const buffer, const size_t count,
+ MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ size_t ret;
+ DBG_ENTER("mysqlnd_net::network_write_ex");
+ ret = php_stream_write(net->stream, (char *)buffer, count);
+ DBG_RETURN(ret);
+}
+/* }}} */
+
+/* {{{ mysqlnd_net::open_pipe */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, open_pipe)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
+ const zend_bool persistent,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+#if PHP_API_VERSION < 20100412
+ unsigned int streams_options = ENFORCE_SAFE_MODE;
+#else
+ unsigned int streams_options = 0;
+#endif
+ DBG_ENTER("mysqlnd_net::open_pipe");
+ if (persistent) {
+ streams_options |= STREAM_OPEN_PERSISTENT;
+ }
+ streams_options |= IGNORE_URL;
+ net->stream = php_stream_open_wrapper((char*) scheme + sizeof("pipe://") - 1, "r+", streams_options, NULL);
+ if (!net->stream) {
+ SET_CLIENT_ERROR(*error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown errror while connecting");
+ DBG_RETURN(FAIL);
+ }
+ /*
+ Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
+ be registered as resource (in EG(regular_list). So far, so good. However, it won't be
+ unregistered yntil the script ends. So, we need to take care of that.
+ */
+ net->stream->in_free = 1;
+ zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id);
+ net->stream->in_free = 0;
+
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::open_tcp_or_unix */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
+ const zend_bool persistent,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+#if PHP_API_VERSION < 20100412
+ unsigned int streams_options = ENFORCE_SAFE_MODE;
+#else
+ unsigned int streams_options = 0;
+#endif
+ unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
+ char * hashed_details = NULL;
+ int hashed_details_len = 0;
+ char * errstr = NULL;
+ int errcode = 0;
+ struct timeval tv;
+
+ DBG_ENTER("mysqlnd_net::open_tcp_or_unix");
+
+ if (persistent) {
+ hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", net);
+ DBG_INF_FMT("hashed_details=%s", hashed_details);
+ }
+
+ if (net->options.timeout_connect) {
+ tv.tv_sec = net->options.timeout_connect;
+ tv.tv_usec = 0;
+ }
+
+ DBG_INF_FMT("calling php_stream_xport_create");
+ net->stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags,
+ hashed_details, (net->options.timeout_connect) ? &tv : NULL,
+ NULL /*ctx*/, &errstr, &errcode);
+ if (errstr || !net->stream) {
+ DBG_ERR("Error");
+ if (hashed_details) {
+ mnd_sprintf_free(hashed_details);
+ }
+ errcode = CR_CONNECTION_ERROR;
+ SET_CLIENT_ERROR(*error_info, errcode? errcode:CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, errstr);
+ if (errstr) {
+ /* no mnd_ since we don't allocate it */
+ efree(errstr);
+ }
+ DBG_RETURN(FAIL);
+ }
+ if (hashed_details) {
+ /*
+ If persistent, the streams register it in EG(persistent_list).
+ This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
+ whatever they have to.
+ */
+ zend_rsrc_list_entry *le;
+
+ if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1, (void*) &le) == SUCCESS) {
+ /*
+ in_free will let streams code skip destructing - big HACK,
+ but STREAMS suck big time regarding persistent streams.
+ Just not compatible for extensions that need persistency.
+ */
+ net->stream->in_free = 1;
+ zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1);
+ net->stream->in_free = 0;
+ }
+#if ZEND_DEBUG
+ /* Shut-up the streams, they don't know what they are doing */
+ net->stream->__exposed = 1;
+#endif
+ mnd_sprintf_free(hashed_details);
+ }
+
+ /*
+ Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
+ be registered as resource (in EG(regular_list). So far, so good. However, it won't be
+ unregistered yntil the script ends. So, we need to take care of that.
+ */
+ net->stream->in_free = 1;
+ zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id);
+ net->stream->in_free = 0;
+
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::connect_ex */
+static void
+MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt)(MYSQLND_NET * const net,
+ const char * const scheme, const size_t scheme_len,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net::post_connect_set_opt");
+ if (net->options.timeout_read) {
+ struct timeval tv;
+ DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
+ tv.tv_sec = net->options.timeout_read;
+ tv.tv_usec = 0;
+ php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
+ }
+
+ if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) {
+ /* TCP -> Set TCP_NODELAY */
+ mysqlnd_set_sock_no_delay(net->stream TSRMLS_CC);
+ }
+
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::connect_ex */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, connect_ex)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len,
+ const zend_bool persistent,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ enum_func_status ret = FAIL;
+ func_mysqlnd_net__open_stream open_stream = NULL;
+ DBG_ENTER("mysqlnd_net::connect_ex");
+
+ net->packet_no = net->compressed_envelope_packet_no = 0;
+
+ net->m.close_stream(net, conn_stats, error_info TSRMLS_CC);
+
+ open_stream = (scheme_len > (sizeof("pipe://") - 1) && !memcmp(scheme, "pipe://", sizeof("pipe://") - 1))? net->m.open_pipe:
+ net->m.open_tcp_or_unix;
+
+ if (PASS == (ret = open_stream(net, scheme, scheme_len, persistent, conn_stats, error_info TSRMLS_CC))) {
+ net->m.post_connect_set_opt(net, scheme, scheme_len, conn_stats, error_info TSRMLS_CC);
+ }
+
+ DBG_RETURN(ret);
+}
+/* }}} */
+
+
+/* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
+#define COPY_HEADER(T,A) do { \
+ *(((char *)(T))) = *(((char *)(A)));\
+ *(((char *)(T))+1) = *(((char *)(A))+1);\
+ *(((char *)(T))+2) = *(((char *)(A))+2);\
+ *(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
+#define STORE_HEADER_SIZE(safe_storage, buffer) COPY_HEADER((safe_storage), (buffer))
+#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
+
+
+/* {{{ mysqlnd_net::send_ex */
+/*
+ IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!!
+ This is done for performance reasons in the caller of this function.
+ Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
+ Neither are quick, thus the clients of this function are obligated to do
+ what they are asked for.
+
+ `count` is actually the length of the payload data. Thus :
+ count + MYSQLND_HEADER_SIZE = sizeof(buffer) (not the pointer but the actual buffer)
+*/
+static size_t
+MYSQLND_METHOD(mysqlnd_net, send_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
+ zend_uchar * safe_storage = safe_buf;
+ size_t bytes_sent, packets_sent = 1;
+ size_t left = count;
+ zend_uchar * p = (zend_uchar *) buffer;
+ zend_uchar * compress_buf = NULL;
+ size_t to_be_sent;
+
+ DBG_ENTER("mysqlnd_net::send_ex");
+ DBG_INF_FMT("count=" MYSQLND_SZ_T_SPEC " compression=%u", count, net->compressed);
+
+ if (net->compressed == TRUE) {
+ size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
+ DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size);
+ compress_buf = mnd_emalloc(comp_buf_size);
+ }
+
+ do {
+ to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ if (net->compressed == TRUE) {
+ /* here we need to compress the data and then write it, first comes the compressed header */
+ size_t tmp_complen = to_be_sent;
+ size_t payload_size;
+ zend_uchar * uncompressed_payload = p; /* should include the header */
+
+ STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
+ int3store(uncompressed_payload, to_be_sent);
+ int1store(uncompressed_payload + 3, net->packet_no);
+ if (PASS == net->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
+ uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC))
+ {
+ int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
+ payload_size = tmp_complen;
+ } else {
+ int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
+ memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
+ payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
+ }
+ RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
+
+ int3store(compress_buf, payload_size);
+ int1store(compress_buf + 3, net->packet_no);
+ DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
+ bytes_sent = net->m.network_write_ex(net, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE,
+ conn_stats, error_info TSRMLS_CC);
+ net->compressed_envelope_packet_no++;
+ #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
+ if (res == Z_OK) {
+ size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
+ zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
+ int error = net->m.decode(decompressed_data, decompressed_size,
+ compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
+ if (error == Z_OK) {
+ int i;
+ DBG_INF("success decompressing");
+ for (i = 0 ; i < decompressed_size; i++) {
+ if (i && (i % 30 == 0)) {
+ printf("\n\t\t");
+ }
+ printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
+ DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
+ }
+ } else {
+ DBG_INF("error decompressing");
+ }
+ mnd_free(decompressed_data);
+ }
+ #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
+ } else
+#endif /* MYSQLND_COMPRESSION_ENABLED */
+ {
+ DBG_INF("no compression");
+ STORE_HEADER_SIZE(safe_storage, p);
+ int3store(p, to_be_sent);
+ int1store(p + 3, net->packet_no);
+ bytes_sent = net->m.network_write_ex(net, p, to_be_sent + MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC);
+ RESTORE_HEADER_SIZE(p, safe_storage);
+ net->compressed_envelope_packet_no++;
+ }
+ net->packet_no++;
+
+ p += to_be_sent;
+ left -= to_be_sent;
+ packets_sent++;
+ /*
+ if left is 0 then there is nothing more to send, but if the last packet was exactly
+ with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
+ empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
+ indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
+ packet will be sent and this loop will end.
+ */
+ } while (bytes_sent && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
+
+ DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no);
+
+ MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats,
+ STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
+ STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
+ STAT_PACKETS_SENT, packets_sent);
+
+ if (compress_buf) {
+ mnd_efree(compress_buf);
+ }
+
+ /* Even for zero size payload we have to send a packet */
+ if (!bytes_sent) {
+ DBG_ERR_FMT("Can't %u send bytes", count);
+ SET_CLIENT_ERROR(*error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
+ }
+ DBG_RETURN(bytes_sent);
+}
+/* }}} */
+
+
+#ifdef MYSQLND_COMPRESSION_ENABLED
+/* {{{ php_mysqlnd_read_buffer_is_empty */
+static zend_bool
+php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)
+{
+ return buffer->len? FALSE:TRUE;
+}
+/* }}} */
+
+
+/* {{{ php_mysqlnd_read_buffer_read */
+static void
+php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest)
+{
+ if (buffer->len >= count) {
+ memcpy(dest, buffer->data + buffer->offset, count);
+ buffer->offset += count;
+ buffer->len -= count;
+ }
+}
+/* }}} */
+
+
+/* {{{ php_mysqlnd_read_buffer_bytes_left */
+static size_t
+php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)
+{
+ return buffer->len;
+}
+/* }}} */
+
+
+/* {{{ php_mysqlnd_read_buffer_free */
+static void
+php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)
+{
+ DBG_ENTER("php_mysqlnd_read_buffer_free");
+ if (*buffer) {
+ mnd_efree((*buffer)->data);
+ mnd_efree(*buffer);
+ *buffer = NULL;
+ }
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ php_mysqlnd_create_read_buffer */
+static MYSQLND_READ_BUFFER *
+mysqlnd_create_read_buffer(size_t count TSRMLS_DC)
+{
+ MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER));
+ DBG_ENTER("mysqlnd_create_read_buffer");
+ ret->is_empty = php_mysqlnd_read_buffer_is_empty;
+ ret->read = php_mysqlnd_read_buffer_read;
+ ret->bytes_left = php_mysqlnd_read_buffer_bytes_left;
+ ret->free_buffer = php_mysqlnd_read_buffer_free;
+ ret->data = mnd_emalloc(count);
+ ret->size = ret->len = count;
+ ret->offset = 0;
+ DBG_RETURN(ret);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffer */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer)
+ (MYSQLND_NET * net, size_t net_payload_size, MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC)
+{
+ size_t decompressed_size;
+ enum_func_status ret = PASS;
+ zend_uchar * compressed_data = NULL;
+ zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
+ DBG_ENTER("mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffe");
+
+ /* Read the compressed header */
+ if (FAIL == net->m.network_read_ex(net, comp_header, COMPRESSED_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) {
+ DBG_RETURN(FAIL);
+ }
+ decompressed_size = uint3korr(comp_header);
+
+ /* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
+ /* we need to decompress the data */
+
+ if (decompressed_size) {
+ compressed_data = mnd_emalloc(net_payload_size);
+ if (FAIL == net->m.network_read_ex(net, compressed_data, net_payload_size, conn_stats, error_info TSRMLS_CC)) {
+ ret = FAIL;
+ goto end;
+ }
+ net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size TSRMLS_CC);
+ ret = net->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size TSRMLS_CC);
+ if (ret == FAIL) {
+ goto end;
+ }
+ } else {
+ DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size);
+ net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size TSRMLS_CC);
+ if (FAIL == net->m.network_read_ex(net, net->uncompressed_data->data, net_payload_size, conn_stats, error_info TSRMLS_CC)) {
+ ret = FAIL;
+ goto end;
+ }
+ }
+end:
+ if (compressed_data) {
+ mnd_efree(compressed_data);
+ }
+ DBG_RETURN(ret);
+}
+/* }}} */
+#endif /* MYSQLND_COMPRESSION_ENABLED */
+
+
+/* {{{ mysqlnd_net::decode */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, const size_t uncompressed_data_len,
+ const zend_uchar * const compressed_data, const size_t compressed_data_len TSRMLS_DC)
+{
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ int error;
+ uLongf tmp_complen = uncompressed_data_len;
+ DBG_ENTER("mysqlnd_net::decode");
+ error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
+
+ DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len);
+ if (error != Z_OK) {
+ DBG_INF_FMT("decompression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
+ }
+ DBG_RETURN(error == Z_OK? PASS:FAIL);
+#else
+ DBG_ENTER("mysqlnd_net::decode");
+ DBG_RETURN(FAIL);
+#endif
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::encode */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
+ const zend_uchar * const uncompressed_data, const size_t uncompressed_data_len TSRMLS_DC)
+{
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ int error;
+ uLongf tmp_complen = *compress_buffer_len;
+ DBG_ENTER("mysqlnd_net::encode");
+ error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
+
+ if (error != Z_OK) {
+ DBG_INF_FMT("compression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
+ } else {
+ *compress_buffer_len = tmp_complen;
+ DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
+ }
+
+ DBG_RETURN(error == Z_OK? PASS:FAIL);
+#else
+ DBG_ENTER("mysqlnd_net::encode");
+ DBG_RETURN(FAIL);
+#endif
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::receive_ex */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, receive_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count,
+ MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ size_t to_read = count;
+ zend_uchar * p = buffer;
+
+ DBG_ENTER("mysqlnd_net::receive_ex");
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ if (net->compressed) {
+ if (net->uncompressed_data) {
+ size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read);
+ DBG_INF_FMT("reading %u from uncompressed_data buffer", to_read_from_buffer);
+ if (to_read_from_buffer) {
+ net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
+ p += to_read_from_buffer;
+ to_read -= to_read_from_buffer;
+ }
+ DBG_INF_FMT("left %u to read", to_read);
+ if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) {
+ /* Everything was consumed. This should never happen here, but for security */
+ net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
+ }
+ }
+ if (to_read) {
+ zend_uchar net_header[MYSQLND_HEADER_SIZE];
+ size_t net_payload_size;
+ zend_uchar packet_no;
+
+ if (FAIL == net->m.network_read_ex(net, net_header, MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) {
+ DBG_RETURN(FAIL);
+ }
+ net_payload_size = uint3korr(net_header);
+ packet_no = uint1korr(net_header + 3);
+ if (net->compressed_envelope_packet_no != packet_no) {
+ DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
+ net->compressed_envelope_packet_no, packet_no, net_payload_size);
+
+ php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
+ net->compressed_envelope_packet_no, packet_no, net_payload_size);
+ DBG_RETURN(FAIL);
+ }
+ net->compressed_envelope_packet_no++;
+#ifdef MYSQLND_DUMP_HEADER_N_BODY
+ DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (unsigned long) net_payload_size);
+#endif
+ /* Now let's read from the wire, decompress it and fill the read buffer */
+ net->m.read_compressed_packet_from_stream_and_fill_read_buffer(net, net_payload_size, conn_stats, error_info TSRMLS_CC);
+
+ /*
+ Now a bit of recursion - read from the read buffer,
+ if the data which we have just read from the wire
+ is not enough, then the recursive call will try to
+ satisfy it until it is satisfied.
+ */
+ DBG_RETURN(net->m.receive_ex(net, p, to_read, conn_stats, error_info TSRMLS_CC));
+ }
+ DBG_RETURN(PASS);
+ }
+#endif /* MYSQLND_COMPRESSION_ENABLED */
+ DBG_RETURN(net->m.network_read_ex(net, p, to_read, conn_stats, error_info TSRMLS_CC));
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::set_client_option */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net::set_client_option");
+ DBG_INF_FMT("option=%u", option);
+ switch (option) {
+ case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
+ DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
+ if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
+ DBG_RETURN(FAIL);
+ }
+ net->cmd_buffer.length = *(unsigned int*) value;
+ DBG_INF_FMT("new_length=%u", net->cmd_buffer.length);
+ if (!net->cmd_buffer.buffer) {
+ net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent);
+ } else {
+ net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent);
+ }
+ break;
+ case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
+ DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
+ net->options.net_read_buffer_size = *(unsigned int*) value;
+ DBG_INF_FMT("new_length=%u", net->options.net_read_buffer_size);
+ break;
+ case MYSQL_OPT_CONNECT_TIMEOUT:
+ DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
+ net->options.timeout_connect = *(unsigned int*) value;
+ break;
+ case MYSQLND_OPT_SSL_KEY:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_key) {
+ mnd_pefree(net->options.ssl_key, pers);
+ }
+ net->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQLND_OPT_SSL_CERT:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_cert) {
+ mnd_pefree(net->options.ssl_cert, pers);
+ }
+ net->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQLND_OPT_SSL_CA:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_ca) {
+ mnd_pefree(net->options.ssl_ca, pers);
+ }
+ net->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQLND_OPT_SSL_CAPATH:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_capath) {
+ mnd_pefree(net->options.ssl_capath, pers);
+ }
+ net->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQLND_OPT_SSL_CIPHER:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_cipher) {
+ mnd_pefree(net->options.ssl_cipher, pers);
+ }
+ net->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQLND_OPT_SSL_PASSPHRASE:
+ {
+ zend_bool pers = net->persistent;
+ if (net->options.ssl_passphrase) {
+ mnd_pefree(net->options.ssl_passphrase, pers);
+ }
+ net->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
+ break;
+ }
+ case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
+ net->options.ssl_verify_peer = value? ((*(zend_bool *)value)? TRUE:FALSE): FALSE;
+ break;
+ case MYSQL_OPT_READ_TIMEOUT:
+ net->options.timeout_read = *(unsigned int*) value;
+ break;
+#ifdef WHEN_SUPPORTED_BY_MYSQLI
+ case MYSQL_OPT_WRITE_TIMEOUT:
+ net->options.timeout_write = *(unsigned int*) value;
+ break;
+#endif
+ case MYSQL_OPT_COMPRESS:
+ net->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION;
+ break;
+ default:
+ DBG_RETURN(FAIL);
+ }
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+/* {{{ mysqlnd_net::consume_uneaten_data */
+size_t
+MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC)
+{
+#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
+ /*
+ Switch to non-blocking mode and try to consume something from
+ the line, if possible, then continue. This saves us from looking for
+ the actuall place where out-of-order packets have been sent.
+ If someone is completely sure that everything is fine, he can switch it
+ off.
+ */
+ char tmp_buf[256];
+ size_t skipped_bytes = 0;
+ int opt = PHP_STREAM_OPTION_BLOCKING;
+ int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC);
+
+ DBG_ENTER("mysqlnd_net::consume_uneaten_data");
+
+ if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
+ /* Do a read of 1 byte */
+ int bytes_consumed;
+
+ do {
+ skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf)));
+ } while (bytes_consumed == sizeof(tmp_buf));
+
+ if (was_blocked) {
+ net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC);
+ }
+
+ if (bytes_consumed) {
+ DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server",
+ bytes_consumed, mysqlnd_command_to_text[net->last_command]);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
+ "consumed all the output from the server",
+ bytes_consumed, mysqlnd_command_to_text[net->last_command]);
+ }
+ }
+ net->last_command = cmd;
+
+ DBG_RETURN(skipped_bytes);
+#else
+ return 0;
+#endif
+}
+/* }}} */
+
+/*
+ in libmyusql, if cert and !key then key=cert
+*/
+/* {{{ mysqlnd_net::enable_ssl */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
+{
+#ifdef MYSQLND_SSL_SUPPORTED
+ php_stream_context *context = php_stream_context_alloc(TSRMLS_C);
+ DBG_ENTER("mysqlnd_net::enable_ssl");
+ if (!context) {
+ DBG_RETURN(FAIL);
+ }
+
+ if (net->options.ssl_key) {
+ zval key_zval;
+ ZVAL_STRING(&key_zval, net->options.ssl_key, 0);
+ php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
+ }
+ if (net->options.ssl_verify_peer) {
+ zval verify_peer_zval;
+ ZVAL_TRUE(&verify_peer_zval);
+ php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
+ }
+ if (net->options.ssl_cert) {
+ zval cert_zval;
+ ZVAL_STRING(&cert_zval, net->options.ssl_cert, 0);
+ php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
+ if (!net->options.ssl_key) {
+ php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
+ }
+ }
+ if (net->options.ssl_ca) {
+ zval cafile_zval;
+ ZVAL_STRING(&cafile_zval, net->options.ssl_ca, 0);
+ php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
+ }
+ if (net->options.ssl_capath) {
+ zval capath_zval;
+ ZVAL_STRING(&capath_zval, net->options.ssl_capath, 0);
+ php_stream_context_set_option(context, "ssl", "cafile", &capath_zval);
+ }
+ if (net->options.ssl_passphrase) {
+ zval passphrase_zval;
+ ZVAL_STRING(&passphrase_zval, net->options.ssl_passphrase, 0);
+ php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
+ }
+ if (net->options.ssl_cipher) {
+ zval cipher_zval;
+ ZVAL_STRING(&cipher_zval, net->options.ssl_cipher, 0);
+ php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
+ }
+ php_stream_context_set(net->stream, context);
+ if (php_stream_xport_crypto_setup(net->stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL TSRMLS_CC) < 0 ||
+ php_stream_xport_crypto_enable(net->stream, 1 TSRMLS_CC) < 0)
+ {
+ DBG_ERR("Cannot connect to MySQL by using SSL");
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot connect to MySQL by using SSL");
+ DBG_RETURN(FAIL);
+ }
+ /*
+ get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
+ then the context would not survive cleaning of EG(regular_list), where it is registered, as a
+ resource. What happens is that after this destruction any use of the network will mean usage
+ of the context, which means usage of already freed memory, bad. Actually we don't need this
+ context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
+ */
+ php_stream_context_set(net->stream, NULL);
+
+ if (net->options.timeout_read) {
+ struct timeval tv;
+ DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
+ tv.tv_sec = net->options.timeout_read;
+ tv.tv_usec = 0;
+ php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
+ }
+
+ DBG_RETURN(PASS);
+#else
+ DBG_ENTER("mysqlnd_net::enable_ssl");
+ DBG_RETURN(PASS);
+#endif
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::disable_ssl */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net::disable_ssl");
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::free_contents */
+static void
+MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net TSRMLS_DC)
+{
+ zend_bool pers = net->persistent;
+ DBG_ENTER("mysqlnd_net::free_contents");
+
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ if (net->uncompressed_data) {
+ net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
+ }
+#endif
+ if (net->options.ssl_key) {
+ mnd_pefree(net->options.ssl_key, pers);
+ net->options.ssl_key = NULL;
+ }
+ if (net->options.ssl_cert) {
+ mnd_pefree(net->options.ssl_cert, pers);
+ net->options.ssl_cert = NULL;
+ }
+ if (net->options.ssl_ca) {
+ mnd_pefree(net->options.ssl_ca, pers);
+ net->options.ssl_ca = NULL;
+ }
+ if (net->options.ssl_capath) {
+ mnd_pefree(net->options.ssl_capath, pers);
+ net->options.ssl_capath = NULL;
+ }
+ if (net->options.ssl_cipher) {
+ mnd_pefree(net->options.ssl_cipher, pers);
+ net->options.ssl_cipher = NULL;
+ }
+
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::close_stream */
+static void
+MYSQLND_METHOD(mysqlnd_net, close_stream)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net::close_stream");
+ if (net && net->stream) {
+ zend_bool pers = net->persistent;
+ DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
+ if (pers) {
+ if (EG(active)) {
+ php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
+ } else {
+ /*
+ otherwise we will crash because the EG(persistent_list) has been freed already,
+ before the modules are shut down
+ */
+ php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
+ }
+ } else {
+ php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
+ }
+ net->stream = NULL;
+ }
+
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::init */
+static enum_func_status
+MYSQLND_METHOD(mysqlnd_net, init)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ unsigned int buf_size;
+ DBG_ENTER("mysqlnd_net::init");
+
+ buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
+ net->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size TSRMLS_CC);
+
+ buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
+ net->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size TSRMLS_CC);
+
+ buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
+ net->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size TSRMLS_CC);
+
+ DBG_RETURN(PASS);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net::dtor */
+static void
+MYSQLND_METHOD(mysqlnd_net, dtor)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net::dtor");
+ if (net) {
+ zend_bool pers = net->persistent;
+
+ net->m.free_contents(net TSRMLS_CC);
+ net->m.close_stream(net, stats, error_info TSRMLS_CC);
+ if (net->cmd_buffer.buffer) {
+ DBG_INF("Freeing cmd buffer");
+ mnd_pefree(net->cmd_buffer.buffer, pers);
+ net->cmd_buffer.buffer = NULL;
+ }
+ mnd_pefree(net, pers);
+ }
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+MYSQLND_CLASS_METHODS_START(mysqlnd_net)
+ MYSQLND_METHOD(mysqlnd_net, init),
+ MYSQLND_METHOD(mysqlnd_net, dtor),
+ MYSQLND_METHOD(mysqlnd_net, connect_ex),
+ MYSQLND_METHOD(mysqlnd_net, close_stream),
+ MYSQLND_METHOD(mysqlnd_net, open_pipe),
+ MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix),
+ NULL, /* unused 1 */
+ NULL, /* unused 2 */
+ MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt),
+ MYSQLND_METHOD(mysqlnd_net, set_client_option),
+ MYSQLND_METHOD(mysqlnd_net, decode),
+ MYSQLND_METHOD(mysqlnd_net, encode),
+ MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
+ MYSQLND_METHOD(mysqlnd_net, free_contents),
+ MYSQLND_METHOD(mysqlnd_net, enable_ssl),
+ MYSQLND_METHOD(mysqlnd_net, disable_ssl),
+ MYSQLND_METHOD(mysqlnd_net, network_read_ex),
+ MYSQLND_METHOD(mysqlnd_net, network_write_ex),
+ MYSQLND_METHOD(mysqlnd_net, send_ex),
+ MYSQLND_METHOD(mysqlnd_net, receive_ex),
+#ifdef MYSQLND_COMPRESSION_ENABLED
+ MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer)
+#else
+ NULL
+#endif
+MYSQLND_CLASS_METHODS_END;
+
+
+/* {{{ mysqlnd_net_init */
+PHPAPI MYSQLND_NET *
+mysqlnd_net_init(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC)
+{
+ MYSQLND_NET * net;
+ DBG_ENTER("mysqlnd_net_init");
+ net = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_io_channel(persistent, stats, error_info TSRMLS_CC);
+ DBG_RETURN(net);
+}
+/* }}} */
+
+
+/* {{{ mysqlnd_net_free */
+PHPAPI void
+mysqlnd_net_free(MYSQLND_NET * const net, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC)
+{
+ DBG_ENTER("mysqlnd_net_free");
+ if (net) {
+ net->m.dtor(net, stats, error_info TSRMLS_CC);
+ }
+ DBG_VOID_RETURN;
+}
+/* }}} */
+
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */