Annotation of embedaddon/php/ext/mysqlnd/mysqlnd_net.c, revision 1.1.1.1
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
5: | Copyright (c) 2006-2012 The PHP Group |
6: +----------------------------------------------------------------------+
7: | This source file is subject to version 3.01 of the PHP license, |
8: | that is bundled with this package in the file LICENSE, and is |
9: | available through the world-wide-web at the following url: |
10: | http://www.php.net/license/3_01.txt |
11: | If you did not receive a copy of the PHP license and are unable to |
12: | obtain it through the world-wide-web, please send a note to |
13: | license@php.net so we can mail you a copy immediately. |
14: +----------------------------------------------------------------------+
15: | Authors: Georg Richter <georg@mysql.com> |
16: | Andrey Hristov <andrey@mysql.com> |
17: | Ulf Wendel <uwendel@mysql.com> |
18: +----------------------------------------------------------------------+
19: */
20: #include "php.h"
21: #include "php_globals.h"
22: #include "mysqlnd.h"
23: #include "mysqlnd_priv.h"
24: #include "mysqlnd_wireprotocol.h"
25: #include "mysqlnd_statistics.h"
26: #include "mysqlnd_debug.h"
27: #include "ext/standard/sha1.h"
28: #include "php_network.h"
29: #include "zend_ini.h"
30: #ifdef MYSQLND_COMPRESSION_ENABLED
31: #include <zlib.h>
32: #endif
33:
34: #ifndef PHP_WIN32
35: #include <netinet/tcp.h>
36: #else
37: #include <winsock.h>
38: #endif
39:
40:
41: /* {{{ mysqlnd_set_sock_no_delay */
42: static int
43: mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC)
44: {
45:
46: int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
47: int ret = SUCCESS;
48: int flag = 1;
49: int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
50:
51: DBG_ENTER("mysqlnd_set_sock_no_delay");
52:
53: if (result == -1) {
54: ret = FAILURE;
55: }
56:
57: DBG_RETURN(ret);
58: }
59: /* }}} */
60:
61:
62: /* {{{ mysqlnd_net::network_read */
63: static enum_func_status
64: MYSQLND_METHOD(mysqlnd_net, network_read)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
65: {
66: enum_func_status return_value = PASS;
67: size_t to_read = count, ret;
68: size_t old_chunk_size = conn->net->stream->chunk_size;
69: DBG_ENTER("mysqlnd_net::network_read");
70: DBG_INF_FMT("count=%u", count);
71: conn->net->stream->chunk_size = MIN(to_read, conn->net->options.net_read_buffer_size);
72: while (to_read) {
73: if (!(ret = php_stream_read(conn->net->stream, (char *) buffer, to_read))) {
74: DBG_ERR_FMT("Error while reading header from socket");
75: return_value = FAIL;
76: break;
77: }
78: buffer += ret;
79: to_read -= ret;
80: }
81: MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_BYTES_RECEIVED, count - to_read);
82: conn->net->stream->chunk_size = old_chunk_size;
83: DBG_RETURN(return_value);
84: }
85: /* }}} */
86:
87:
88: /* {{{ mysqlnd_net::network_write */
89: static size_t
90: MYSQLND_METHOD(mysqlnd_net, network_write)(MYSQLND * const conn, const zend_uchar * const buf, size_t count TSRMLS_DC)
91: {
92: size_t ret;
93: DBG_ENTER("mysqlnd_net::network_write");
94: ret = php_stream_write(conn->net->stream, (char *)buf, count);
95: DBG_RETURN(ret);
96: }
97: /* }}} */
98:
99:
100:
101: /* {{{ mysqlnd_net::connect */
102: static enum_func_status
103: MYSQLND_METHOD(mysqlnd_net, connect)(MYSQLND_NET * net, const char * const scheme, size_t scheme_len, zend_bool persistent, char **errstr, int * errcode TSRMLS_DC)
104: {
105: #if PHP_API_VERSION < 20100412
106: unsigned int streams_options = ENFORCE_SAFE_MODE;
107: #else
108: unsigned int streams_options = 0;
109: #endif
110: unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
111: char * hashed_details = NULL;
112: int hashed_details_len = 0;
113: struct timeval tv;
114: DBG_ENTER("mysqlnd_net::connect");
115:
116: if (persistent) {
117: hashed_details_len = spprintf(&hashed_details, 0, "%p", net);
118: DBG_INF_FMT("hashed_details=%s", hashed_details);
119: }
120:
121: net->packet_no = net->compressed_envelope_packet_no = 0;
122:
123: if (net->stream) {
124: /* close before opening a new one */
125: DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
126: if (net->persistent) {
127: php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
128: } else {
129: php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
130: }
131: net->stream = NULL;
132: }
133:
134: if (net->options.timeout_connect) {
135: tv.tv_sec = net->options.timeout_connect;
136: tv.tv_usec = 0;
137: }
138:
139: DBG_INF_FMT("calling php_stream_xport_create");
140: net->stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags,
141: hashed_details, (net->options.timeout_connect) ? &tv : NULL,
142: NULL /*ctx*/, errstr, errcode);
143:
144: if (*errstr || !net->stream) {
145: if (hashed_details) {
146: efree(hashed_details); /* allocated by spprintf */
147: }
148: *errcode = CR_CONNECTION_ERROR;
149: DBG_RETURN(FAIL);
150: }
151:
152: if (hashed_details) {
153: /*
154: If persistent, the streams register it in EG(persistent_list).
155: This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
156: whatever they have to.
157: */
158: zend_rsrc_list_entry *le;
159:
160: if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1, (void*) &le) == SUCCESS) {
161: /*
162: in_free will let streams code skip destructing - big HACK,
163: but STREAMS suck big time regarding persistent streams.
164: Just not compatible for extensions that need persistency.
165: */
166: net->stream->in_free = 1;
167: zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1);
168: net->stream->in_free = 0;
169: }
170: #if ZEND_DEBUG
171: /* Shut-up the streams, they don't know what they are doing */
172: net->stream->__exposed = 1;
173: #endif
174: efree(hashed_details);
175: }
176: /*
177: Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
178: be registered as resource (in EG(regular_list). So far, so good. However, it won't be
179: unregistered till the script ends. So, we need to take care of that.
180: */
181: net->stream->in_free = 1;
182: zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id);
183: net->stream->in_free = 0;
184:
185: if (!net->options.timeout_read) {
186: /* should always happen because read_timeout cannot be set via API */
187: net->options.timeout_read = (unsigned int) MYSQLND_G(net_read_timeout);
188: }
189: if (net->options.timeout_read) {
190: DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
191: tv.tv_sec = net->options.timeout_read;
192: tv.tv_usec = 0;
193: php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
194: }
195:
196: if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) {
197: /* TCP -> Set TCP_NODELAY */
198: mysqlnd_set_sock_no_delay(net->stream TSRMLS_CC);
199: }
200:
201: {
202: unsigned int buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
203: net->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size TSRMLS_CC);
204: }
205:
206:
207: DBG_RETURN(PASS);
208: }
209: /* }}} */
210:
211:
212: /* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
213: #define COPY_HEADER(T,A) do { \
214: *(((char *)(T))) = *(((char *)(A)));\
215: *(((char *)(T))+1) = *(((char *)(A))+1);\
216: *(((char *)(T))+2) = *(((char *)(A))+2);\
217: *(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
218: #define STORE_HEADER_SIZE(safe_storage, buffer) COPY_HEADER((safe_storage), (buffer))
219: #define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
220:
221: /* {{{ mysqlnd_net::send */
222: /*
223: IMPORTANT : It's expected that buf has place in the beginning for MYSQLND_HEADER_SIZE !!!!
224: This is done for performance reasons in the caller of this function.
225: Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
226: Neither are quick, thus the clients of this function are obligated to do
227: what they are asked for.
228:
229: `count` is actually the length of the payload data. Thus :
230: count + MYSQLND_HEADER_SIZE = sizeof(buf) (not the pointer but the actual buffer)
231: */
232: size_t
233: MYSQLND_METHOD(mysqlnd_net, send)(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC)
234: {
235: zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
236: zend_uchar *safe_storage = safe_buf;
237: MYSQLND_NET *net = conn->net;
238: size_t old_chunk_size = net->stream->chunk_size;
239: size_t ret, packets_sent = 1;
240: size_t left = count;
241: zend_uchar *p = (zend_uchar *) buf;
242: zend_uchar * compress_buf = NULL;
243: size_t to_be_sent;
244:
245: DBG_ENTER("mysqlnd_net::send");
246: DBG_INF_FMT("conn=%llu count=%lu compression=%u", conn->thread_id, count, net->compressed);
247:
248: net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE;
249:
250: if (net->compressed == TRUE) {
251: size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
252: DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size);
253: compress_buf = mnd_emalloc(comp_buf_size);
254: }
255:
256: do {
257: to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
258: #ifdef MYSQLND_COMPRESSION_ENABLED
259: if (net->compressed == TRUE) {
260: /* here we need to compress the data and then write it, first comes the compressed header */
261: size_t tmp_complen = to_be_sent;
262: size_t payload_size;
263: zend_uchar * uncompressed_payload = p; /* should include the header */
264:
265: STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
266: int3store(uncompressed_payload, to_be_sent);
267: int1store(uncompressed_payload + 3, net->packet_no);
268: if (PASS == net->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
269: uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC))
270: {
271: int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
272: payload_size = tmp_complen;
273: } else {
274: int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
275: memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
276: payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
277: }
278: RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
279:
280: int3store(compress_buf, payload_size);
281: int1store(compress_buf + 3, net->packet_no);
282: DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
283: ret = conn->net->m.network_write(conn, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE TSRMLS_CC);
284: net->compressed_envelope_packet_no++;
285: #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
286: if (res == Z_OK) {
287: size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
288: zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
289: int error = net->m.decode(decompressed_data, decompressed_size,
290: compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
291: if (error == Z_OK) {
292: int i;
293: DBG_INF("success decompressing");
294: for (i = 0 ; i < decompressed_size; i++) {
295: if (i && (i % 30 == 0)) {
296: printf("\n\t\t");
297: }
298: printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
299: DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
300: }
301: } else {
302: DBG_INF("error decompressing");
303: }
304: mnd_free(decompressed_data);
305: }
306: #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
307: } else
308: #endif /* MYSQLND_COMPRESSION_ENABLED */
309: {
310: DBG_INF("no compression");
311: STORE_HEADER_SIZE(safe_storage, p);
312: int3store(p, to_be_sent);
313: int1store(p + 3, net->packet_no);
314: ret = conn->net->m.network_write(conn, p, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC);
315: RESTORE_HEADER_SIZE(p, safe_storage);
316: net->compressed_envelope_packet_no++;
317: }
318: net->packet_no++;
319:
320: p += to_be_sent;
321: left -= to_be_sent;
322: packets_sent++;
323: /*
324: if left is 0 then there is nothing more to send, but if the last packet was exactly
325: with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
326: empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
327: indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
328: packet will be sent and this loop will end.
329: */
330: } while (ret && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
331:
332: DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no);
333: /* Even for zero size payload we have to send a packet */
334: if (!ret) {
335: DBG_ERR_FMT("Can't %u send bytes", count);
336: conn->state = CONN_QUIT_SENT;
337: SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
338: }
339:
340: MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn->stats,
341: STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
342: STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
343: STAT_PACKETS_SENT, packets_sent);
344:
345: net->stream->chunk_size = old_chunk_size;
346: if (compress_buf) {
347: mnd_efree(compress_buf);
348: }
349: DBG_RETURN(ret);
350: }
351: /* }}} */
352:
353:
354: #ifdef MYSQLND_COMPRESSION_ENABLED
355: /* {{{ php_mysqlnd_read_buffer_is_empty */
356: static zend_bool
357: php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer)
358: {
359: return buffer->len? FALSE:TRUE;
360: }
361: /* }}} */
362:
363:
364: /* {{{ php_mysqlnd_read_buffer_read */
365: static void
366: php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest)
367: {
368: if (buffer->len >= count) {
369: memcpy(dest, buffer->data + buffer->offset, count);
370: buffer->offset += count;
371: buffer->len -= count;
372: }
373: }
374: /* }}} */
375:
376:
377: /* {{{ php_mysqlnd_read_buffer_bytes_left */
378: static size_t
379: php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer)
380: {
381: return buffer->len;
382: }
383: /* }}} */
384:
385:
386: /* {{{ php_mysqlnd_read_buffer_free */
387: static void
388: php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC)
389: {
390: DBG_ENTER("php_mysqlnd_read_buffer_free");
391: if (*buffer) {
392: mnd_efree((*buffer)->data);
393: mnd_efree(*buffer);
394: *buffer = NULL;
395: }
396: DBG_VOID_RETURN;
397: }
398: /* }}} */
399:
400:
401: /* {{{ php_mysqlnd_create_read_buffer */
402: static MYSQLND_READ_BUFFER *
403: mysqlnd_create_read_buffer(size_t count TSRMLS_DC)
404: {
405: MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER));
406: DBG_ENTER("mysqlnd_create_read_buffer");
407: ret->is_empty = php_mysqlnd_read_buffer_is_empty;
408: ret->read = php_mysqlnd_read_buffer_read;
409: ret->bytes_left = php_mysqlnd_read_buffer_bytes_left;
410: ret->free_buffer = php_mysqlnd_read_buffer_free;
411: ret->data = mnd_emalloc(count);
412: ret->size = ret->len = count;
413: ret->offset = 0;
414: DBG_RETURN(ret);
415: }
416: /* }}} */
417:
418:
419: /* {{{ mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer */
420: static enum_func_status
421: mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(MYSQLND * conn, size_t net_payload_size TSRMLS_DC)
422: {
423: MYSQLND_NET * net = conn->net;
424: size_t decompressed_size;
425: enum_func_status ret = PASS;
426: zend_uchar * compressed_data = NULL;
427: zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
428: DBG_ENTER("mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer");
429:
430: /* Read the compressed header */
431: if (FAIL == conn->net->m.network_read(conn, comp_header, COMPRESSED_HEADER_SIZE TSRMLS_CC)) {
432: DBG_RETURN(FAIL);
433: }
434: decompressed_size = uint3korr(comp_header);
435:
436: /* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
437: /* we need to decompress the data */
438:
439: if (decompressed_size) {
440: compressed_data = mnd_emalloc(net_payload_size);
441: if (FAIL == conn->net->m.network_read(conn, compressed_data, net_payload_size TSRMLS_CC)) {
442: ret = FAIL;
443: goto end;
444: }
445: net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size TSRMLS_CC);
446: ret = net->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size TSRMLS_CC);
447: if (ret == FAIL) {
448: goto end;
449: }
450: } else {
451: DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size);
452: net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size TSRMLS_CC);
453: if (FAIL == conn->net->m.network_read(conn, net->uncompressed_data->data, net_payload_size TSRMLS_CC)) {
454: ret = FAIL;
455: goto end;
456: }
457: }
458: end:
459: if (compressed_data) {
460: mnd_efree(compressed_data);
461: }
462: DBG_RETURN(ret);
463: }
464: /* }}} */
465: #endif /* MYSQLND_COMPRESSION_ENABLED */
466:
467:
468: /* {{{ mysqlnd_net::decode */
469: static enum_func_status
470: MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, size_t uncompressed_data_len,
471: const zend_uchar * const compressed_data, size_t compressed_data_len TSRMLS_DC)
472: {
473: #ifdef MYSQLND_COMPRESSION_ENABLED
474: int error;
475: uLongf tmp_complen = uncompressed_data_len;
476: DBG_ENTER("mysqlnd_net::decode");
477: error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
478:
479: DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len);
480: if (error != Z_OK) {
481: 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);
482: }
483: DBG_RETURN(error == Z_OK? PASS:FAIL);
484: #else
485: DBG_ENTER("mysqlnd_net::decode");
486: DBG_RETURN(FAIL);
487: #endif
488: }
489: /* }}} */
490:
491:
492: /* {{{ mysqlnd_net::encode */
493: static enum_func_status
494: MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
495: const zend_uchar * const uncompressed_data, size_t uncompressed_data_len TSRMLS_DC)
496: {
497: #ifdef MYSQLND_COMPRESSION_ENABLED
498: int error;
499: uLongf tmp_complen = *compress_buffer_len;
500: DBG_ENTER("mysqlnd_net::encode");
501: error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
502:
503: if (error != Z_OK) {
504: 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);
505: } else {
506: *compress_buffer_len = tmp_complen;
507: DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
508: }
509:
510: DBG_RETURN(error == Z_OK? PASS:FAIL);
511: #else
512: DBG_ENTER("mysqlnd_net::encode");
513: DBG_RETURN(FAIL);
514: #endif
515: }
516: /* }}} */
517:
518:
519: /* {{{ mysqlnd_net::receive */
520: static enum_func_status
521: MYSQLND_METHOD(mysqlnd_net, receive)(MYSQLND * conn, zend_uchar * buffer, size_t count TSRMLS_DC)
522: {
523: size_t to_read = count;
524: zend_uchar * p = buffer;
525: MYSQLND_NET * net = conn->net;
526:
527: DBG_ENTER("mysqlnd_net::receive");
528: #ifdef MYSQLND_COMPRESSION_ENABLED
529: if (net->compressed) {
530: if (net->uncompressed_data) {
531: size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read);
532: DBG_INF_FMT("reading %u from uncompressed_data buffer", to_read_from_buffer);
533: if (to_read_from_buffer) {
534: net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
535: p += to_read_from_buffer;
536: to_read -= to_read_from_buffer;
537: }
538: DBG_INF_FMT("left %u to read", to_read);
539: if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) {
540: /* Everything was consumed. This should never happen here, but for security */
541: net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
542: }
543: }
544: if (to_read) {
545: zend_uchar net_header[MYSQLND_HEADER_SIZE];
546: size_t net_payload_size;
547: zend_uchar packet_no;
548:
549: if (FAIL == net->m.network_read(conn, net_header, MYSQLND_HEADER_SIZE TSRMLS_CC)) {
550: DBG_RETURN(FAIL);
551: }
552: net_payload_size = uint3korr(net_header);
553: packet_no = uint1korr(net_header + 3);
554: if (net->compressed_envelope_packet_no != packet_no) {
555: DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
556: net->compressed_envelope_packet_no, packet_no, net_payload_size);
557:
558: php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC,
559: net->compressed_envelope_packet_no, packet_no, net_payload_size);
560: DBG_RETURN(FAIL);
561: }
562: net->compressed_envelope_packet_no++;
563: #ifdef MYSQLND_DUMP_HEADER_N_BODY
564: DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (unsigned long) net_payload_size);
565: #endif
566: /* Now let's read from the wire, decompress it and fill the read buffer */
567: mysqlnd_read_compressed_packet_from_stream_and_fill_read_buffer(conn, net_payload_size TSRMLS_CC);
568:
569: /*
570: Now a bit of recursion - read from the read buffer,
571: if the data which we have just read from the wire
572: is not enough, then the recursive call will try to
573: satisfy it until it is satisfied.
574: */
575: DBG_RETURN(net->m.receive(conn, p, to_read TSRMLS_CC));
576: }
577: DBG_RETURN(PASS);
578: }
579: #endif /* MYSQLND_COMPRESSION_ENABLED */
580: DBG_RETURN(net->m.network_read(conn, p, to_read TSRMLS_CC));
581: }
582: /* }}} */
583:
584:
585: /* {{{ mysqlnd_net::set_client_option */
586: static enum_func_status
587: MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value TSRMLS_DC)
588: {
589: DBG_ENTER("mysqlnd_net::set_client_option");
590: DBG_INF_FMT("option=%u", option);
591: switch (option) {
592: case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
593: DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
594: if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
595: DBG_RETURN(FAIL);
596: }
597: net->cmd_buffer.length = *(unsigned int*) value;
598: DBG_INF_FMT("new_length=%u", net->cmd_buffer.length);
599: if (!net->cmd_buffer.buffer) {
600: net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent);
601: } else {
602: net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent);
603: }
604: break;
605: case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
606: DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
607: net->options.net_read_buffer_size = *(unsigned int*) value;
608: DBG_INF_FMT("new_length=%u", net->options.net_read_buffer_size);
609: break;
610: case MYSQL_OPT_CONNECT_TIMEOUT:
611: DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
612: net->options.timeout_connect = *(unsigned int*) value;
613: break;
614: case MYSQLND_OPT_SSL_KEY:
615: {
616: zend_bool pers = net->persistent;
617: if (net->options.ssl_key) {
618: mnd_pefree(net->options.ssl_key, pers);
619: }
620: net->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
621: break;
622: }
623: case MYSQLND_OPT_SSL_CERT:
624: {
625: zend_bool pers = net->persistent;
626: if (net->options.ssl_cert) {
627: mnd_pefree(net->options.ssl_cert, pers);
628: }
629: net->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
630: break;
631: }
632: case MYSQLND_OPT_SSL_CA:
633: {
634: zend_bool pers = net->persistent;
635: if (net->options.ssl_ca) {
636: mnd_pefree(net->options.ssl_ca, pers);
637: }
638: net->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
639: break;
640: }
641: case MYSQLND_OPT_SSL_CAPATH:
642: {
643: zend_bool pers = net->persistent;
644: if (net->options.ssl_capath) {
645: mnd_pefree(net->options.ssl_capath, pers);
646: }
647: net->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
648: break;
649: }
650: case MYSQLND_OPT_SSL_CIPHER:
651: {
652: zend_bool pers = net->persistent;
653: if (net->options.ssl_cipher) {
654: mnd_pefree(net->options.ssl_cipher, pers);
655: }
656: net->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
657: break;
658: }
659: case MYSQLND_OPT_SSL_PASSPHRASE:
660: {
661: zend_bool pers = net->persistent;
662: if (net->options.ssl_passphrase) {
663: mnd_pefree(net->options.ssl_passphrase, pers);
664: }
665: net->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
666: break;
667: }
668: case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
669: net->options.ssl_verify_peer = value? ((*(zend_bool *)value)? TRUE:FALSE): FALSE;
670: break;
671: #ifdef WHEN_SUPPORTED_BY_MYSQLI
672: case MYSQL_OPT_READ_TIMEOUT:
673: DBG_INF("MYSQL_OPT_READ_TIMEOUT");
674: net->options.timeout_read = *(unsigned int*) value;
675: break;
676: case MYSQL_OPT_WRITE_TIMEOUT:
677: DBG_INF("MYSQL_OPT_WRITE_TIMEOUT");
678: net->options.timeout_write = *(unsigned int*) value;
679: break;
680: #endif
681: case MYSQL_OPT_COMPRESS:
682: net->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION;
683: break;
684: default:
685: DBG_RETURN(FAIL);
686: }
687: DBG_RETURN(PASS);
688: }
689: /* }}} */
690:
691: /* {{{ mysqlnd_net::consume_uneaten_data */
692: size_t
693: MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC)
694: {
695: #ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
696: /*
697: Switch to non-blocking mode and try to consume something from
698: the line, if possible, then continue. This saves us from looking for
699: the actuall place where out-of-order packets have been sent.
700: If someone is completely sure that everything is fine, he can switch it
701: off.
702: */
703: char tmp_buf[256];
704: size_t skipped_bytes = 0;
705: int opt = PHP_STREAM_OPTION_BLOCKING;
706: int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC);
707:
708: DBG_ENTER("mysqlnd_net::consume_uneaten_data");
709:
710: if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
711: /* Do a read of 1 byte */
712: int bytes_consumed;
713:
714: do {
715: skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf)));
716: } while (bytes_consumed == sizeof(tmp_buf));
717:
718: if (was_blocked) {
719: net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC);
720: }
721:
722: if (bytes_consumed) {
723: DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server",
724: bytes_consumed, mysqlnd_command_to_text[net->last_command]);
725: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
726: "consumed all the output from the server",
727: bytes_consumed, mysqlnd_command_to_text[net->last_command]);
728: }
729: }
730: net->last_command = cmd;
731:
732: DBG_RETURN(skipped_bytes);
733: #else
734: return 0;
735: #endif
736: }
737: /* }}} */
738:
739: /*
740: in libmyusql, if cert and !key then key=cert
741: */
742: /* {{{ mysqlnd_net::enable_ssl */
743: static enum_func_status
744: MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
745: {
746: #ifdef MYSQLND_SSL_SUPPORTED
747: php_stream_context *context = php_stream_context_alloc();
748: DBG_ENTER("mysqlnd_net::enable_ssl");
749: if (!context) {
750: DBG_RETURN(FAIL);
751: }
752:
753: if (net->options.ssl_key) {
754: zval key_zval;
755: ZVAL_STRING(&key_zval, net->options.ssl_key, 0);
756: DBG_INF("key");
757: php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
758: }
759: if (net->options.ssl_verify_peer) {
760: zval verify_peer_zval;
761: ZVAL_TRUE(&verify_peer_zval);
762: DBG_INF("verify peer");
763: php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
764: }
765: if (net->options.ssl_cert) {
766: zval cert_zval;
767: ZVAL_STRING(&cert_zval, net->options.ssl_cert, 0);
768: DBG_INF_FMT("local_cert=%s", net->options.ssl_cert);
769: php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
770: if (!net->options.ssl_key) {
771: php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
772: }
773: }
774: if (net->options.ssl_ca) {
775: zval cafile_zval;
776: ZVAL_STRING(&cafile_zval, net->options.ssl_ca, 0);
777: DBG_INF_FMT("cafile=%s", net->options.ssl_ca);
778: php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
779: }
780: if (net->options.ssl_capath) {
781: zval capath_zval;
782: ZVAL_STRING(&capath_zval, net->options.ssl_capath, 0);
783: DBG_INF_FMT("capath=%s", net->options.ssl_capath);
784: php_stream_context_set_option(context, "ssl", "cafile", &capath_zval);
785: }
786: if (net->options.ssl_passphrase) {
787: zval passphrase_zval;
788: ZVAL_STRING(&passphrase_zval, net->options.ssl_passphrase, 0);
789: php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
790: }
791: if (net->options.ssl_cipher) {
792: zval cipher_zval;
793: ZVAL_STRING(&cipher_zval, net->options.ssl_cipher, 0);
794: DBG_INF_FMT("ciphers=%s", net->options.ssl_cipher);
795: php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
796: }
797: php_stream_context_set(net->stream, context);
798: if (php_stream_xport_crypto_setup(net->stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL TSRMLS_CC) < 0 ||
799: php_stream_xport_crypto_enable(net->stream, 1 TSRMLS_CC) < 0)
800: {
801: DBG_ERR("Cannot connect to MySQL by using SSL");
802: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot connect to MySQL by using SSL");
803: DBG_RETURN(FAIL);
804: }
805: /*
806: get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
807: then the context would not survive cleaning of EG(regular_list), where it is registered, as a
808: resource. What happens is that after this destruction any use of the network will mean usage
809: of the context, which means usage of already freed memory, bad. Actually we don't need this
810: context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
811: */
812: php_stream_context_set(net->stream, NULL);
813:
814: if (net->options.timeout_read) {
815: struct timeval tv;
816: DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read);
817: tv.tv_sec = net->options.timeout_read;
818: tv.tv_usec = 0;
819: php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
820: }
821:
822: DBG_RETURN(PASS);
823: #else
824: DBG_ENTER("mysqlnd_net::enable_ssl");
825: DBG_RETURN(PASS);
826: #endif
827: }
828: /* }}} */
829:
830:
831: /* {{{ mysqlnd_net::disable_ssl */
832: static enum_func_status
833: MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net TSRMLS_DC)
834: {
835: DBG_ENTER("mysqlnd_net::disable_ssl");
836: DBG_RETURN(PASS);
837: }
838: /* }}} */
839:
840:
841: /* {{{ mysqlnd_net::set_client_option */
842: static void
843: MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net TSRMLS_DC)
844: {
845: zend_bool pers = net->persistent;
846: DBG_ENTER("mysqlnd_net::free_contents");
847:
848: #ifdef MYSQLND_COMPRESSION_ENABLED
849: if (net->uncompressed_data) {
850: net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC);
851: }
852: #endif
853: if (net->options.ssl_key) {
854: mnd_pefree(net->options.ssl_key, pers);
855: net->options.ssl_key = NULL;
856: }
857: if (net->options.ssl_cert) {
858: mnd_pefree(net->options.ssl_cert, pers);
859: net->options.ssl_cert = NULL;
860: }
861: if (net->options.ssl_ca) {
862: mnd_pefree(net->options.ssl_ca, pers);
863: net->options.ssl_ca = NULL;
864: }
865: if (net->options.ssl_capath) {
866: mnd_pefree(net->options.ssl_capath, pers);
867: net->options.ssl_capath = NULL;
868: }
869: if (net->options.ssl_cipher) {
870: mnd_pefree(net->options.ssl_cipher, pers);
871: net->options.ssl_cipher = NULL;
872: }
873:
874: DBG_VOID_RETURN;
875: }
876: /* }}} */
877:
878: static
879: MYSQLND_CLASS_METHODS_START(mysqlnd_net)
880: MYSQLND_METHOD(mysqlnd_net, connect),
881: MYSQLND_METHOD(mysqlnd_net, send),
882: MYSQLND_METHOD(mysqlnd_net, receive),
883: MYSQLND_METHOD(mysqlnd_net, set_client_option),
884: MYSQLND_METHOD(mysqlnd_net, network_read),
885: MYSQLND_METHOD(mysqlnd_net, network_write),
886: MYSQLND_METHOD(mysqlnd_net, decode),
887: MYSQLND_METHOD(mysqlnd_net, encode),
888: MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data),
889: MYSQLND_METHOD(mysqlnd_net, free_contents),
890: MYSQLND_METHOD(mysqlnd_net, enable_ssl),
891: MYSQLND_METHOD(mysqlnd_net, disable_ssl)
892: MYSQLND_CLASS_METHODS_END;
893:
894:
895: /* {{{ mysqlnd_net_init */
896: PHPAPI MYSQLND_NET *
897: mysqlnd_net_init(zend_bool persistent TSRMLS_DC)
898: {
899: size_t alloc_size = sizeof(MYSQLND_NET) + mysqlnd_plugin_count() * sizeof(void *);
900: MYSQLND_NET * net = mnd_pecalloc(1, alloc_size, persistent);
901:
902: DBG_ENTER("mysqlnd_net_init");
903: DBG_INF_FMT("persistent=%u", persistent);
904: if (net) {
905: net->persistent = persistent;
906: net->m = mysqlnd_mysqlnd_net_methods;
907:
908: {
909: unsigned int buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
910: net->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size TSRMLS_CC);
911: }
912: }
913: DBG_RETURN(net);
914: }
915: /* }}} */
916:
917:
918: /* {{{ mysqlnd_net_free */
919: PHPAPI void
920: mysqlnd_net_free(MYSQLND_NET * const net TSRMLS_DC)
921: {
922: DBG_ENTER("mysqlnd_net_free");
923:
924: if (net) {
925: zend_bool pers = net->persistent;
926:
927: net->m.free_contents(net TSRMLS_CC);
928: if (net->cmd_buffer.buffer) {
929: DBG_INF("Freeing cmd buffer");
930: mnd_pefree(net->cmd_buffer.buffer, pers);
931: net->cmd_buffer.buffer = NULL;
932: }
933: if (net->stream) {
934: DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract);
935: if (pers) {
936: php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
937: } else {
938: php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE);
939: }
940: net->stream = NULL;
941: }
942: mnd_pefree(net, pers);
943: }
944: DBG_VOID_RETURN;
945: }
946: /* }}} */
947:
948:
949: /* {{{ _mysqlnd_plugin_get_plugin_net_data */
950: PHPAPI void ** _mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net, unsigned int plugin_id TSRMLS_DC)
951: {
952: DBG_ENTER("_mysqlnd_plugin_get_plugin_net_data");
953: DBG_INF_FMT("plugin_id=%u", plugin_id);
954: if (!net || plugin_id >= mysqlnd_plugin_count()) {
955: return NULL;
956: }
957: DBG_RETURN((void *)((char *)net + sizeof(MYSQLND_NET) + plugin_id * sizeof(void *)));
958: }
959: /* }}} */
960:
961:
962: /* {{{ mysqlnd_net_get_methods */
963: PHPAPI struct st_mysqlnd_net_methods *
964: mysqlnd_net_get_methods()
965: {
966: return &mysqlnd_mysqlnd_net_methods;
967: }
968: /* }}} */
969:
970:
971: /*
972: * Local variables:
973: * tab-width: 4
974: * c-basic-offset: 4
975: * End:
976: * vim600: noet sw=4 ts=4 fdm=marker
977: * vim<600: noet sw=4 ts=4
978: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>