1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 7                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 2006-2014 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: Andrey Hristov <andrey@mysql.com>                           |
16  |          Ulf Wendel <uwendel@mysql.com>                              |
17  |          Georg Richter <georg@mysql.com>                             |
18  +----------------------------------------------------------------------+
19*/
20
21/* $Id$ */
22#include "php.h"
23#include "mysqlnd.h"
24#include "mysqlnd_wireprotocol.h"
25#include "mysqlnd_priv.h"
26#include "mysqlnd_result.h"
27#include "mysqlnd_statistics.h"
28#include "mysqlnd_charset.h"
29#include "mysqlnd_debug.h"
30#include "zend_smart_str.h"
31
32/*
33  TODO :
34  - Don't bind so tightly the metadata with the result set. This means
35    that the metadata reading should not expect a MYSQLND_RES pointer, it
36    does not need it, but return a pointer to the metadata (MYSQLND_FIELD *).
37    For normal statements we will then just assign it to a member of
38    MYSQLND_RES. For PS statements, it will stay as part of the statement
39    (MYSQLND_STMT) between prepare and execute. At execute the new metadata
40    will be sent by the server, so we will discard the old one and then
41    finally attach it to the result set. This will make the code more clean,
42    as a prepared statement won't have anymore stmt->result != NULL, as it
43    is now, just to have where to store the metadata.
44
45  - Change mysqlnd_simple_command to accept a heap dynamic array of MYSQLND_STRING
46    terminated by a string with ptr being NULL. Thus, multi-part messages can be
47    sent to the network like writev() and this can save at least for
48    mysqlnd_stmt_send_long_data() new malloc. This change will probably make the
49    code in few other places cleaner.
50*/
51
52extern MYSQLND_CHARSET *mysqlnd_charsets;
53
54
55
56PHPAPI const char * const mysqlnd_old_passwd  = "mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication. "
57"Please use an administration tool to reset your password with the command SET PASSWORD = PASSWORD('your_existing_password'). This will "
58"store a new, and more secure, hash value in mysql.user. If this user is used in other scripts executed by PHP 5.2 or earlier you might need to remove the old-passwords "
59"flag from your my.cnf file";
60
61PHPAPI const char * const mysqlnd_server_gone = "MySQL server has gone away";
62PHPAPI const char * const mysqlnd_out_of_sync = "Commands out of sync; you can't run this command now";
63PHPAPI const char * const mysqlnd_out_of_memory = "Out of memory";
64
65PHPAPI MYSQLND_STATS *mysqlnd_global_stats = NULL;
66
67
68/* {{{ mysqlnd_conn_data::free_options */
69static void
70MYSQLND_METHOD(mysqlnd_conn_data, free_options)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
71{
72    zend_bool pers = conn->persistent;
73
74    if (conn->options->charset_name) {
75        mnd_pefree(conn->options->charset_name, pers);
76        conn->options->charset_name = NULL;
77    }
78    if (conn->options->auth_protocol) {
79        mnd_pefree(conn->options->auth_protocol, pers);
80        conn->options->auth_protocol = NULL;
81    }
82    if (conn->options->num_commands) {
83        unsigned int i;
84        for (i = 0; i < conn->options->num_commands; i++) {
85            /* allocated with pestrdup */
86            mnd_pefree(conn->options->init_commands[i], pers);
87        }
88        mnd_pefree(conn->options->init_commands, pers);
89        conn->options->init_commands = NULL;
90    }
91    if (conn->options->cfg_file) {
92        mnd_pefree(conn->options->cfg_file, pers);
93        conn->options->cfg_file = NULL;
94    }
95    if (conn->options->cfg_section) {
96        mnd_pefree(conn->options->cfg_section, pers);
97        conn->options->cfg_section = NULL;
98    }
99    if (conn->options->connect_attr) {
100        zend_hash_destroy(conn->options->connect_attr);
101        mnd_pefree(conn->options->connect_attr, pers);
102        conn->options->connect_attr = NULL;
103    }
104}
105/* }}} */
106
107
108/* {{{ mysqlnd_conn_data::free_contents */
109static void
110MYSQLND_METHOD(mysqlnd_conn_data, free_contents)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
111{
112    zend_bool pers = conn->persistent;
113
114    DBG_ENTER("mysqlnd_conn_data::free_contents");
115
116    if (conn->current_result) {
117        conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC);
118        conn->current_result = NULL;
119    }
120
121    if (conn->net) {
122        conn->net->data->m.free_contents(conn->net TSRMLS_CC);
123    }
124
125    DBG_INF("Freeing memory of members");
126
127    if (conn->host) {
128        mnd_pefree(conn->host, pers);
129        conn->host = NULL;
130    }
131    if (conn->user) {
132        mnd_pefree(conn->user, pers);
133        conn->user = NULL;
134    }
135    if (conn->passwd) {
136        mnd_pefree(conn->passwd, pers);
137        conn->passwd = NULL;
138    }
139    if (conn->connect_or_select_db) {
140        mnd_pefree(conn->connect_or_select_db, pers);
141        conn->connect_or_select_db = NULL;
142    }
143    if (conn->unix_socket) {
144        mnd_pefree(conn->unix_socket, pers);
145        conn->unix_socket = NULL;
146    }
147    DBG_INF_FMT("scheme=%s", conn->scheme);
148    if (conn->scheme) {
149        mnd_pefree(conn->scheme, pers);
150        conn->scheme = NULL;
151    }
152    if (conn->server_version) {
153        mnd_pefree(conn->server_version, pers);
154        conn->server_version = NULL;
155    }
156    if (conn->host_info) {
157        mnd_pefree(conn->host_info, pers);
158        conn->host_info = NULL;
159    }
160    if (conn->auth_plugin_data) {
161        mnd_pefree(conn->auth_plugin_data, pers);
162        conn->auth_plugin_data = NULL;
163    }
164    if (conn->last_message) {
165        mnd_pefree(conn->last_message, pers);
166        conn->last_message = NULL;
167    }
168    if (conn->error_info->error_list) {
169        zend_llist_clean(conn->error_info->error_list);
170        mnd_pefree(conn->error_info->error_list, pers);
171        conn->error_info->error_list = NULL;
172    }
173    conn->charset = NULL;
174    conn->greet_charset = NULL;
175
176    DBG_VOID_RETURN;
177}
178/* }}} */
179
180
181/* {{{ mysqlnd_conn_data::dtor */
182static void
183MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, dtor)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
184{
185    DBG_ENTER("mysqlnd_conn_data::dtor");
186    DBG_INF_FMT("conn=%llu", conn->thread_id);
187
188    conn->m->free_contents(conn TSRMLS_CC);
189    conn->m->free_options(conn TSRMLS_CC);
190
191    if (conn->net) {
192        mysqlnd_net_free(conn->net, conn->stats, conn->error_info TSRMLS_CC);
193        conn->net = NULL;
194    }
195
196    if (conn->protocol) {
197        mysqlnd_protocol_free(conn->protocol TSRMLS_CC);
198        conn->protocol = NULL;
199    }
200
201    if (conn->stats) {
202        mysqlnd_stats_end(conn->stats);
203    }
204
205    mnd_pefree(conn, conn->persistent);
206
207    DBG_VOID_RETURN;
208}
209/* }}} */
210
211
212/* {{{ mysqlnd_conn_data::simple_command_handle_response */
213static enum_func_status
214MYSQLND_METHOD(mysqlnd_conn_data, simple_command_handle_response)(MYSQLND_CONN_DATA * conn, enum mysqlnd_packet_type ok_packet,
215                                                             zend_bool silent, enum php_mysqlnd_server_command command,
216                                                             zend_bool ignore_upsert_status TSRMLS_DC)
217{
218    enum_func_status ret = FAIL;
219
220    DBG_ENTER("mysqlnd_conn_data::simple_command_handle_response");
221    DBG_INF_FMT("silent=%u packet=%u command=%s", silent, ok_packet, mysqlnd_command_to_text[command]);
222
223    switch (ok_packet) {
224        case PROT_OK_PACKET:{
225            MYSQLND_PACKET_OK * ok_response = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC);
226            if (!ok_response) {
227                SET_OOM_ERROR(*conn->error_info);
228                break;
229            }
230            if (FAIL == (ret = PACKET_READ(ok_response, conn))) {
231                if (!silent) {
232                    DBG_ERR_FMT("Error while reading %s's OK packet", mysqlnd_command_to_text[command]);
233                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's OK packet. PID=%u",
234                                     mysqlnd_command_to_text[command], getpid());
235                }
236            } else {
237                DBG_INF_FMT("OK from server");
238                if (0xFF == ok_response->field_count) {
239                    /* The server signalled error. Set the error */
240                    SET_CLIENT_ERROR(*conn->error_info, ok_response->error_no, ok_response->sqlstate, ok_response->error);
241                    ret = FAIL;
242                    /*
243                      Cover a protocol design error: error packet does not
244                      contain the server status. Therefore, the client has no way
245                      to find out whether there are more result sets of
246                      a multiple-result-set statement pending. Luckily, in 5.0 an
247                      error always aborts execution of a statement, wherever it is
248                      a multi-statement or a stored procedure, so it should be
249                      safe to unconditionally turn off the flag here.
250                    */
251                    conn->upsert_status->server_status &= ~SERVER_MORE_RESULTS_EXISTS;
252                    SET_ERROR_AFF_ROWS(conn);
253                } else {
254                    SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
255                                    ok_response->message, ok_response->message_len,
256                                    conn->persistent);
257
258                    if (!ignore_upsert_status) {
259                        memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
260                        conn->upsert_status->warning_count = ok_response->warning_count;
261                        conn->upsert_status->server_status = ok_response->server_status;
262                        conn->upsert_status->affected_rows = ok_response->affected_rows;
263                        conn->upsert_status->last_insert_id = ok_response->last_insert_id;
264                    }
265                }
266            }
267            PACKET_FREE(ok_response);
268            break;
269        }
270        case PROT_EOF_PACKET:{
271            MYSQLND_PACKET_EOF * ok_response = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC);
272            if (!ok_response) {
273                SET_OOM_ERROR(*conn->error_info);
274                break;
275            }
276            if (FAIL == (ret = PACKET_READ(ok_response, conn))) {
277                SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE,
278                                 "Malformed packet");
279                if (!silent) {
280                    DBG_ERR_FMT("Error while reading %s's EOF packet", mysqlnd_command_to_text[command]);
281                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's EOF packet. PID=%d",
282                                     mysqlnd_command_to_text[command], getpid());
283                }
284            } else if (0xFF == ok_response->field_count) {
285                /* The server signalled error. Set the error */
286                SET_CLIENT_ERROR(*conn->error_info, ok_response->error_no, ok_response->sqlstate, ok_response->error);
287                SET_ERROR_AFF_ROWS(conn);
288            } else if (0xFE != ok_response->field_count) {
289                SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "Malformed packet");
290                if (!silent) {
291                    DBG_ERR_FMT("EOF packet expected, field count wasn't 0xFE but 0x%2X", ok_response->field_count);
292                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "EOF packet expected, field count wasn't 0xFE but 0x%2X",
293                                    ok_response->field_count);
294                }
295            } else {
296                DBG_INF_FMT("OK from server");
297            }
298            PACKET_FREE(ok_response);
299            break;
300        }
301        default:
302            SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "Malformed packet");
303            php_error_docref(NULL TSRMLS_CC, E_ERROR, "Wrong response packet %u passed to the function", ok_packet);
304            break;
305    }
306    DBG_INF(ret == PASS ? "PASS":"FAIL");
307    DBG_RETURN(ret);
308}
309/* }}} */
310
311
312/* {{{ mysqlnd_conn_data::simple_command_send_request */
313static enum_func_status
314MYSQLND_METHOD(mysqlnd_conn_data, simple_command_send_request)(MYSQLND_CONN_DATA * conn, enum php_mysqlnd_server_command command,
315               const zend_uchar * const arg, size_t arg_len, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC)
316{
317    enum_func_status ret = PASS;
318    MYSQLND_PACKET_COMMAND * cmd_packet;
319
320    DBG_ENTER("mysqlnd_conn_data::simple_command_send_request");
321    DBG_INF_FMT("command=%s silent=%u", mysqlnd_command_to_text[command], silent);
322    DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
323    DBG_INF_FMT("sending %u bytes", arg_len + 1); /* + 1 is for the command */
324
325    switch (CONN_GET_STATE(conn)) {
326        case CONN_READY:
327            break;
328        case CONN_QUIT_SENT:
329            SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
330            DBG_ERR("Server is gone");
331            DBG_RETURN(FAIL);
332        default:
333            SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
334            DBG_ERR_FMT("Command out of sync. State=%u", CONN_GET_STATE(conn));
335            DBG_RETURN(FAIL);
336    }
337
338    SET_ERROR_AFF_ROWS(conn);
339    SET_EMPTY_ERROR(*conn->error_info);
340
341    cmd_packet = conn->protocol->m.get_command_packet(conn->protocol, FALSE TSRMLS_CC);
342    if (!cmd_packet) {
343        SET_OOM_ERROR(*conn->error_info);
344        DBG_RETURN(FAIL);
345    }
346
347    cmd_packet->command = command;
348    if (arg && arg_len) {
349        cmd_packet->argument = arg;
350        cmd_packet->arg_len  = arg_len;
351    }
352
353    MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_COM_QUIT + command - 1 /* because of COM_SLEEP */ );
354
355    if (! PACKET_WRITE(cmd_packet, conn)) {
356        if (!silent) {
357            DBG_ERR_FMT("Error while sending %s packet", mysqlnd_command_to_text[command]);
358            php_error(E_WARNING, "Error while sending %s packet. PID=%d", mysqlnd_command_to_text[command], getpid());
359        }
360        CONN_SET_STATE(conn, CONN_QUIT_SENT);
361        conn->m->send_close(conn TSRMLS_CC);
362        DBG_ERR("Server is gone");
363        ret = FAIL;
364    }
365    PACKET_FREE(cmd_packet);
366    DBG_RETURN(ret);
367}
368/* }}} */
369
370
371/* {{{ mysqlnd_conn_data::simple_command */
372static enum_func_status
373MYSQLND_METHOD(mysqlnd_conn_data, simple_command)(MYSQLND_CONN_DATA * conn, enum php_mysqlnd_server_command command,
374               const zend_uchar * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent,
375               zend_bool ignore_upsert_status TSRMLS_DC)
376{
377    enum_func_status ret;
378    DBG_ENTER("mysqlnd_conn_data::simple_command");
379
380    ret = conn->m->simple_command_send_request(conn, command, arg, arg_len, silent, ignore_upsert_status TSRMLS_CC);
381    if (PASS == ret && ok_packet != PROT_LAST) {
382        ret = conn->m->simple_command_handle_response(conn, ok_packet, silent, command, ignore_upsert_status TSRMLS_CC);
383    }
384
385    DBG_INF(ret == PASS ? "PASS":"FAIL");
386    DBG_RETURN(ret);
387}
388/* }}} */
389
390
391/* {{{ mysqlnd_conn_data::set_server_option */
392static enum_func_status
393MYSQLND_METHOD(mysqlnd_conn_data, set_server_option)(MYSQLND_CONN_DATA * const conn, enum_mysqlnd_server_option option TSRMLS_DC)
394{
395    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_server_option);
396    zend_uchar buffer[2];
397    enum_func_status ret = FAIL;
398    DBG_ENTER("mysqlnd_conn_data::set_server_option");
399    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
400
401        int2store(buffer, (unsigned int) option);
402        ret = conn->m->simple_command(conn, COM_SET_OPTION, buffer, sizeof(buffer), PROT_EOF_PACKET, FALSE, TRUE TSRMLS_CC);
403
404        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
405    }
406    DBG_RETURN(ret);
407}
408/* }}} */
409
410
411/* {{{ mysqlnd_conn_data::restart_psession */
412static enum_func_status
413MYSQLND_METHOD(mysqlnd_conn_data, restart_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
414{
415    DBG_ENTER("mysqlnd_conn_data::restart_psession");
416    MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_REUSED);
417    /* Free here what should not be seen by the next script */
418    if (conn->last_message) {
419        mnd_pefree(conn->last_message, conn->persistent);
420        conn->last_message = NULL;
421    }
422    DBG_RETURN(PASS);
423}
424/* }}} */
425
426
427/* {{{ mysqlnd_conn_data::end_psession */
428static enum_func_status
429MYSQLND_METHOD(mysqlnd_conn_data, end_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
430{
431    DBG_ENTER("mysqlnd_conn_data::end_psession");
432    DBG_RETURN(PASS);
433}
434/* }}} */
435
436
437/* {{{ mysqlnd_switch_to_ssl_if_needed */
438static enum_func_status
439mysqlnd_switch_to_ssl_if_needed(
440            MYSQLND_CONN_DATA * conn,
441            const MYSQLND_PACKET_GREET * const greet_packet,
442            const MYSQLND_OPTIONS * const options,
443            zend_ulong mysql_flags
444            TSRMLS_DC
445        )
446{
447    enum_func_status ret = FAIL;
448    const MYSQLND_CHARSET * charset;
449    MYSQLND_PACKET_AUTH * auth_packet;
450    DBG_ENTER("mysqlnd_switch_to_ssl_if_needed");
451    DBG_INF_FMT("client_capability_flags=%lu", mysql_flags);
452    DBG_INF_FMT("CLIENT_LONG_PASSWORD=  %d", mysql_flags & CLIENT_LONG_PASSWORD? 1:0);
453    DBG_INF_FMT("CLIENT_FOUND_ROWS=     %d", mysql_flags & CLIENT_FOUND_ROWS? 1:0);
454    DBG_INF_FMT("CLIENT_LONG_FLAG=      %d", mysql_flags & CLIENT_LONG_FLAG? 1:0);
455    DBG_INF_FMT("CLIENT_NO_SCHEMA=      %d", mysql_flags & CLIENT_NO_SCHEMA? 1:0);
456    DBG_INF_FMT("CLIENT_COMPRESS=       %d", mysql_flags & CLIENT_COMPRESS? 1:0);
457    DBG_INF_FMT("CLIENT_ODBC=           %d", mysql_flags & CLIENT_ODBC? 1:0);
458    DBG_INF_FMT("CLIENT_LOCAL_FILES=    %d", mysql_flags & CLIENT_LOCAL_FILES? 1:0);
459    DBG_INF_FMT("CLIENT_IGNORE_SPACE=   %d", mysql_flags & CLIENT_IGNORE_SPACE? 1:0);
460    DBG_INF_FMT("CLIENT_PROTOCOL_41=    %d", mysql_flags & CLIENT_PROTOCOL_41? 1:0);
461    DBG_INF_FMT("CLIENT_INTERACTIVE=    %d", mysql_flags & CLIENT_INTERACTIVE? 1:0);
462    DBG_INF_FMT("CLIENT_SSL=            %d", mysql_flags & CLIENT_SSL? 1:0);
463    DBG_INF_FMT("CLIENT_IGNORE_SIGPIPE= %d", mysql_flags & CLIENT_IGNORE_SIGPIPE? 1:0);
464    DBG_INF_FMT("CLIENT_TRANSACTIONS=   %d", mysql_flags & CLIENT_TRANSACTIONS? 1:0);
465    DBG_INF_FMT("CLIENT_RESERVED=       %d", mysql_flags & CLIENT_RESERVED? 1:0);
466    DBG_INF_FMT("CLIENT_SECURE_CONNECTION=%d", mysql_flags & CLIENT_SECURE_CONNECTION? 1:0);
467    DBG_INF_FMT("CLIENT_MULTI_STATEMENTS=%d", mysql_flags & CLIENT_MULTI_STATEMENTS? 1:0);
468    DBG_INF_FMT("CLIENT_MULTI_RESULTS=  %d", mysql_flags & CLIENT_MULTI_RESULTS? 1:0);
469    DBG_INF_FMT("CLIENT_PS_MULTI_RESULTS=%d", mysql_flags & CLIENT_PS_MULTI_RESULTS? 1:0);
470    DBG_INF_FMT("CLIENT_CONNECT_ATTRS=  %d", mysql_flags & CLIENT_PLUGIN_AUTH? 1:0);
471    DBG_INF_FMT("CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA= %d", mysql_flags & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA? 1:0);
472    DBG_INF_FMT("CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS=   %d", mysql_flags & CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS? 1:0);
473    DBG_INF_FMT("CLIENT_SESSION_TRACK=      %d", mysql_flags & CLIENT_SESSION_TRACK? 1:0);
474    DBG_INF_FMT("CLIENT_SSL_VERIFY_SERVER_CERT= %d", mysql_flags & CLIENT_SSL_VERIFY_SERVER_CERT? 1:0);
475    DBG_INF_FMT("CLIENT_REMEMBER_OPTIONS=       %d", mysql_flags & CLIENT_REMEMBER_OPTIONS? 1:0);
476
477    auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC);
478    if (!auth_packet) {
479        SET_OOM_ERROR(*conn->error_info);
480        goto end;
481    }
482    auth_packet->client_flags = mysql_flags;
483    auth_packet->max_packet_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE;
484
485    if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) {
486        auth_packet->charset_no = charset->nr;
487    } else {
488        auth_packet->charset_no = greet_packet->charset_no;
489    }
490
491#ifdef MYSQLND_SSL_SUPPORTED
492    if ((greet_packet->server_capabilities & CLIENT_SSL) && (mysql_flags & CLIENT_SSL)) {
493        zend_bool verify = mysql_flags & CLIENT_SSL_VERIFY_SERVER_CERT? TRUE:FALSE;
494        DBG_INF("Switching to SSL");
495        if (!PACKET_WRITE(auth_packet, conn)) {
496            CONN_SET_STATE(conn, CONN_QUIT_SENT);
497            conn->m->send_close(conn TSRMLS_CC);
498            SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
499            goto end;
500        }
501
502        conn->net->data->m.set_client_option(conn->net, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (const char *) &verify TSRMLS_CC);
503
504        if (FAIL == conn->net->data->m.enable_ssl(conn->net TSRMLS_CC)) {
505            goto end;
506        }
507    }
508#endif
509    ret = PASS;
510end:
511    PACKET_FREE(auth_packet);
512    DBG_RETURN(ret);
513}
514/* }}} */
515
516
517/* {{{ mysqlnd_conn_data::fetch_auth_plugin_by_name */
518static struct st_mysqlnd_authentication_plugin *
519MYSQLND_METHOD(mysqlnd_conn_data, fetch_auth_plugin_by_name)(const char * const requested_protocol TSRMLS_DC)
520{
521    struct st_mysqlnd_authentication_plugin * auth_plugin;
522    char * plugin_name = NULL;
523    DBG_ENTER("mysqlnd_conn_data::fetch_auth_plugin_by_name");
524
525    mnd_sprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol);
526    DBG_INF_FMT("looking for %s auth plugin", plugin_name);
527    auth_plugin = mysqlnd_plugin_find(plugin_name);
528    mnd_sprintf_free(plugin_name);
529
530    DBG_RETURN(auth_plugin);
531}
532/* }}} */
533
534
535/* {{{ mysqlnd_run_authentication */
536static enum_func_status
537mysqlnd_run_authentication(
538            MYSQLND_CONN_DATA * conn,
539            const char * const user,
540            const char * const passwd,
541            const size_t passwd_len,
542            const char * const db,
543            const size_t db_len,
544            const zend_uchar * const auth_plugin_data,
545            const size_t auth_plugin_data_len,
546            const char * const auth_protocol,
547            unsigned int charset_no,
548            const MYSQLND_OPTIONS * const options,
549            zend_ulong mysql_flags,
550            zend_bool silent,
551            zend_bool is_change_user
552            TSRMLS_DC)
553{
554    enum_func_status ret = FAIL;
555    zend_bool first_call = TRUE;
556
557    char * switch_to_auth_protocol = NULL;
558    size_t switch_to_auth_protocol_len = 0;
559    char * requested_protocol = NULL;
560    zend_uchar * plugin_data;
561    size_t plugin_data_len;
562
563    DBG_ENTER("mysqlnd_run_authentication");
564
565    plugin_data_len = auth_plugin_data_len;
566    plugin_data = mnd_emalloc(plugin_data_len + 1);
567    if (!plugin_data) {
568        goto end;
569    }
570    memcpy(plugin_data, auth_plugin_data, plugin_data_len);
571    plugin_data[plugin_data_len] = '\0';
572
573    requested_protocol = mnd_pestrdup(auth_protocol? auth_protocol : MYSQLND_DEFAULT_AUTH_PROTOCOL, FALSE);
574    if (!requested_protocol) {
575        goto end;
576    }
577
578    do {
579        struct st_mysqlnd_authentication_plugin * auth_plugin = conn->m->fetch_auth_plugin_by_name(requested_protocol TSRMLS_CC);
580
581        if (!auth_plugin) {
582            php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol);
583            SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method unknown to the client");
584            goto end;
585        }
586        DBG_INF("plugin found");
587
588        {
589            zend_uchar * switch_to_auth_protocol_data = NULL;
590            size_t switch_to_auth_protocol_data_len = 0;
591            zend_uchar * scrambled_data = NULL;
592            size_t scrambled_data_len = 0;
593
594            switch_to_auth_protocol = NULL;
595            switch_to_auth_protocol_len = 0;
596
597            if (conn->auth_plugin_data) {
598                mnd_pefree(conn->auth_plugin_data, conn->persistent);
599                conn->auth_plugin_data = NULL;
600            }
601            conn->auth_plugin_data_len = plugin_data_len;
602            conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent);
603            if (!conn->auth_plugin_data) {
604                SET_OOM_ERROR(*conn->error_info);
605                goto end;
606            }
607            memcpy(conn->auth_plugin_data, plugin_data, plugin_data_len);
608
609            DBG_INF_FMT("salt(%d)=[%.*s]", plugin_data_len, plugin_data_len, plugin_data);
610            /* The data should be allocated with malloc() */
611            scrambled_data =
612                auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len,
613                                                   plugin_data, plugin_data_len, options, &conn->net->data->options, mysql_flags TSRMLS_CC);
614            if (conn->error_info->error_no) {
615                goto end;
616            }
617            if (FALSE == is_change_user) {
618                ret = mysqlnd_auth_handshake(conn, user, passwd, passwd_len, db, db_len, options, mysql_flags,
619                                            charset_no,
620                                            first_call,
621                                            requested_protocol,
622                                            scrambled_data, scrambled_data_len,
623                                            &switch_to_auth_protocol, &switch_to_auth_protocol_len,
624                                            &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
625                                            TSRMLS_CC);
626            } else {
627                ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, passwd_len, db, db_len, silent,
628                                               first_call,
629                                               requested_protocol,
630                                               scrambled_data, scrambled_data_len,
631                                               &switch_to_auth_protocol, &switch_to_auth_protocol_len,
632                                               &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
633                                               TSRMLS_CC);
634            }
635            first_call = FALSE;
636            free(scrambled_data);
637
638            DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a");
639            if (requested_protocol && switch_to_auth_protocol) {
640                mnd_efree(requested_protocol);
641                requested_protocol = switch_to_auth_protocol;
642            }
643
644            if (plugin_data) {
645                mnd_efree(plugin_data);
646            }
647            plugin_data_len = switch_to_auth_protocol_data_len;
648            plugin_data = switch_to_auth_protocol_data;
649        }
650        DBG_INF_FMT("conn->error_info->error_no = %d", conn->error_info->error_no);
651    } while (ret == FAIL && conn->error_info->error_no == 0 && switch_to_auth_protocol != NULL);
652
653    if (ret == PASS) {
654        DBG_INF_FMT("saving requested_protocol=%s", requested_protocol);
655        conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC);
656    }
657end:
658    if (plugin_data) {
659        mnd_efree(plugin_data);
660    }
661    if (requested_protocol) {
662        mnd_efree(requested_protocol);
663    }
664
665    DBG_RETURN(ret);
666}
667/* }}} */
668
669
670/* {{{ mysqlnd_connect_run_authentication */
671static enum_func_status
672mysqlnd_connect_run_authentication(
673            MYSQLND_CONN_DATA * conn,
674            const char * const user,
675            const char * const passwd,
676            const char * const db,
677            size_t db_len,
678            size_t passwd_len,
679            const MYSQLND_PACKET_GREET * const greet_packet,
680            const MYSQLND_OPTIONS * const options,
681            zend_ulong mysql_flags
682            TSRMLS_DC)
683{
684    enum_func_status ret = FAIL;
685    DBG_ENTER("mysqlnd_connect_run_authentication");
686
687    ret = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC);
688    if (PASS == ret) {
689        ret = mysqlnd_run_authentication(conn, user, passwd, passwd_len, db, db_len,
690                                         greet_packet->auth_plugin_data, greet_packet->auth_plugin_data_len, greet_packet->auth_protocol,
691                                         greet_packet->charset_no, options, mysql_flags, FALSE /*silent*/, FALSE/*is_change*/ TSRMLS_CC);
692    }
693    DBG_RETURN(ret);
694}
695/* }}} */
696
697
698/* {{{ mysqlnd_conn_data::execute_init_commands */
699static enum_func_status
700MYSQLND_METHOD(mysqlnd_conn_data, execute_init_commands)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
701{
702    enum_func_status ret = PASS;
703
704    DBG_ENTER("mysqlnd_conn_data::execute_init_commands");
705    if (conn->options->init_commands) {
706        unsigned int current_command = 0;
707        for (; current_command < conn->options->num_commands; ++current_command) {
708            const char * const command = conn->options->init_commands[current_command];
709            if (command) {
710                MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_INIT_COMMAND_EXECUTED_COUNT);
711                if (PASS != conn->m->query(conn, command, strlen(command) TSRMLS_CC)) {
712                    MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_INIT_COMMAND_FAILED_COUNT);
713                    ret = FAIL;
714                    break;
715                }
716                if (conn->last_query_type == QUERY_SELECT) {
717                    MYSQLND_RES * result = conn->m->use_result(conn, 0 TSRMLS_CC);
718                    if (result) {
719                        result->m.free_result(result, TRUE TSRMLS_CC);
720                    }
721                }
722            }
723        }
724    }
725    DBG_RETURN(ret);
726}
727/* }}} */
728
729
730/* {{{ mysqlnd_conn_data::get_updated_connect_flags */
731static unsigned int
732MYSQLND_METHOD(mysqlnd_conn_data, get_updated_connect_flags)(MYSQLND_CONN_DATA * conn, unsigned int mysql_flags TSRMLS_DC)
733{
734    MYSQLND_NET * net = conn->net;
735
736    DBG_ENTER("mysqlnd_conn_data::get_updated_connect_flags");
737    /* we allow load data local infile by default */
738    mysql_flags |= MYSQLND_CAPABILITIES;
739
740    mysql_flags |= conn->options->flags; /* use the flags from set_client_option() */
741
742    if (PG(open_basedir) && strlen(PG(open_basedir))) {
743        mysql_flags ^= CLIENT_LOCAL_FILES;
744    }
745
746#ifndef MYSQLND_COMPRESSION_ENABLED
747    if (mysql_flags & CLIENT_COMPRESS) {
748        mysql_flags &= ~CLIENT_COMPRESS;
749    }
750#else
751    if (net && net->data->options.flags & MYSQLND_NET_FLAG_USE_COMPRESSION) {
752        mysql_flags |= CLIENT_COMPRESS;
753    }
754#endif
755#ifndef MYSQLND_SSL_SUPPORTED
756    if (mysql_flags & CLIENT_SSL) {
757        mysql_flags &= ~CLIENT_SSL;
758    }
759#else
760    if (net && (net->data->options.ssl_key || net->data->options.ssl_cert ||
761        net->data->options.ssl_ca || net->data->options.ssl_capath || net->data->options.ssl_cipher))
762    {
763        mysql_flags |= CLIENT_SSL;
764    }
765#endif
766
767    DBG_RETURN(mysql_flags);
768}
769/* }}} */
770
771
772/* {{{ mysqlnd_conn_data::connect_handshake */
773static enum_func_status
774MYSQLND_METHOD(mysqlnd_conn_data, connect_handshake)(MYSQLND_CONN_DATA * conn,
775                        const char * const host, const char * const user,
776                        const char * const passwd, const unsigned int passwd_len,
777                        const char * const db, const unsigned int db_len,
778                        const unsigned int mysql_flags TSRMLS_DC)
779{
780    MYSQLND_PACKET_GREET * greet_packet;
781    MYSQLND_NET * net = conn->net;
782
783    DBG_ENTER("mysqlnd_conn_data::connect_handshake");
784
785    greet_packet = conn->protocol->m.get_greet_packet(conn->protocol, FALSE TSRMLS_CC);
786    if (!greet_packet) {
787        SET_OOM_ERROR(*conn->error_info);
788        DBG_RETURN(FAIL); /* OOM */
789    }
790
791    if (FAIL == net->data->m.connect_ex(conn->net, conn->scheme, conn->scheme_len, conn->persistent,
792                                        conn->stats, conn->error_info TSRMLS_CC))
793    {
794        goto err;
795    }
796
797    DBG_INF_FMT("stream=%p", net->data->m.get_stream(net TSRMLS_CC));
798
799    if (FAIL == PACKET_READ(greet_packet, conn)) {
800        DBG_ERR("Error while reading greeting packet");
801        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading greeting packet. PID=%d", getpid());
802        goto err;
803    } else if (greet_packet->error_no) {
804        DBG_ERR_FMT("errorno=%u error=%s", greet_packet->error_no, greet_packet->error);
805        SET_CLIENT_ERROR(*conn->error_info, greet_packet->error_no, greet_packet->sqlstate, greet_packet->error);
806        goto err;
807    } else if (greet_packet->pre41) {
808        DBG_ERR_FMT("Connecting to 3.22, 3.23 & 4.0 is not supported. Server is %-.32s", greet_packet->server_version);
809        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connecting to 3.22, 3.23 & 4.0 "
810                        " is not supported. Server is %-.32s", greet_packet->server_version);
811        SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE,
812                         "Connecting to 3.22, 3.23 & 4.0 servers is not supported");
813        goto err;
814    }
815
816    conn->thread_id         = greet_packet->thread_id;
817    conn->protocol_version  = greet_packet->protocol_version;
818    conn->server_version    = mnd_pestrdup(greet_packet->server_version, conn->persistent);
819
820    conn->greet_charset = mysqlnd_find_charset_nr(greet_packet->charset_no);
821    if (!conn->greet_charset) {
822        php_error_docref(NULL TSRMLS_CC, E_WARNING,
823            "Server sent charset (%d) unknown to the client. Please, report to the developers", greet_packet->charset_no);
824        SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE,
825            "Server sent charset unknown to the client. Please, report to the developers");
826        goto err;
827    }
828
829    conn->client_flag           = mysql_flags;
830    conn->server_capabilities   = greet_packet->server_capabilities;
831
832    if (FAIL == mysqlnd_connect_run_authentication(conn, user, passwd, db, db_len, (size_t) passwd_len,
833                                                   greet_packet, conn->options, mysql_flags TSRMLS_CC))
834    {
835        goto err;
836    }
837    memset(conn->upsert_status, 0, sizeof(*conn->upsert_status));
838    conn->upsert_status->warning_count = 0;
839    conn->upsert_status->server_status = greet_packet->server_status;
840    conn->upsert_status->affected_rows = 0;
841
842    PACKET_FREE(greet_packet);
843    DBG_RETURN(PASS);
844err:
845    conn->client_flag = 0;
846    conn->server_capabilities = 0;
847    PACKET_FREE(greet_packet);
848    DBG_RETURN(FAIL);
849}
850/* }}} */
851
852
853/* {{{ mysqlnd_conn_data::connect */
854static enum_func_status
855MYSQLND_METHOD(mysqlnd_conn_data, connect)(MYSQLND_CONN_DATA * conn,
856                         const char *host, const char *user,
857                         const char *passwd, unsigned int passwd_len,
858                         const char *db, unsigned int db_len,
859                         unsigned int port,
860                         const char *socket_or_pipe,
861                         unsigned int mysql_flags
862                         TSRMLS_DC)
863{
864    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, connect);
865    size_t host_len;
866    zend_bool unix_socket = FALSE;
867    zend_bool named_pipe = FALSE;
868    zend_bool reconnect = FALSE;
869    zend_bool saved_compression = FALSE;
870    zend_bool local_tx_started = FALSE;
871    MYSQLND_NET * net = conn->net;
872
873    DBG_ENTER("mysqlnd_conn_data::connect");
874    DBG_INF_FMT("conn=%p", conn);
875
876    if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
877        goto err;
878    }
879    local_tx_started = TRUE;
880
881    SET_EMPTY_ERROR(*conn->error_info);
882    SET_ERROR_AFF_ROWS(conn);
883
884    DBG_INF_FMT("host=%s user=%s db=%s port=%u flags=%u persistent=%u state=%u",
885                host?host:"", user?user:"", db?db:"", port, mysql_flags,
886                conn? conn->persistent:0, conn? CONN_GET_STATE(conn):-1);
887
888    if (CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) {
889        DBG_INF("Connecting on a connected handle.");
890
891        if (CONN_GET_STATE(conn) < CONN_QUIT_SENT) {
892            MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CLOSE_IMPLICIT);
893            reconnect = TRUE;
894            conn->m->send_close(conn TSRMLS_CC);
895        }
896
897        conn->m->free_contents(conn TSRMLS_CC);
898        MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_CONNECTIONS);
899        if (conn->persistent) {
900            MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS);
901        }
902        /* Now reconnect using the same handle */
903        if (net->data->compressed) {
904            /*
905              we need to save the state. As we will re-connect, net->compressed should be off, or
906              we will look for a compression header as part of the greet message, but there will
907              be none.
908            */
909            saved_compression = TRUE;
910            net->data->compressed = FALSE;
911        }
912        if (net->data->ssl) {
913            net->data->ssl = FALSE;
914        }
915    } else {
916        unsigned int max_allowed_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE;
917        conn->m->set_client_option(conn, MYSQLND_OPT_MAX_ALLOWED_PACKET, (char *)&max_allowed_size TSRMLS_CC);
918    }
919
920    if (!host || !host[0]) {
921        host = "localhost";
922    }
923    if (!user) {
924        DBG_INF_FMT("no user given, using empty string");
925        user = "";
926    }
927    if (!passwd) {
928        DBG_INF_FMT("no password given, using empty string");
929        passwd = "";
930        passwd_len = 0;
931    }
932    if (!db) {
933        DBG_INF_FMT("no db given, using empty string");
934        db = "";
935        db_len = 0;
936    } else {
937        mysql_flags |= CLIENT_CONNECT_WITH_DB;
938    }
939
940    host_len = strlen(host);
941    {
942        char * transport = NULL;
943        int transport_len;
944#ifndef PHP_WIN32
945        if (host_len == sizeof("localhost") - 1 && !strncasecmp(host, "localhost", host_len)) {
946            DBG_INF_FMT("socket=%s", socket_or_pipe? socket_or_pipe:"n/a");
947            if (!socket_or_pipe) {
948                socket_or_pipe = "/tmp/mysql.sock";
949            }
950            transport_len = mnd_sprintf(&transport, 0, "unix://%s", socket_or_pipe);
951            unix_socket = TRUE;
952#else
953        if (host_len == sizeof(".") - 1 && host[0] == '.') {
954            /* named pipe in socket */
955            if (!socket_or_pipe) {
956                socket_or_pipe = "\\\\.\\pipe\\MySQL";
957            }
958            transport_len = mnd_sprintf(&transport, 0, "pipe://%s", socket_or_pipe);
959            named_pipe = TRUE;
960#endif
961        } else {
962            if (!port) {
963                port = 3306;
964            }
965            transport_len = mnd_sprintf(&transport, 0, "tcp://%s:%u", host, port);
966        }
967        if (!transport) {
968            SET_OOM_ERROR(*conn->error_info);
969            goto err; /* OOM */
970        }
971        DBG_INF_FMT("transport=%s conn->scheme=%s", transport, conn->scheme);
972        conn->scheme = mnd_pestrndup(transport, transport_len, conn->persistent);
973        conn->scheme_len = transport_len;
974        mnd_sprintf_free(transport);
975        transport = NULL;
976        if (!conn->scheme) {
977            goto err; /* OOM */
978        }
979    }
980
981    mysql_flags = conn->m->get_updated_connect_flags(conn, mysql_flags TSRMLS_CC);
982
983    if (FAIL == conn->m->connect_handshake(conn, host, user, passwd, passwd_len, db, db_len, mysql_flags TSRMLS_CC)) {
984        goto err;
985    }
986
987    {
988        CONN_SET_STATE(conn, CONN_READY);
989
990        if (saved_compression) {
991            net->data->compressed = TRUE;
992        }
993        /*
994          If a connect on a existing handle is performed and mysql_flags is
995          passed which doesn't CLIENT_COMPRESS, then we need to overwrite the value
996          which we set based on saved_compression.
997        */
998        net->data->compressed = mysql_flags & CLIENT_COMPRESS? TRUE:FALSE;
999
1000        conn->user_len          = strlen(user);
1001        conn->user              = mnd_pestrndup(user, conn->user_len, conn->persistent);
1002        conn->passwd            = mnd_pestrndup(passwd, passwd_len, conn->persistent);
1003        conn->passwd_len        = passwd_len;
1004        conn->port              = port;
1005        conn->connect_or_select_db = mnd_pestrndup(db, db_len, conn->persistent);
1006        conn->connect_or_select_db_len = db_len;
1007
1008        if (!conn->user || !conn->passwd || !conn->connect_or_select_db) {
1009            SET_OOM_ERROR(*conn->error_info);
1010            goto err; /* OOM */
1011        }
1012
1013        if (!unix_socket && !named_pipe) {
1014            conn->host = mnd_pestrndup(host, host_len, conn->persistent);
1015            if (!conn->host) {
1016                SET_OOM_ERROR(*conn->error_info);
1017                goto err; /* OOM */
1018            }
1019            conn->host_len = host_len;
1020            {
1021                char *p;
1022                mnd_sprintf(&p, 0, "%s via TCP/IP", conn->host);
1023                if (!p) {
1024                    SET_OOM_ERROR(*conn->error_info);
1025                    goto err; /* OOM */
1026                }
1027                conn->host_info = mnd_pestrdup(p, conn->persistent);
1028                mnd_sprintf_free(p);
1029                if (!conn->host_info) {
1030                    SET_OOM_ERROR(*conn->error_info);
1031                    goto err; /* OOM */
1032                }
1033            }
1034        } else {
1035            conn->unix_socket = mnd_pestrdup(socket_or_pipe, conn->persistent);
1036            if (unix_socket) {
1037                conn->host_info = mnd_pestrdup("Localhost via UNIX socket", conn->persistent);
1038            } else if (named_pipe) {
1039                char *p;
1040                mnd_sprintf(&p, 0, "%s via named pipe", conn->unix_socket);
1041                if (!p) {
1042                    SET_OOM_ERROR(*conn->error_info);
1043                    goto err; /* OOM */
1044                }
1045                conn->host_info =  mnd_pestrdup(p, conn->persistent);
1046                mnd_sprintf_free(p);
1047                if (!conn->host_info) {
1048                    SET_OOM_ERROR(*conn->error_info);
1049                    goto err; /* OOM */
1050                }
1051            } else {
1052                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Impossible. Should be either socket or a pipe. Report a bug!");
1053            }
1054            if (!conn->unix_socket || !conn->host_info) {
1055                SET_OOM_ERROR(*conn->error_info);
1056                goto err; /* OOM */
1057            }
1058            conn->unix_socket_len = strlen(conn->unix_socket);
1059        }
1060        conn->max_packet_size   = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE;
1061        /* todo: check if charset is available */
1062
1063        SET_EMPTY_ERROR(*conn->error_info);
1064
1065        mysqlnd_local_infile_default(conn);
1066
1067        if (FAIL == conn->m->execute_init_commands(conn TSRMLS_CC)) {
1068            goto err;
1069        }
1070
1071        MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, STAT_CONNECT_SUCCESS, 1, STAT_OPENED_CONNECTIONS, 1);
1072        if (reconnect) {
1073            MYSQLND_INC_GLOBAL_STATISTIC(STAT_RECONNECT);
1074        }
1075        if (conn->persistent) {
1076            MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, STAT_PCONNECT_SUCCESS, 1, STAT_OPENED_PERSISTENT_CONNECTIONS, 1);
1077        }
1078
1079        DBG_INF_FMT("connection_id=%llu", conn->thread_id);
1080
1081        conn->m->local_tx_end(conn, this_func, PASS TSRMLS_CC);
1082        DBG_RETURN(PASS);
1083    }
1084err:
1085
1086    DBG_ERR_FMT("[%u] %.128s (trying to connect via %s)", conn->error_info->error_no, conn->error_info->error, conn->scheme);
1087    if (!conn->error_info->error_no) {
1088        SET_CLIENT_ERROR(*conn->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, conn->error_info->error? conn->error_info->error:"Unknown error");
1089        php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%u] %.128s (trying to connect via %s)",
1090                         conn->error_info->error_no, conn->error_info->error, conn->scheme);
1091    }
1092
1093    conn->m->free_contents(conn TSRMLS_CC);
1094    MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_FAILURE);
1095    if (TRUE == local_tx_started) {
1096        conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC);
1097    }
1098
1099    DBG_RETURN(FAIL);
1100}
1101/* }}} */
1102
1103
1104/* {{{ mysqlnd_conn::connect */
1105static enum_func_status
1106MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn_handle,
1107                         const char * host, const char * user,
1108                         const char * passwd, unsigned int passwd_len,
1109                         const char * db, unsigned int db_len,
1110                         unsigned int port,
1111                         const char * socket_or_pipe,
1112                         unsigned int mysql_flags
1113                         TSRMLS_DC)
1114{
1115    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, connect);
1116    enum_func_status ret = FAIL;
1117    MYSQLND_CONN_DATA * conn = conn_handle->data;
1118
1119    DBG_ENTER("mysqlnd_conn::connect");
1120
1121    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1122        mysqlnd_options4(conn_handle, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_name", "mysqlnd");
1123        ret = conn->m->connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket_or_pipe, mysql_flags TSRMLS_CC);
1124
1125        conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC);
1126    }
1127    DBG_RETURN(ret);
1128}
1129/* }}} */
1130
1131
1132/* {{{ mysqlnd_connect */
1133PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle,
1134                         const char * host, const char * user,
1135                         const char * passwd, unsigned int passwd_len,
1136                         const char * db, unsigned int db_len,
1137                         unsigned int port,
1138                         const char * socket_or_pipe,
1139                         unsigned int mysql_flags,
1140                         unsigned int client_api_flags
1141                         TSRMLS_DC)
1142{
1143    enum_func_status ret = FAIL;
1144    zend_bool self_alloced = FALSE;
1145
1146    DBG_ENTER("mysqlnd_connect");
1147    DBG_INF_FMT("host=%s user=%s db=%s port=%u flags=%u", host?host:"", user?user:"", db?db:"", port, mysql_flags);
1148
1149    if (!conn_handle) {
1150        self_alloced = TRUE;
1151        if (!(conn_handle = mysqlnd_init(client_api_flags, FALSE))) {
1152            /* OOM */
1153            DBG_RETURN(NULL);
1154        }
1155    }
1156
1157    ret = conn_handle->m->connect(conn_handle, host, user, passwd, passwd_len, db, db_len, port, socket_or_pipe, mysql_flags TSRMLS_CC);
1158
1159    if (ret == FAIL) {
1160        if (self_alloced) {
1161            /*
1162              We have alloced, thus there are no references to this
1163              object - we are free to kill it!
1164            */
1165            conn_handle->m->dtor(conn_handle TSRMLS_CC);
1166        }
1167        DBG_RETURN(NULL);
1168    }
1169    DBG_RETURN(conn_handle);
1170}
1171/* }}} */
1172
1173
1174/* {{{ mysqlnd_conn_data::query */
1175/*
1176  If conn->error_info->error_no is not zero, then we had an error.
1177  Still the result from the query is PASS
1178*/
1179static enum_func_status
1180MYSQLND_METHOD(mysqlnd_conn_data, query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC)
1181{
1182    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, query);
1183    enum_func_status ret = FAIL;
1184    DBG_ENTER("mysqlnd_conn_data::query");
1185    DBG_INF_FMT("conn=%p conn=%llu query=%s", conn, conn->thread_id, query);
1186
1187    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1188        if (PASS == conn->m->send_query(conn, query, query_len TSRMLS_CC) &&
1189            PASS == conn->m->reap_query(conn TSRMLS_CC))
1190        {
1191            ret = PASS;
1192            if (conn->last_query_type == QUERY_UPSERT && conn->upsert_status->affected_rows) {
1193                MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_NORMAL, conn->upsert_status->affected_rows);
1194            }
1195        }
1196        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1197    }
1198    DBG_RETURN(ret);
1199}
1200/* }}} */
1201
1202
1203/* {{{ mysqlnd_conn_data::send_query */
1204static enum_func_status
1205MYSQLND_METHOD(mysqlnd_conn_data, send_query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC)
1206{
1207    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, send_query);
1208    enum_func_status ret = FAIL;
1209    DBG_ENTER("mysqlnd_conn_data::send_query");
1210    DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query);
1211    DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
1212
1213    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1214        ret = conn->m->simple_command(conn, COM_QUERY, (zend_uchar *) query, query_len,
1215                                             PROT_LAST /* we will handle the OK packet*/,
1216                                             FALSE, FALSE TSRMLS_CC);
1217        if (PASS == ret) {
1218            CONN_SET_STATE(conn, CONN_QUERY_SENT);
1219        }
1220        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1221    }
1222    DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
1223    DBG_RETURN(ret);
1224}
1225/* }}} */
1226
1227
1228/* {{{ mysqlnd_conn_data::reap_query */
1229static enum_func_status
1230MYSQLND_METHOD(mysqlnd_conn_data, reap_query)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
1231{
1232    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, reap_query);
1233    enum_mysqlnd_connection_state state = CONN_GET_STATE(conn);
1234    enum_func_status ret = FAIL;
1235    DBG_ENTER("mysqlnd_conn_data::reap_query");
1236    DBG_INF_FMT("conn=%llu", conn->thread_id);
1237
1238    DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
1239    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1240        if (state <= CONN_READY || state == CONN_QUIT_SENT) {
1241            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connection not opened, clear or has been closed");
1242            DBG_ERR_FMT("Connection not opened, clear or has been closed. State=%u", state);
1243            DBG_RETURN(ret);
1244        }
1245        ret = conn->m->query_read_result_set_header(conn, NULL TSRMLS_CC);
1246
1247        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1248    }
1249    DBG_INF_FMT("conn->server_status=%u", conn->upsert_status->server_status);
1250    DBG_RETURN(ret);
1251}
1252/* }}} */
1253
1254
1255#include "php_network.h"
1256
1257/* {{{ mysqlnd_stream_array_to_fd_set */
1258MYSQLND ** mysqlnd_stream_array_check_for_readiness(MYSQLND ** conn_array TSRMLS_DC)
1259{
1260    int cnt = 0;
1261    MYSQLND **p = conn_array, **p_p;
1262    MYSQLND **ret = NULL;
1263
1264    while (*p) {
1265        if (CONN_GET_STATE((*p)->data) <= CONN_READY || CONN_GET_STATE((*p)->data) == CONN_QUIT_SENT) {
1266            cnt++;
1267        }
1268        p++;
1269    }
1270    if (cnt) {
1271        MYSQLND **ret_p = ret = ecalloc(cnt + 1, sizeof(MYSQLND *));
1272        p_p = p = conn_array;
1273        while (*p) {
1274            if (CONN_GET_STATE((*p)->data) <= CONN_READY || CONN_GET_STATE((*p)->data) == CONN_QUIT_SENT) {
1275                *ret_p = *p;
1276                *p = NULL;
1277                ret_p++;
1278            } else {
1279                *p_p = *p;
1280                p_p++;
1281            }
1282            p++;
1283        }
1284        *ret_p = NULL;
1285    }
1286    return ret;
1287}
1288/* }}} */
1289
1290
1291/* {{{ mysqlnd_stream_array_to_fd_set */
1292static int mysqlnd_stream_array_to_fd_set(MYSQLND ** conn_array, fd_set * fds, php_socket_t * max_fd TSRMLS_DC)
1293{
1294    php_socket_t this_fd;
1295    php_stream *stream = NULL;
1296    unsigned int cnt = 0;
1297    MYSQLND **p = conn_array;
1298    DBG_ENTER("mysqlnd_stream_array_to_fd_set");
1299
1300    while (*p) {
1301        /* get the fd.
1302         * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag
1303         * when casting.  It is only used here so that the buffered data warning
1304         * is not displayed.
1305         * */
1306        stream = (*p)->data->net->data->m.get_stream((*p)->data->net TSRMLS_CC);
1307        DBG_INF_FMT("conn=%llu stream=%p", (*p)->data->thread_id, stream);
1308        if (stream != NULL && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL,
1309                                        (void*)&this_fd, 1) && this_fd >= 0) {
1310
1311            PHP_SAFE_FD_SET(this_fd, fds);
1312
1313            if (this_fd > *max_fd) {
1314                *max_fd = this_fd;
1315            }
1316            cnt++;
1317        }
1318        p++;
1319    }
1320    DBG_RETURN(cnt ? 1 : 0);
1321}
1322/* }}} */
1323
1324
1325/* {{{ mysqlnd_stream_array_from_fd_set */
1326static int mysqlnd_stream_array_from_fd_set(MYSQLND ** conn_array, fd_set * fds TSRMLS_DC)
1327{
1328    php_socket_t this_fd;
1329    php_stream *stream = NULL;
1330    int ret = 0;
1331    zend_bool disproportion = FALSE;
1332    MYSQLND **fwd = conn_array, **bckwd = conn_array;
1333    DBG_ENTER("mysqlnd_stream_array_from_fd_set");
1334
1335    while (*fwd) {
1336        stream = (*fwd)->data->net->data->m.get_stream((*fwd)->data->net TSRMLS_CC);
1337        DBG_INF_FMT("conn=%llu stream=%p", (*fwd)->data->thread_id, stream);
1338        if (stream != NULL && SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL,
1339                                        (void*)&this_fd, 1) && this_fd >= 0) {
1340            if (PHP_SAFE_FD_ISSET(this_fd, fds)) {
1341                if (disproportion) {
1342                    *bckwd = *fwd;
1343                }
1344                bckwd++;
1345                fwd++;
1346                ret++;
1347                continue;
1348            }
1349        }
1350        disproportion = TRUE;
1351        fwd++;
1352    }
1353    *bckwd = NULL;/* NULL-terminate the list */
1354
1355    DBG_RETURN(ret);
1356}
1357/* }}} */
1358
1359
1360#ifndef PHP_WIN32
1361#define php_select(m, r, w, e, t)   select(m, r, w, e, t)
1362#else
1363#include "win32/select.h"
1364#endif
1365
1366
1367/* {{{ _mysqlnd_poll */
1368PHPAPI enum_func_status
1369_mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, int * desc_num TSRMLS_DC)
1370{
1371    struct timeval  tv;
1372    struct timeval *tv_p = NULL;
1373    fd_set          rfds, wfds, efds;
1374    php_socket_t    max_fd = 0;
1375    int             retval, sets = 0;
1376    int             set_count, max_set_count = 0;
1377
1378    DBG_ENTER("_mysqlnd_poll");
1379    if (sec < 0 || usec < 0) {
1380        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Negative values passed for sec and/or usec");
1381        DBG_RETURN(FAIL);
1382    }
1383
1384    FD_ZERO(&rfds);
1385    FD_ZERO(&wfds);
1386    FD_ZERO(&efds);
1387
1388    if (r_array != NULL) {
1389        *dont_poll = mysqlnd_stream_array_check_for_readiness(r_array TSRMLS_CC);
1390        set_count = mysqlnd_stream_array_to_fd_set(r_array, &rfds, &max_fd TSRMLS_CC);
1391        if (set_count > max_set_count) {
1392            max_set_count = set_count;
1393        }
1394        sets += set_count;
1395    }
1396
1397    if (e_array != NULL) {
1398        set_count = mysqlnd_stream_array_to_fd_set(e_array, &efds, &max_fd TSRMLS_CC);
1399        if (set_count > max_set_count) {
1400            max_set_count = set_count;
1401        }
1402        sets += set_count;
1403    }
1404
1405    if (!sets) {
1406        php_error_docref(NULL TSRMLS_CC, E_WARNING, *dont_poll ? "All arrays passed are clear":"No stream arrays were passed");
1407        DBG_ERR_FMT(*dont_poll ? "All arrays passed are clear":"No stream arrays were passed");
1408        DBG_RETURN(FAIL);
1409    }
1410
1411    PHP_SAFE_MAX_FD(max_fd, max_set_count);
1412
1413    /* Solaris + BSD do not like microsecond values which are >= 1 sec */
1414    if (usec > 999999) {
1415        tv.tv_sec = sec + (usec / 1000000);
1416        tv.tv_usec = usec % 1000000;
1417    } else {
1418        tv.tv_sec = sec;
1419        tv.tv_usec = usec;
1420    }
1421
1422    tv_p = &tv;
1423
1424    retval = php_select(max_fd + 1, &rfds, &wfds, &efds, tv_p);
1425
1426    if (retval == -1) {
1427        php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s (max_fd=%d)",
1428                        errno, strerror(errno), max_fd);
1429        DBG_RETURN(FAIL);
1430    }
1431
1432    if (r_array != NULL) {
1433        mysqlnd_stream_array_from_fd_set(r_array, &rfds TSRMLS_CC);
1434    }
1435    if (e_array != NULL) {
1436        mysqlnd_stream_array_from_fd_set(e_array, &efds TSRMLS_CC);
1437    }
1438
1439    *desc_num = retval;
1440    DBG_RETURN(PASS);
1441}
1442/* }}} */
1443
1444
1445/*
1446  COM_FIELD_LIST is special, different from a SHOW FIELDS FROM :
1447  - There is no result set header - status from the command, which
1448    impacts us to allocate big chunk of memory for reading the metadata.
1449  - The EOF packet is consumed by the metadata packet reader.
1450*/
1451
1452/* {{{ mysqlnd_conn_data::list_fields */
1453MYSQLND_RES *
1454MYSQLND_METHOD(mysqlnd_conn_data, list_fields)(MYSQLND_CONN_DATA * conn, const char *table, const char *achtung_wild TSRMLS_DC)
1455{
1456    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, list_fields);
1457    /* db + \0 + wild + \0 (for wild) */
1458    zend_uchar buff[MYSQLND_MAX_ALLOWED_DB_LEN * 2 + 1 + 1], *p;
1459    size_t table_len, wild_len;
1460    MYSQLND_RES * result = NULL;
1461    DBG_ENTER("mysqlnd_conn_data::list_fields");
1462    DBG_INF_FMT("conn=%llu table=%s wild=%s", conn->thread_id, table? table:"",achtung_wild? achtung_wild:"");
1463
1464    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1465        do {
1466            p = buff;
1467            if (table && (table_len = strlen(table))) {
1468                size_t to_copy = MIN(table_len, MYSQLND_MAX_ALLOWED_DB_LEN);
1469                memcpy(p, table, to_copy);
1470                p += to_copy;
1471                *p++ = '\0';
1472            }
1473
1474            if (achtung_wild && (wild_len = strlen(achtung_wild))) {
1475                size_t to_copy = MIN(wild_len, MYSQLND_MAX_ALLOWED_DB_LEN);
1476                memcpy(p, achtung_wild, to_copy);
1477                p += to_copy;
1478                *p++ = '\0';
1479            }
1480
1481            if (PASS != conn->m->simple_command(conn, COM_FIELD_LIST, buff, p - buff,
1482                                               PROT_LAST /* we will handle the OK packet*/,
1483                                               FALSE, TRUE TSRMLS_CC)) {
1484                conn->m->local_tx_end(conn, 0, FAIL TSRMLS_CC);
1485                break;
1486            }
1487
1488            /*
1489               Prepare for the worst case.
1490               MyISAM goes to 2500 BIT columns, double it for safety.
1491            */
1492            result = conn->m->result_init(5000, conn->persistent TSRMLS_CC);
1493            if (!result) {
1494                break;
1495            }
1496
1497            if (FAIL == result->m.read_result_metadata(result, conn TSRMLS_CC)) {
1498                DBG_ERR("Error occurred while reading metadata");
1499                result->m.free_result(result, TRUE TSRMLS_CC);
1500                result = NULL;
1501                break;
1502            }
1503
1504            result->type = MYSQLND_RES_NORMAL;
1505            result->unbuf = mysqlnd_result_unbuffered_init(result->field_count, FALSE, result->persistent TSRMLS_CC);
1506            if (!result->unbuf) {
1507                /* OOM */
1508                SET_OOM_ERROR(*conn->error_info);
1509                result->m.free_result(result, TRUE TSRMLS_CC);
1510                result = NULL;
1511                break;
1512            }
1513            result->unbuf->eof_reached = TRUE;
1514        } while (0);
1515        conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC);
1516    }
1517
1518    DBG_RETURN(result);
1519}
1520/* }}} */
1521
1522
1523/* {{{ mysqlnd_conn_data::list_method */
1524MYSQLND_RES *
1525MYSQLND_METHOD(mysqlnd_conn_data, list_method)(MYSQLND_CONN_DATA * conn, const char * query, const char *achtung_wild, char *par1 TSRMLS_DC)
1526{
1527    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, list_method);
1528    char * show_query = NULL;
1529    size_t show_query_len;
1530    MYSQLND_RES * result = NULL;
1531
1532    DBG_ENTER("mysqlnd_conn_data::list_method");
1533    DBG_INF_FMT("conn=%llu query=%s wild=%u", conn->thread_id, query, achtung_wild);
1534
1535    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1536        if (par1) {
1537            if (achtung_wild) {
1538                show_query_len = mnd_sprintf(&show_query, 0, query, par1, achtung_wild);
1539            } else {
1540                show_query_len = mnd_sprintf(&show_query, 0, query, par1);
1541            }
1542        } else {
1543            if (achtung_wild) {
1544                show_query_len = mnd_sprintf(&show_query, 0, query, achtung_wild);
1545            } else {
1546                show_query_len = strlen(show_query = (char *)query);
1547            }
1548        }
1549
1550        if (PASS == conn->m->query(conn, show_query, show_query_len TSRMLS_CC)) {
1551            result = conn->m->store_result(conn, MYSQLND_STORE_NO_COPY TSRMLS_CC);
1552        }
1553        if (show_query != query) {
1554            mnd_sprintf_free(show_query);
1555        }
1556        conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC);
1557    }
1558    DBG_RETURN(result);
1559}
1560/* }}} */
1561
1562
1563/* {{{ mysqlnd_conn_data::errno */
1564static unsigned int
1565MYSQLND_METHOD(mysqlnd_conn_data, errno)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1566{
1567    return conn->error_info->error_no;
1568}
1569/* }}} */
1570
1571
1572/* {{{ mysqlnd_conn_data::error */
1573static const char *
1574MYSQLND_METHOD(mysqlnd_conn_data, error)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1575{
1576    return conn->error_info->error;
1577}
1578/* }}} */
1579
1580
1581/* {{{ mysqlnd_conn_data::sqlstate */
1582static const char *
1583MYSQLND_METHOD(mysqlnd_conn_data, sqlstate)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1584{
1585    return conn->error_info->sqlstate[0] ? conn->error_info->sqlstate:MYSQLND_SQLSTATE_NULL;
1586}
1587/* }}} */
1588
1589
1590/* {{{ mysqlnd_old_escape_string */
1591PHPAPI  zend_ulong
1592mysqlnd_old_escape_string(char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC)
1593{
1594    DBG_ENTER("mysqlnd_old_escape_string");
1595    DBG_RETURN(mysqlnd_cset_escape_slashes(mysqlnd_find_charset_name("latin1"), newstr, escapestr, escapestr_len TSRMLS_CC));
1596}
1597/* }}} */
1598
1599
1600/* {{{ mysqlnd_conn_data::ssl_set */
1601static enum_func_status
1602MYSQLND_METHOD(mysqlnd_conn_data, ssl_set)(MYSQLND_CONN_DATA * const conn, const char * key, const char * const cert,
1603                                      const char * const ca, const char * const capath, const char * const cipher TSRMLS_DC)
1604{
1605    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, ssl_set);
1606    enum_func_status ret = FAIL;
1607    MYSQLND_NET * net = conn->net;
1608    DBG_ENTER("mysqlnd_conn_data::ssl_set");
1609
1610    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1611        ret = (PASS == net->data->m.set_client_option(net, MYSQLND_OPT_SSL_KEY, key TSRMLS_CC) &&
1612            PASS == net->data->m.set_client_option(net, MYSQLND_OPT_SSL_CERT, cert TSRMLS_CC) &&
1613            PASS == net->data->m.set_client_option(net, MYSQLND_OPT_SSL_CA, ca TSRMLS_CC) &&
1614            PASS == net->data->m.set_client_option(net, MYSQLND_OPT_SSL_CAPATH, capath TSRMLS_CC) &&
1615            PASS == net->data->m.set_client_option(net, MYSQLND_OPT_SSL_CIPHER, cipher TSRMLS_CC)) ? PASS : FAIL;
1616
1617        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1618    }
1619    DBG_RETURN(ret);
1620}
1621/* }}} */
1622
1623
1624/* {{{ mysqlnd_conn_data::escape_string */
1625static zend_ulong
1626MYSQLND_METHOD(mysqlnd_conn_data, escape_string)(MYSQLND_CONN_DATA * const conn, char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC)
1627{
1628    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, escape_string);
1629    zend_ulong ret = FAIL;
1630    DBG_ENTER("mysqlnd_conn_data::escape_string");
1631    DBG_INF_FMT("conn=%llu", conn->thread_id);
1632
1633    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1634        DBG_INF_FMT("server_status=%u", conn->upsert_status->server_status);
1635        if (conn->upsert_status->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) {
1636            ret = mysqlnd_cset_escape_quotes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC);
1637        } else {
1638            ret = mysqlnd_cset_escape_slashes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC);
1639        }
1640        conn->m->local_tx_end(conn, this_func, PASS TSRMLS_CC);
1641    }
1642    DBG_RETURN(ret);
1643}
1644/* }}} */
1645
1646
1647/* {{{ mysqlnd_conn_data::dump_debug_info */
1648static enum_func_status
1649MYSQLND_METHOD(mysqlnd_conn_data, dump_debug_info)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1650{
1651    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, server_dump_debug_information);
1652    enum_func_status ret = FAIL;
1653    DBG_ENTER("mysqlnd_conn_data::dump_debug_info");
1654    DBG_INF_FMT("conn=%llu", conn->thread_id);
1655    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1656        ret = conn->m->simple_command(conn, COM_DEBUG, NULL, 0, PROT_EOF_PACKET, FALSE, TRUE TSRMLS_CC);
1657
1658        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1659    }
1660
1661    DBG_RETURN(ret);
1662}
1663/* }}} */
1664
1665
1666/* {{{ mysqlnd_conn_data::select_db */
1667static enum_func_status
1668MYSQLND_METHOD(mysqlnd_conn_data, select_db)(MYSQLND_CONN_DATA * const conn, const char * const db, unsigned int db_len TSRMLS_DC)
1669{
1670    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, select_db);
1671    enum_func_status ret = FAIL;
1672
1673    DBG_ENTER("mysqlnd_conn_data::select_db");
1674    DBG_INF_FMT("conn=%llu db=%s", conn->thread_id, db);
1675
1676    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1677        ret = conn->m->simple_command(conn, COM_INIT_DB, (zend_uchar*) db, db_len, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC);
1678        /*
1679          The server sends 0 but libmysql doesn't read it and has established
1680          a protocol of giving back -1. Thus we have to follow it :(
1681        */
1682        SET_ERROR_AFF_ROWS(conn);
1683        if (ret == PASS) {
1684            if (conn->connect_or_select_db) {
1685                mnd_pefree(conn->connect_or_select_db, conn->persistent);
1686            }
1687            conn->connect_or_select_db = mnd_pestrndup(db, db_len, conn->persistent);
1688            conn->connect_or_select_db_len = db_len;
1689            if (!conn->connect_or_select_db) {
1690                /* OOM */
1691                SET_OOM_ERROR(*conn->error_info);
1692                ret = FAIL;
1693            }
1694        }
1695        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1696    }
1697    DBG_RETURN(ret);
1698}
1699/* }}} */
1700
1701
1702/* {{{ mysqlnd_conn_data::ping */
1703static enum_func_status
1704MYSQLND_METHOD(mysqlnd_conn_data, ping)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1705{
1706    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, ping);
1707    enum_func_status ret = FAIL;
1708
1709    DBG_ENTER("mysqlnd_conn_data::ping");
1710    DBG_INF_FMT("conn=%llu", conn->thread_id);
1711
1712    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1713        ret = conn->m->simple_command(conn, COM_PING, NULL, 0, PROT_OK_PACKET, TRUE, TRUE TSRMLS_CC);
1714        /*
1715          The server sends 0 but libmysql doesn't read it and has established
1716          a protocol of giving back -1. Thus we have to follow it :(
1717        */
1718        SET_ERROR_AFF_ROWS(conn);
1719
1720        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1721    }
1722    DBG_INF_FMT("ret=%u", ret);
1723    DBG_RETURN(ret);
1724}
1725/* }}} */
1726
1727
1728/* {{{ mysqlnd_conn_data::statistic */
1729static enum_func_status
1730MYSQLND_METHOD(mysqlnd_conn_data, statistic)(MYSQLND_CONN_DATA * conn, zend_string **message TSRMLS_DC)
1731{
1732    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, get_server_statistics);
1733    enum_func_status ret = FAIL;
1734    MYSQLND_PACKET_STATS * stats_header;
1735
1736    DBG_ENTER("mysqlnd_conn_data::statistic");
1737    DBG_INF_FMT("conn=%llu", conn->thread_id);
1738
1739    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1740        do {
1741            ret = conn->m->simple_command(conn, COM_STATISTICS, NULL, 0, PROT_LAST, FALSE, TRUE TSRMLS_CC);
1742            if (FAIL == ret) {
1743                break;
1744            }
1745            stats_header = conn->protocol->m.get_stats_packet(conn->protocol, FALSE TSRMLS_CC);
1746            if (!stats_header) {
1747                SET_OOM_ERROR(*conn->error_info);
1748                break;
1749            }
1750
1751            if (PASS == (ret = PACKET_READ(stats_header, conn))) {
1752                /* will be freed by Zend, thus don't use the mnd_ allocator */
1753                *message = zend_string_init(stats_header->message, stats_header->message_len, 0);
1754                DBG_INF((*message)->val);
1755            }
1756            PACKET_FREE(stats_header);
1757        } while (0);
1758
1759        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1760    }
1761    DBG_RETURN(ret);
1762}
1763/* }}} */
1764
1765
1766/* {{{ mysqlnd_conn_data::kill */
1767static enum_func_status
1768MYSQLND_METHOD(mysqlnd_conn_data, kill)(MYSQLND_CONN_DATA * conn, unsigned int pid TSRMLS_DC)
1769{
1770    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, kill_connection);
1771    enum_func_status ret = FAIL;
1772    zend_uchar buff[4];
1773
1774    DBG_ENTER("mysqlnd_conn_data::kill");
1775    DBG_INF_FMT("conn=%llu pid=%u", conn->thread_id, pid);
1776
1777    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1778        int4store(buff, pid);
1779
1780        /* If we kill ourselves don't expect OK packet, PROT_LAST will skip it */
1781        if (pid != conn->thread_id) {
1782            ret = conn->m->simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC);
1783            /*
1784              The server sends 0 but libmysql doesn't read it and has established
1785              a protocol of giving back -1. Thus we have to follow it :(
1786            */
1787            SET_ERROR_AFF_ROWS(conn);
1788        } else if (PASS == (ret = conn->m->simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_LAST, FALSE, TRUE TSRMLS_CC))) {
1789            CONN_SET_STATE(conn, CONN_QUIT_SENT);
1790            conn->m->send_close(conn TSRMLS_CC);
1791        }
1792
1793        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1794    }
1795    DBG_RETURN(ret);
1796}
1797/* }}} */
1798
1799
1800/* {{{ mysqlnd_conn_data::set_charset */
1801static enum_func_status
1802MYSQLND_METHOD(mysqlnd_conn_data, set_charset)(MYSQLND_CONN_DATA * const conn, const char * const csname TSRMLS_DC)
1803{
1804    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_charset);
1805    enum_func_status ret = FAIL;
1806    const MYSQLND_CHARSET * const charset = mysqlnd_find_charset_name(csname);
1807
1808    DBG_ENTER("mysqlnd_conn_data::set_charset");
1809    DBG_INF_FMT("conn=%llu cs=%s", conn->thread_id, csname);
1810
1811    if (!charset) {
1812        SET_CLIENT_ERROR(*conn->error_info, CR_CANT_FIND_CHARSET, UNKNOWN_SQLSTATE,
1813                         "Invalid characterset or character set not supported");
1814        DBG_RETURN(ret);
1815    }
1816
1817    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1818        char * query;
1819        size_t query_len = mnd_sprintf(&query, 0, "SET NAMES %s", csname);
1820
1821        if (FAIL == (ret = conn->m->query(conn, query, query_len TSRMLS_CC))) {
1822            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error executing query");
1823        } else if (conn->error_info->error_no) {
1824            ret = FAIL;
1825        } else {
1826            conn->charset = charset;
1827        }
1828        mnd_sprintf_free(query);
1829
1830        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1831    }
1832
1833    DBG_INF(ret == PASS? "PASS":"FAIL");
1834    DBG_RETURN(ret);
1835}
1836/* }}} */
1837
1838
1839/* {{{ mysqlnd_conn_data::refresh */
1840static enum_func_status
1841MYSQLND_METHOD(mysqlnd_conn_data, refresh)(MYSQLND_CONN_DATA * const conn, uint8_t options TSRMLS_DC)
1842{
1843    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, refresh_server);
1844    enum_func_status ret = FAIL;
1845    zend_uchar bits[1];
1846    DBG_ENTER("mysqlnd_conn_data::refresh");
1847    DBG_INF_FMT("conn=%llu options=%lu", conn->thread_id, options);
1848
1849    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1850        int1store(bits, options);
1851
1852        ret = conn->m->simple_command(conn, COM_REFRESH, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC);
1853
1854        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1855    }
1856    DBG_RETURN(ret);
1857}
1858/* }}} */
1859
1860
1861/* {{{ mysqlnd_conn_data::shutdown */
1862static enum_func_status
1863MYSQLND_METHOD(mysqlnd_conn_data, shutdown)(MYSQLND_CONN_DATA * const conn, uint8_t level TSRMLS_DC)
1864{
1865    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, shutdown_server);
1866    enum_func_status ret = FAIL;
1867    zend_uchar bits[1];
1868    DBG_ENTER("mysqlnd_conn_data::shutdown");
1869    DBG_INF_FMT("conn=%llu level=%lu", conn->thread_id, level);
1870
1871    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
1872        int1store(bits, level);
1873
1874        ret = conn->m->simple_command(conn, COM_SHUTDOWN, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC);
1875
1876        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
1877    }
1878    DBG_RETURN(ret);
1879}
1880/* }}} */
1881
1882
1883/* {{{ mysqlnd_send_close */
1884static enum_func_status
1885MYSQLND_METHOD(mysqlnd_conn_data, send_close)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1886{
1887    enum_func_status ret = PASS;
1888    MYSQLND_NET * net = conn->net;
1889    php_stream * net_stream = net->data->m.get_stream(net TSRMLS_CC);
1890    enum mysqlnd_connection_state state;
1891
1892    DBG_ENTER("mysqlnd_send_close");
1893    DBG_INF_FMT("conn=%llu net->data->stream->abstract=%p", conn->thread_id, net_stream? net_stream->abstract:NULL);
1894
1895    if (CONN_GET_STATE(conn) >= CONN_READY) {
1896        MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_CONNECTIONS);
1897        if (conn->persistent) {
1898            MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS);
1899        }
1900    }
1901    state = CONN_GET_STATE(conn);
1902    DBG_INF_FMT("state=%u", state);
1903    switch (state) {
1904        case CONN_READY:
1905            DBG_INF("Connection clean, sending COM_QUIT");
1906            if (net_stream) {
1907                ret = conn->m->simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST, TRUE, TRUE TSRMLS_CC);
1908                net->data->m.close_stream(net, conn->stats, conn->error_info TSRMLS_CC);
1909            }
1910            CONN_SET_STATE(conn, CONN_QUIT_SENT);
1911            break;
1912        case CONN_SENDING_LOAD_DATA:
1913            /*
1914              Don't send COM_QUIT if we are in a middle of a LOAD DATA or we
1915              will crash (assert) a debug server.
1916            */
1917        case CONN_NEXT_RESULT_PENDING:
1918        case CONN_QUERY_SENT:
1919        case CONN_FETCHING_DATA:
1920            MYSQLND_INC_GLOBAL_STATISTIC(STAT_CLOSE_IN_MIDDLE);
1921            DBG_ERR_FMT("Brutally closing connection [%p][%s]", conn, conn->scheme);
1922            /*
1923              Do nothing, the connection will be brutally closed
1924              and the server will catch it and free close from its side.
1925            */
1926            /* Fall-through */
1927        case CONN_ALLOCED:
1928            /*
1929              Allocated but not connected or there was failure when trying
1930              to connect with pre-allocated connect.
1931
1932              Fall-through
1933            */
1934            CONN_SET_STATE(conn, CONN_QUIT_SENT);
1935            /* Fall-through */
1936        case CONN_QUIT_SENT:
1937            /* The user has killed its own connection */
1938            net->data->m.close_stream(net, conn->stats, conn->error_info TSRMLS_CC);
1939            break;
1940    }
1941
1942    DBG_RETURN(ret);
1943}
1944/* }}} */
1945
1946
1947/* {{{ mysqlnd_conn_data::get_reference */
1948static MYSQLND_CONN_DATA *
1949MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1950{
1951    DBG_ENTER("mysqlnd_conn_data::get_reference");
1952    ++conn->refcount;
1953    DBG_INF_FMT("conn=%llu new_refcount=%u", conn->thread_id, conn->refcount);
1954    DBG_RETURN(conn);
1955}
1956/* }}} */
1957
1958
1959/* {{{ mysqlnd_conn_data::free_reference */
1960static enum_func_status
1961MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, free_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1962{
1963    enum_func_status ret = PASS;
1964    DBG_ENTER("mysqlnd_conn_data::free_reference");
1965    DBG_INF_FMT("conn=%llu old_refcount=%u", conn->thread_id, conn->refcount);
1966    if (!(--conn->refcount)) {
1967        /*
1968          No multithreading issues as we don't share the connection :)
1969          This will free the object too, of course because references has
1970          reached zero.
1971        */
1972        ret = conn->m->send_close(conn TSRMLS_CC);
1973        conn->m->dtor(conn TSRMLS_CC);
1974    }
1975    DBG_RETURN(ret);
1976}
1977/* }}} */
1978
1979
1980/* {{{ mysqlnd_conn_data::get_state */
1981static enum mysqlnd_connection_state
1982MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_state)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
1983{
1984    DBG_ENTER("mysqlnd_conn_data::get_state");
1985    DBG_RETURN(conn->state);
1986}
1987/* }}} */
1988
1989
1990/* {{{ mysqlnd_conn_data::set_state */
1991static void
1992MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, set_state)(MYSQLND_CONN_DATA * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC)
1993{
1994    DBG_ENTER("mysqlnd_conn_data::set_state");
1995    DBG_INF_FMT("New state=%u", new_state);
1996    conn->state = new_state;
1997    DBG_VOID_RETURN;
1998}
1999/* }}} */
2000
2001
2002/* {{{ mysqlnd_conn_data::field_count */
2003static unsigned int
2004MYSQLND_METHOD(mysqlnd_conn_data, field_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2005{
2006    return conn->field_count;
2007}
2008/* }}} */
2009
2010
2011/* {{{ mysqlnd_conn_data::server_status */
2012static unsigned int
2013MYSQLND_METHOD(mysqlnd_conn_data, server_status)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2014{
2015    return conn->upsert_status->server_status;
2016}
2017/* }}} */
2018
2019
2020/* {{{ mysqlnd_conn_data::insert_id */
2021static uint64_t
2022MYSQLND_METHOD(mysqlnd_conn_data, insert_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2023{
2024    return conn->upsert_status->last_insert_id;
2025}
2026/* }}} */
2027
2028
2029/* {{{ mysqlnd_conn_data::affected_rows */
2030static uint64_t
2031MYSQLND_METHOD(mysqlnd_conn_data, affected_rows)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2032{
2033    return conn->upsert_status->affected_rows;
2034}
2035/* }}} */
2036
2037
2038/* {{{ mysqlnd_conn_data::warning_count */
2039static unsigned int
2040MYSQLND_METHOD(mysqlnd_conn_data, warning_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2041{
2042    return conn->upsert_status->warning_count;
2043}
2044/* }}} */
2045
2046
2047/* {{{ mysqlnd_conn_data::info */
2048static const char *
2049MYSQLND_METHOD(mysqlnd_conn_data, info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2050{
2051    return conn->last_message;
2052}
2053/* }}} */
2054
2055/* {{{ mysqlnd_get_client_info */
2056PHPAPI const char * mysqlnd_get_client_info()
2057{
2058    return MYSQLND_VERSION;
2059}
2060/* }}} */
2061
2062
2063/* {{{ mysqlnd_get_client_version */
2064PHPAPI unsigned int mysqlnd_get_client_version()
2065{
2066    return MYSQLND_VERSION_ID;
2067}
2068/* }}} */
2069
2070
2071/* {{{ mysqlnd_conn_data::get_server_info */
2072static const char *
2073MYSQLND_METHOD(mysqlnd_conn_data, get_server_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2074{
2075    return conn->server_version;
2076}
2077/* }}} */
2078
2079
2080/* {{{ mysqlnd_conn_data::get_host_info */
2081static const char *
2082MYSQLND_METHOD(mysqlnd_conn_data, get_host_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2083{
2084    return conn->host_info;
2085}
2086/* }}} */
2087
2088
2089/* {{{ mysqlnd_conn_data::get_proto_info */
2090static unsigned int
2091MYSQLND_METHOD(mysqlnd_conn_data, get_proto_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2092{
2093    return conn->protocol_version;
2094}
2095/* }}} */
2096
2097
2098/* {{{ mysqlnd_conn_data::charset_name */
2099static const char *
2100MYSQLND_METHOD(mysqlnd_conn_data, charset_name)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2101{
2102    return conn->charset->name;
2103}
2104/* }}} */
2105
2106
2107/* {{{ mysqlnd_conn_data::thread_id */
2108static uint64_t
2109MYSQLND_METHOD(mysqlnd_conn_data, thread_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2110{
2111    return conn->thread_id;
2112}
2113/* }}} */
2114
2115
2116/* {{{ mysqlnd_conn_data::get_server_version */
2117static zend_ulong
2118MYSQLND_METHOD(mysqlnd_conn_data, get_server_version)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2119{
2120    zend_long major, minor, patch;
2121    char *p;
2122
2123    if (!(p = conn->server_version)) {
2124        return 0;
2125    }
2126
2127    major = ZEND_STRTOL(p, &p, 10);
2128    p += 1; /* consume the dot */
2129    minor = ZEND_STRTOL(p, &p, 10);
2130    p += 1; /* consume the dot */
2131    patch = ZEND_STRTOL(p, &p, 10);
2132
2133    return (zend_ulong)(major * Z_L(10000) + (zend_ulong)(minor * Z_L(100) + patch));
2134}
2135/* }}} */
2136
2137
2138/* {{{ mysqlnd_conn_data::more_results */
2139static zend_bool
2140MYSQLND_METHOD(mysqlnd_conn_data, more_results)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2141{
2142    DBG_ENTER("mysqlnd_conn_data::more_results");
2143    /* (conn->state == CONN_NEXT_RESULT_PENDING) too */
2144    DBG_RETURN(conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS? TRUE:FALSE);
2145}
2146/* }}} */
2147
2148
2149/* {{{ mysqlnd_conn_data::next_result */
2150static enum_func_status
2151MYSQLND_METHOD(mysqlnd_conn_data, next_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2152{
2153    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, next_result);
2154    enum_func_status ret = FAIL;
2155
2156    DBG_ENTER("mysqlnd_conn_data::next_result");
2157    DBG_INF_FMT("conn=%llu", conn->thread_id);
2158
2159    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2160        do {
2161            if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING) {
2162                break;
2163            }
2164
2165            SET_EMPTY_ERROR(*conn->error_info);
2166            SET_ERROR_AFF_ROWS(conn);
2167            /*
2168              We are sure that there is a result set, since conn->state is set accordingly
2169              in mysqlnd_store_result() or mysqlnd_fetch_row_unbuffered()
2170            */
2171            if (FAIL == (ret = conn->m->query_read_result_set_header(conn, NULL TSRMLS_CC))) {
2172                /*
2173                  There can be an error in the middle of a multi-statement, which will cancel the multi-statement.
2174                  So there are no more results and we should just return FALSE, error_no has been set
2175                */
2176                if (!conn->error_info->error_no) {
2177                    DBG_ERR_FMT("Serious error. %s::%u", __FILE__, __LINE__);
2178                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid());
2179                    CONN_SET_STATE(conn, CONN_QUIT_SENT);
2180                    conn->m->send_close(conn TSRMLS_CC);
2181                } else {
2182                    DBG_INF_FMT("Error from the server : (%u) %s", conn->error_info->error_no, conn->error_info->error);
2183                }
2184                break;
2185            }
2186            if (conn->last_query_type == QUERY_UPSERT && conn->upsert_status->affected_rows) {
2187                MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_NORMAL, conn->upsert_status->affected_rows);
2188            }
2189        } while (0);
2190        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2191    }
2192
2193    DBG_RETURN(ret);
2194}
2195/* }}} */
2196
2197
2198/* {{{ mysqlnd_field_type_name */
2199PHPAPI const char *mysqlnd_field_type_name(enum mysqlnd_field_types field_type)
2200{
2201    switch(field_type) {
2202        case FIELD_TYPE_STRING:
2203        case FIELD_TYPE_VAR_STRING:
2204            return "string";
2205        case FIELD_TYPE_TINY:
2206        case FIELD_TYPE_SHORT:
2207        case FIELD_TYPE_LONG:
2208        case FIELD_TYPE_LONGLONG:
2209        case FIELD_TYPE_INT24:
2210            return "int";
2211        case FIELD_TYPE_FLOAT:
2212        case FIELD_TYPE_DOUBLE:
2213        case FIELD_TYPE_DECIMAL:
2214        case FIELD_TYPE_NEWDECIMAL:
2215            return "real";
2216        case FIELD_TYPE_TIMESTAMP:
2217            return "timestamp";
2218        case FIELD_TYPE_YEAR:
2219            return "year";
2220        case FIELD_TYPE_DATE:
2221        case FIELD_TYPE_NEWDATE:
2222            return "date";
2223        case FIELD_TYPE_TIME:
2224            return "time";
2225        case FIELD_TYPE_SET:
2226            return "set";
2227        case FIELD_TYPE_ENUM:
2228            return "enum";
2229        case FIELD_TYPE_GEOMETRY:
2230            return "geometry";
2231        case FIELD_TYPE_DATETIME:
2232            return "datetime";
2233        case FIELD_TYPE_TINY_BLOB:
2234        case FIELD_TYPE_MEDIUM_BLOB:
2235        case FIELD_TYPE_LONG_BLOB:
2236        case FIELD_TYPE_BLOB:
2237            return "blob";
2238        case FIELD_TYPE_NULL:
2239            return "null";
2240        case FIELD_TYPE_BIT:
2241            return "bit";
2242        default:
2243            return "unknown";
2244    }
2245}
2246/* }}} */
2247
2248
2249/* {{{ mysqlnd_conn_data::change_user */
2250static enum_func_status
2251MYSQLND_METHOD(mysqlnd_conn_data, change_user)(MYSQLND_CONN_DATA * const conn,
2252                                          const char * user,
2253                                          const char * passwd,
2254                                          const char * db,
2255                                          zend_bool silent,
2256                                          size_t passwd_len
2257                                          TSRMLS_DC)
2258{
2259    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, change_user);
2260    enum_func_status ret = FAIL;
2261
2262    DBG_ENTER("mysqlnd_conn_data::change_user");
2263    DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s silent=%u",
2264                conn->thread_id, user?user:"", passwd?"***":"null", db?db:"", (silent == TRUE)?1:0 );
2265
2266    if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2267        goto end;
2268    }
2269
2270    SET_EMPTY_ERROR(*conn->error_info);
2271    SET_ERROR_AFF_ROWS(conn);
2272
2273    if (!user) {
2274        user = "";
2275    }
2276    if (!passwd) {
2277        passwd = "";
2278    }
2279    if (!db) {
2280        db = "";
2281    }
2282
2283    /* XXX: passwords that have \0 inside work during auth, but in this case won't work with change user */
2284    ret = mysqlnd_run_authentication(conn, user, passwd, passwd_len, db, strlen(db),
2285                                    conn->auth_plugin_data, conn->auth_plugin_data_len, conn->options->auth_protocol,
2286                                    0 /*charset not used*/, conn->options, conn->server_capabilities, silent, TRUE/*is_change*/ TSRMLS_CC);
2287
2288    /*
2289      Here we should close all statements. Unbuffered queries should not be a
2290      problem as we won't allow sending COM_CHANGE_USER.
2291    */
2292    conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2293end:
2294    DBG_INF(ret == PASS? "PASS":"FAIL");
2295    DBG_RETURN(ret);
2296}
2297/* }}} */
2298
2299
2300/* {{{ mysqlnd_conn_data::set_client_option */
2301static enum_func_status
2302MYSQLND_METHOD(mysqlnd_conn_data, set_client_option)(MYSQLND_CONN_DATA * const conn,
2303                                                enum mysqlnd_option option,
2304                                                const char * const value
2305                                                TSRMLS_DC)
2306{
2307    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_client_option);
2308    enum_func_status ret = PASS;
2309    DBG_ENTER("mysqlnd_conn_data::set_client_option");
2310    DBG_INF_FMT("conn=%llu option=%u", conn->thread_id, option);
2311
2312    if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2313        goto end;
2314    }
2315    switch (option) {
2316        case MYSQL_OPT_COMPRESS:
2317#ifdef WHEN_SUPPORTED_BY_MYSQLI
2318        case MYSQL_OPT_READ_TIMEOUT:
2319        case MYSQL_OPT_WRITE_TIMEOUT:
2320#endif
2321        case MYSQLND_OPT_SSL_KEY:
2322        case MYSQLND_OPT_SSL_CERT:
2323        case MYSQLND_OPT_SSL_CA:
2324        case MYSQLND_OPT_SSL_CAPATH:
2325        case MYSQLND_OPT_SSL_CIPHER:
2326        case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
2327        case MYSQL_OPT_CONNECT_TIMEOUT:
2328        case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
2329        case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
2330        case MYSQL_SERVER_PUBLIC_KEY:
2331            ret = conn->net->data->m.set_client_option(conn->net, option, value TSRMLS_CC);
2332            break;
2333#ifdef MYSQLND_STRING_TO_INT_CONVERSION
2334        case MYSQLND_OPT_INT_AND_FLOAT_NATIVE:
2335            conn->options->int_and_float_native = *(unsigned int*) value;
2336            break;
2337#endif
2338        case MYSQL_OPT_LOCAL_INFILE:
2339            if (value && (*(unsigned int*) value) ? 1 : 0) {
2340                conn->options->flags |= CLIENT_LOCAL_FILES;
2341            } else {
2342                conn->options->flags &= ~CLIENT_LOCAL_FILES;
2343            }
2344            break;
2345        case MYSQL_INIT_COMMAND:
2346        {
2347            char ** new_init_commands;
2348            char * new_command;
2349            /* when num_commands is 0, then realloc will be effectively a malloc call, internally */
2350            /* Don't assign to conn->options->init_commands because in case of OOM we will lose the pointer and leak */
2351            new_init_commands = mnd_perealloc(conn->options->init_commands, sizeof(char *) * (conn->options->num_commands + 1), conn->persistent);
2352            if (!new_init_commands) {
2353                goto oom;
2354            }
2355            conn->options->init_commands = new_init_commands;
2356            new_command = mnd_pestrdup(value, conn->persistent);
2357            if (!new_command) {
2358                goto oom;
2359            }
2360            conn->options->init_commands[conn->options->num_commands] = new_command;
2361            ++conn->options->num_commands;
2362            break;
2363        }
2364        case MYSQL_READ_DEFAULT_FILE:
2365        case MYSQL_READ_DEFAULT_GROUP:
2366#ifdef WHEN_SUPPORTED_BY_MYSQLI
2367        case MYSQL_SET_CLIENT_IP:
2368        case MYSQL_REPORT_DATA_TRUNCATION:
2369#endif
2370            /* currently not supported. Todo!! */
2371            break;
2372        case MYSQL_SET_CHARSET_NAME:
2373        {
2374            char * new_charset_name;
2375            if (!mysqlnd_find_charset_name(value)) {
2376                SET_CLIENT_ERROR(*conn->error_info, CR_CANT_FIND_CHARSET, UNKNOWN_SQLSTATE, "Unknown character set");
2377                ret = FAIL;
2378                break;
2379            }
2380
2381            new_charset_name = mnd_pestrdup(value, conn->persistent);
2382            if (!new_charset_name) {
2383                goto oom;
2384            }
2385            if (conn->options->charset_name) {
2386                mnd_pefree(conn->options->charset_name, conn->persistent);
2387            }
2388            conn->options->charset_name = new_charset_name;
2389            DBG_INF_FMT("charset=%s", conn->options->charset_name);
2390            break;
2391        }
2392        case MYSQL_OPT_NAMED_PIPE:
2393            conn->options->protocol = MYSQL_PROTOCOL_PIPE;
2394            break;
2395        case MYSQL_OPT_PROTOCOL:
2396            if (*(unsigned int*) value < MYSQL_PROTOCOL_LAST) {
2397                conn->options->protocol = *(unsigned int*) value;
2398            }
2399            break;
2400#ifdef WHEN_SUPPORTED_BY_MYSQLI
2401        case MYSQL_SET_CHARSET_DIR:
2402        case MYSQL_OPT_RECONNECT:
2403            /* we don't need external character sets, all character sets are
2404               compiled in. For compatibility we just ignore this setting.
2405               Same for protocol, we don't support old protocol */
2406        case MYSQL_OPT_USE_REMOTE_CONNECTION:
2407        case MYSQL_OPT_USE_EMBEDDED_CONNECTION:
2408        case MYSQL_OPT_GUESS_CONNECTION:
2409            /* todo: throw an error, we don't support embedded */
2410            break;
2411#endif
2412        case MYSQLND_OPT_MAX_ALLOWED_PACKET:
2413            if (*(unsigned int*) value > (1<<16)) {
2414                conn->options->max_allowed_packet = *(unsigned int*) value;
2415            }
2416            break;
2417        case MYSQLND_OPT_AUTH_PROTOCOL:
2418        {
2419            char * new_auth_protocol = value? mnd_pestrdup(value, conn->persistent) : NULL;
2420            if (value && !new_auth_protocol) {
2421                goto oom;
2422            }
2423            if (conn->options->auth_protocol) {
2424                mnd_pefree(conn->options->auth_protocol, conn->persistent);
2425            }
2426            conn->options->auth_protocol = new_auth_protocol;
2427            DBG_INF_FMT("auth_protocol=%s", conn->options->auth_protocol);
2428            break;
2429        }
2430        case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS:
2431            if (value && (*(unsigned int*) value) ? 1 : 0) {
2432                conn->options->flags |= CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
2433            } else {
2434                conn->options->flags &= ~CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
2435            }
2436            break;
2437        case MYSQL_OPT_CONNECT_ATTR_RESET:
2438            if (conn->options->connect_attr) {
2439                DBG_INF_FMT("Before reset %d attribute(s)", zend_hash_num_elements(conn->options->connect_attr));
2440                zend_hash_clean(conn->options->connect_attr);
2441            }
2442            break;
2443        case MYSQL_OPT_CONNECT_ATTR_DELETE:
2444            if (conn->options->connect_attr && value) {
2445                DBG_INF_FMT("Before delete %d attribute(s)", zend_hash_num_elements(conn->options->connect_attr));
2446                zend_hash_str_del(conn->options->connect_attr, value, strlen(value));
2447                DBG_INF_FMT("%d left", zend_hash_num_elements(conn->options->connect_attr));
2448            }
2449            break;
2450#ifdef WHEN_SUPPORTED_BY_MYSQLI
2451        case MYSQL_SHARED_MEMORY_BASE_NAME:
2452        case MYSQL_OPT_USE_RESULT:
2453        case MYSQL_SECURE_AUTH:
2454            /* not sure, todo ? */
2455#endif
2456        default:
2457            ret = FAIL;
2458    }
2459    conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2460    DBG_RETURN(ret);
2461oom:
2462    SET_OOM_ERROR(*conn->error_info);
2463    conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC);
2464end:
2465    DBG_RETURN(FAIL);
2466}
2467/* }}} */
2468
2469
2470/* {{{ mysqlnd_conn_data::set_client_option_2d */
2471static enum_func_status
2472MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d)(MYSQLND_CONN_DATA * const conn,
2473                                                        enum mysqlnd_option option,
2474                                                        const char * const key,
2475                                                        const char * const value
2476                                                        TSRMLS_DC)
2477{
2478    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_client_option_2d);
2479    enum_func_status ret = PASS;
2480    DBG_ENTER("mysqlnd_conn_data::set_client_option_2d");
2481    DBG_INF_FMT("conn=%llu option=%u", conn->thread_id, option);
2482
2483    if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2484        goto end;
2485    }
2486    switch (option) {
2487        case MYSQL_OPT_CONNECT_ATTR_ADD:
2488            if (!conn->options->connect_attr) {
2489                DBG_INF("Initializing connect_attr hash");
2490                conn->options->connect_attr = mnd_pemalloc(sizeof(HashTable), conn->persistent);
2491                if (!conn->options->connect_attr) {
2492                    goto oom;
2493                }
2494                zend_hash_init(conn->options->connect_attr, 0, NULL, ZVAL_PTR_DTOR, conn->persistent);
2495            }
2496            DBG_INF_FMT("Adding [%s][%s]", key, value);
2497            {
2498                zval attrz;
2499                ZVAL_NEW_STR(&attrz, zend_string_init(value, strlen(value), 1));
2500                zend_hash_str_update(conn->options->connect_attr, key, strlen(key), &attrz);
2501            }
2502            break;
2503        default:
2504            ret = FAIL;
2505    }
2506    conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2507    DBG_RETURN(ret);
2508oom:
2509    SET_OOM_ERROR(*conn->error_info);
2510    conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC);
2511end:
2512    DBG_RETURN(FAIL);
2513}
2514/* }}} */
2515
2516
2517/* {{{ mysqlnd_conn_data::use_result */
2518static MYSQLND_RES *
2519MYSQLND_METHOD(mysqlnd_conn_data, use_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC)
2520{
2521    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, use_result);
2522    MYSQLND_RES * result = NULL;
2523
2524    DBG_ENTER("mysqlnd_conn_data::use_result");
2525    DBG_INF_FMT("conn=%llu", conn->thread_id);
2526
2527    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2528        do {
2529            if (!conn->current_result) {
2530                break;
2531            }
2532
2533            /* Nothing to store for UPSERT/LOAD DATA */
2534            if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) {
2535                SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
2536                DBG_ERR("Command out of sync");
2537                break;
2538            }
2539
2540            MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_UNBUFFERED_SETS);
2541
2542            conn->current_result->conn = conn->m->get_reference(conn TSRMLS_CC);
2543            result = conn->current_result->m.use_result(conn->current_result, FALSE TSRMLS_CC);
2544
2545            if (!result) {
2546                conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC);
2547            }
2548            conn->current_result = NULL;
2549        } while (0);
2550
2551        conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC);
2552    }
2553
2554    DBG_RETURN(result);
2555}
2556/* }}} */
2557
2558
2559/* {{{ mysqlnd_conn_data::store_result */
2560static MYSQLND_RES *
2561MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC)
2562{
2563    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, store_result);
2564    MYSQLND_RES * result = NULL;
2565
2566    DBG_ENTER("mysqlnd_conn_data::store_result");
2567    DBG_INF_FMT("conn=%llu conn=%p", conn->thread_id, conn);
2568
2569    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2570        do {
2571            unsigned int f = flags;
2572            if (!conn->current_result) {
2573                break;
2574            }
2575
2576            /* Nothing to store for UPSERT/LOAD DATA*/
2577            if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != CONN_FETCHING_DATA) {
2578                SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
2579                DBG_ERR("Command out of sync");
2580                break;
2581            }
2582
2583            MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS);
2584
2585            /* overwrite */
2586            if ((conn->m->get_client_api_capabilities(conn TSRMLS_CC) & MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA)) {
2587                if (MYSQLND_G(fetch_data_copy)) {
2588                    f &= ~MYSQLND_STORE_NO_COPY;
2589                    f |= MYSQLND_STORE_COPY;
2590                }
2591            } else {
2592                /* if for some reason PDO borks something */
2593                if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) {
2594                    f |= MYSQLND_STORE_COPY;
2595                }
2596            }
2597            if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) {
2598                SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Unknown fetch mode");
2599                DBG_ERR("Unknown fetch mode");
2600                break;
2601            }
2602            result = conn->current_result->m.store_result(conn->current_result, conn, f TSRMLS_CC);
2603            if (!result) {
2604                conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC);
2605            }
2606            conn->current_result = NULL;
2607        } while (0);
2608
2609        conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC);
2610    }
2611    DBG_RETURN(result);
2612}
2613/* }}} */
2614
2615
2616/* {{{ mysqlnd_conn_data::get_connection_stats */
2617static void
2618MYSQLND_METHOD(mysqlnd_conn_data, get_connection_stats)(const MYSQLND_CONN_DATA * const conn,
2619                                                   zval * return_value TSRMLS_DC ZEND_FILE_LINE_DC)
2620{
2621    DBG_ENTER("mysqlnd_conn_data::get_connection_stats");
2622    mysqlnd_fill_stats_hash(conn->stats, mysqlnd_stats_values_names, return_value TSRMLS_CC ZEND_FILE_LINE_CC);
2623    DBG_VOID_RETURN;
2624}
2625/* }}} */
2626
2627
2628/* {{{ mysqlnd_conn_data::set_autocommit */
2629static enum_func_status
2630MYSQLND_METHOD(mysqlnd_conn_data, set_autocommit)(MYSQLND_CONN_DATA * conn, unsigned int mode TSRMLS_DC)
2631{
2632    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_autocommit);
2633    enum_func_status ret = FAIL;
2634    DBG_ENTER("mysqlnd_conn_data::set_autocommit");
2635
2636    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2637        ret = conn->m->query(conn, (mode) ? "SET AUTOCOMMIT=1":"SET AUTOCOMMIT=0", sizeof("SET AUTOCOMMIT=1") - 1 TSRMLS_CC);
2638        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2639    }
2640
2641    DBG_RETURN(ret);
2642}
2643/* }}} */
2644
2645
2646/* {{{ mysqlnd_conn_data::tx_commit */
2647static enum_func_status
2648MYSQLND_METHOD(mysqlnd_conn_data, tx_commit)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
2649{
2650    return conn->m->tx_commit_or_rollback(conn, TRUE, TRANS_COR_NO_OPT, NULL TSRMLS_CC);
2651}
2652/* }}} */
2653
2654
2655/* {{{ mysqlnd_conn_data::tx_rollback */
2656static enum_func_status
2657MYSQLND_METHOD(mysqlnd_conn_data, tx_rollback)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
2658{
2659    return conn->m->tx_commit_or_rollback(conn, FALSE, TRANS_COR_NO_OPT, NULL TSRMLS_CC);
2660}
2661/* }}} */
2662
2663
2664/* {{{ mysqlnd_tx_cor_options_to_string */
2665static void
2666MYSQLND_METHOD(mysqlnd_conn_data, tx_cor_options_to_string)(const MYSQLND_CONN_DATA * const conn, smart_str * str, const unsigned int mode TSRMLS_DC)
2667{
2668    if (mode & TRANS_COR_AND_CHAIN && !(mode & TRANS_COR_AND_NO_CHAIN)) {
2669        if (str->s && str->s->len) {
2670            smart_str_appendl(str, " ", sizeof(" ") - 1);
2671        }
2672        smart_str_appendl(str, "AND CHAIN", sizeof("AND CHAIN") - 1);
2673    } else if (mode & TRANS_COR_AND_NO_CHAIN && !(mode & TRANS_COR_AND_CHAIN)) {
2674        if (str->s && str->s->len) {
2675            smart_str_appendl(str, " ", sizeof(" ") - 1);
2676        }
2677        smart_str_appendl(str, "AND NO CHAIN", sizeof("AND NO CHAIN") - 1);
2678    }
2679
2680    if (mode & TRANS_COR_RELEASE && !(mode & TRANS_COR_NO_RELEASE)) {
2681        if (str->s && str->s->len) {
2682            smart_str_appendl(str, " ", sizeof(" ") - 1);
2683        }
2684        smart_str_appendl(str, "RELEASE", sizeof("RELEASE") - 1);
2685    } else if (mode & TRANS_COR_NO_RELEASE && !(mode & TRANS_COR_RELEASE)) {
2686        if (str->s && str->s->len) {
2687            smart_str_appendl(str, " ", sizeof(" ") - 1);
2688        }
2689        smart_str_appendl(str, "NO RELEASE", sizeof("NO RELEASE") - 1);
2690    }
2691    smart_str_0(str);
2692}
2693/* }}} */
2694
2695
2696/* {{{ mysqlnd_escape_string_for_tx_name_in_comment */
2697static char *
2698mysqlnd_escape_string_for_tx_name_in_comment(const char * const name TSRMLS_DC)
2699{
2700    char * ret = NULL;
2701    DBG_ENTER("mysqlnd_escape_string_for_tx_name_in_comment");
2702    if (name) {
2703        zend_bool warned = FALSE;
2704        const char * p_orig = name;
2705        char * p_copy;
2706        p_copy = ret = mnd_emalloc(strlen(name) + 1 + 2 + 2 + 1); /* space, open, close, NullS */
2707        *p_copy++ = ' ';
2708        *p_copy++ = '/';
2709        *p_copy++ = '*';
2710        while (1) {
2711            register char v = *p_orig;
2712            if (v == 0) {
2713                break;
2714            }
2715            if ((v >= '0' && v <= '9') ||
2716                (v >= 'a' && v <= 'z') ||
2717                (v >= 'A' && v <= 'Z') ||
2718                v == '-' ||
2719                v == '_' ||
2720                v == ' ' ||
2721                v == '=')
2722            {
2723                *p_copy++ = v;
2724            } else if (warned == FALSE) {
2725                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Transaction name truncated. Must be only [0-9A-Za-z\\-_=]+");
2726                warned = TRUE;
2727            }
2728            ++p_orig;
2729        }
2730        *p_copy++ = '*';
2731        *p_copy++ = '/';
2732        *p_copy++ = 0;
2733    }
2734    DBG_RETURN(ret);
2735}
2736/* }}} */
2737
2738
2739/* {{{ mysqlnd_conn_data::tx_commit_ex */
2740static enum_func_status
2741MYSQLND_METHOD(mysqlnd_conn_data, tx_commit_or_rollback)(MYSQLND_CONN_DATA * conn, const zend_bool commit, const unsigned int flags, const char * const name TSRMLS_DC)
2742{
2743    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_commit_or_rollback);
2744    enum_func_status ret = FAIL;
2745    DBG_ENTER("mysqlnd_conn_data::tx_commit_or_rollback");
2746
2747    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2748        do {
2749            smart_str tmp_str = {0, 0};
2750            conn->m->tx_cor_options_to_string(conn, &tmp_str, flags TSRMLS_CC);
2751            smart_str_0(&tmp_str);
2752
2753
2754            {
2755                char * query;
2756                size_t query_len;
2757                char * name_esc = mysqlnd_escape_string_for_tx_name_in_comment(name TSRMLS_CC);
2758
2759                query_len = mnd_sprintf(&query, 0, (commit? "COMMIT%s %s":"ROLLBACK%s %s"),
2760                                        name_esc? name_esc:"", tmp_str.s? tmp_str.s->val:"");
2761                smart_str_free(&tmp_str);
2762                if (name_esc) {
2763                    mnd_efree(name_esc);
2764                    name_esc = NULL;
2765                }
2766                if (!query) {
2767                    SET_OOM_ERROR(*conn->error_info);
2768                    break;
2769                }
2770
2771                ret = conn->m->query(conn, query, query_len TSRMLS_CC);
2772                mnd_sprintf_free(query);
2773            }
2774        } while (0);
2775        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2776    }
2777
2778    DBG_RETURN(ret);
2779}
2780/* }}} */
2781
2782
2783/* {{{ mysqlnd_conn_data::tx_begin */
2784static enum_func_status
2785MYSQLND_METHOD(mysqlnd_conn_data, tx_begin)(MYSQLND_CONN_DATA * conn, const unsigned int mode, const char * const name TSRMLS_DC)
2786{
2787    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_begin);
2788    enum_func_status ret = FAIL;
2789    DBG_ENTER("mysqlnd_conn_data::tx_begin");
2790
2791    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2792        do {
2793            smart_str tmp_str = {0, 0};
2794            if (mode & TRANS_START_WITH_CONSISTENT_SNAPSHOT) {
2795                if (tmp_str.s) {
2796                    smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1);
2797                }
2798                smart_str_appendl(&tmp_str, "WITH CONSISTENT SNAPSHOT", sizeof("WITH CONSISTENT SNAPSHOT") - 1);
2799            }
2800            if (mode & (TRANS_START_READ_WRITE | TRANS_START_READ_ONLY)) {
2801                zend_ulong server_version = conn->m->get_server_version(conn TSRMLS_CC);
2802                if (server_version < 50605L) {
2803                    php_error_docref(NULL TSRMLS_CC, E_WARNING, "This server version doesn't support 'READ WRITE' and 'READ ONLY'. Minimum 5.6.5 is required");
2804                    smart_str_free(&tmp_str);
2805                    break;
2806                } else if (mode & TRANS_START_READ_WRITE) {
2807                    if (tmp_str.s && tmp_str.s->len) {
2808                        smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1);
2809                    }
2810                    smart_str_appendl(&tmp_str, "READ WRITE", sizeof("READ WRITE") - 1);
2811                } else if (mode & TRANS_START_READ_ONLY) {
2812                    if (tmp_str.s && tmp_str.s->len) {
2813                        smart_str_appendl(&tmp_str, ", ", sizeof(", ") - 1);
2814                    }
2815                    smart_str_appendl(&tmp_str, "READ ONLY", sizeof("READ ONLY") - 1);
2816                }
2817            }
2818            smart_str_0(&tmp_str);
2819
2820            {
2821                char * name_esc = mysqlnd_escape_string_for_tx_name_in_comment(name TSRMLS_CC);
2822                char * query;
2823                unsigned int query_len = mnd_sprintf(&query, 0, "START TRANSACTION%s %s", name_esc? name_esc:"", tmp_str.s? tmp_str.s->val:"");
2824                smart_str_free(&tmp_str);
2825                if (name_esc) {
2826                    mnd_efree(name_esc);
2827                    name_esc = NULL;
2828                }
2829                if (!query) {
2830                    SET_OOM_ERROR(*conn->error_info);
2831                    break;
2832                }
2833                ret = conn->m->query(conn, query, query_len TSRMLS_CC);
2834                mnd_sprintf_free(query);
2835            }
2836        } while (0);
2837        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2838    }
2839
2840    DBG_RETURN(ret);
2841}
2842/* }}} */
2843
2844
2845/* {{{ mysqlnd_conn_data::tx_savepoint */
2846static enum_func_status
2847MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint)(MYSQLND_CONN_DATA * conn, const char * const name TSRMLS_DC)
2848{
2849    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_savepoint);
2850    enum_func_status ret = FAIL;
2851    DBG_ENTER("mysqlnd_conn_data::tx_savepoint");
2852
2853    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2854        do {
2855            char * query;
2856            unsigned int query_len;
2857            if (!name) {
2858                SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Savepoint name not provided");
2859                break;
2860            }
2861            query_len = mnd_sprintf(&query, 0, "SAVEPOINT `%s`", name);
2862            if (!query) {
2863                SET_OOM_ERROR(*conn->error_info);
2864                break;
2865            }
2866            ret = conn->m->query(conn, query, query_len TSRMLS_CC);
2867            mnd_sprintf_free(query);
2868        } while (0);
2869        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2870    }
2871
2872    DBG_RETURN(ret);
2873}
2874/* }}} */
2875
2876
2877/* {{{ mysqlnd_conn_data::tx_savepoint_release */
2878static enum_func_status
2879MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint_release)(MYSQLND_CONN_DATA * conn, const char * const name TSRMLS_DC)
2880{
2881    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_savepoint_release);
2882    enum_func_status ret = FAIL;
2883    DBG_ENTER("mysqlnd_conn_data::tx_savepoint_release");
2884
2885    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
2886        do {
2887            char * query;
2888            unsigned int query_len;
2889            if (!name) {
2890                SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Savepoint name not provided");
2891                break;
2892            }
2893            query_len = mnd_sprintf(&query, 0, "RELEASE SAVEPOINT `%s`", name);
2894            if (!query) {
2895                SET_OOM_ERROR(*conn->error_info);
2896                break;
2897            }
2898            ret = conn->m->query(conn, query, query_len TSRMLS_CC);
2899            mnd_sprintf_free(query);
2900        } while (0);
2901        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
2902    }
2903
2904    DBG_RETURN(ret);
2905}
2906/* }}} */
2907
2908
2909/* {{{ mysqlnd_conn_data::negotiate_client_api_capabilities */
2910static unsigned int
2911MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC)
2912{
2913    unsigned int ret = 0;
2914    DBG_ENTER("mysqlnd_conn_data::negotiate_client_api_capabilities");
2915    if (conn) {
2916        ret = conn->client_api_capabilities;
2917        conn->client_api_capabilities = flags;
2918    }
2919
2920    DBG_RETURN(ret);
2921}
2922/* }}} */
2923
2924
2925/* {{{ mysqlnd_conn_data::get_client_api_capabilities */
2926static unsigned int
2927MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC)
2928{
2929    DBG_ENTER("mysqlnd_conn_data::get_client_api_capabilities");
2930    DBG_RETURN(conn? conn->client_api_capabilities : 0);
2931}
2932/* }}} */
2933
2934
2935/* {{{ mysqlnd_conn_data::local_tx_start */
2936static enum_func_status
2937MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start)(MYSQLND_CONN_DATA * conn, size_t this_func TSRMLS_DC)
2938{
2939    enum_func_status ret = PASS;
2940    DBG_ENTER("mysqlnd_conn_data::local_tx_start");
2941    DBG_RETURN(ret);
2942}
2943/* }}} */
2944
2945
2946/* {{{ mysqlnd_conn_data::local_tx_end */
2947static enum_func_status
2948MYSQLND_METHOD(mysqlnd_conn_data, local_tx_end)(MYSQLND_CONN_DATA * conn, size_t this_func, enum_func_status status TSRMLS_DC)
2949{
2950    DBG_ENTER("mysqlnd_conn_data::local_tx_end");
2951    DBG_RETURN(status);
2952}
2953/* }}} */
2954
2955
2956/* {{{ mysqlnd_conn_data::init */
2957static enum_func_status
2958MYSQLND_METHOD(mysqlnd_conn_data, init)(MYSQLND_CONN_DATA * conn TSRMLS_DC)
2959{
2960    DBG_ENTER("mysqlnd_conn_data::init");
2961    mysqlnd_stats_init(&conn->stats, STAT_LAST);
2962    SET_ERROR_AFF_ROWS(conn);
2963
2964    conn->net = mysqlnd_net_init(conn->persistent, conn->stats, conn->error_info TSRMLS_CC);
2965    conn->protocol = mysqlnd_protocol_init(conn->persistent TSRMLS_CC);
2966
2967    DBG_RETURN(conn->stats && conn->net && conn->protocol? PASS:FAIL);
2968}
2969/* }}} */
2970
2971
2972MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC);
2973
2974
2975MYSQLND_CLASS_METHODS_START(mysqlnd_conn_data)
2976    MYSQLND_METHOD(mysqlnd_conn_data, init),
2977    MYSQLND_METHOD(mysqlnd_conn_data, connect),
2978
2979    MYSQLND_METHOD(mysqlnd_conn_data, escape_string),
2980    MYSQLND_METHOD(mysqlnd_conn_data, set_charset),
2981    MYSQLND_METHOD(mysqlnd_conn_data, query),
2982    MYSQLND_METHOD(mysqlnd_conn_data, send_query),
2983    MYSQLND_METHOD(mysqlnd_conn_data, reap_query),
2984    MYSQLND_METHOD(mysqlnd_conn_data, use_result),
2985    MYSQLND_METHOD(mysqlnd_conn_data, store_result),
2986    MYSQLND_METHOD(mysqlnd_conn_data, next_result),
2987    MYSQLND_METHOD(mysqlnd_conn_data, more_results),
2988
2989    _mysqlnd_stmt_init,
2990
2991    MYSQLND_METHOD(mysqlnd_conn_data, shutdown),
2992    MYSQLND_METHOD(mysqlnd_conn_data, refresh),
2993
2994    MYSQLND_METHOD(mysqlnd_conn_data, ping),
2995    MYSQLND_METHOD(mysqlnd_conn_data, kill),
2996    MYSQLND_METHOD(mysqlnd_conn_data, select_db),
2997    MYSQLND_METHOD(mysqlnd_conn_data, dump_debug_info),
2998    MYSQLND_METHOD(mysqlnd_conn_data, change_user),
2999
3000    MYSQLND_METHOD(mysqlnd_conn_data, errno),
3001    MYSQLND_METHOD(mysqlnd_conn_data, error),
3002    MYSQLND_METHOD(mysqlnd_conn_data, sqlstate),
3003    MYSQLND_METHOD(mysqlnd_conn_data, thread_id),
3004
3005    MYSQLND_METHOD(mysqlnd_conn_data, get_connection_stats),
3006
3007    MYSQLND_METHOD(mysqlnd_conn_data, get_server_version),
3008    MYSQLND_METHOD(mysqlnd_conn_data, get_server_info),
3009    MYSQLND_METHOD(mysqlnd_conn_data, statistic),
3010    MYSQLND_METHOD(mysqlnd_conn_data, get_host_info),
3011    MYSQLND_METHOD(mysqlnd_conn_data, get_proto_info),
3012    MYSQLND_METHOD(mysqlnd_conn_data, info),
3013    MYSQLND_METHOD(mysqlnd_conn_data, charset_name),
3014    MYSQLND_METHOD(mysqlnd_conn_data, list_fields),
3015    MYSQLND_METHOD(mysqlnd_conn_data, list_method),
3016
3017    MYSQLND_METHOD(mysqlnd_conn_data, insert_id),
3018    MYSQLND_METHOD(mysqlnd_conn_data, affected_rows),
3019    MYSQLND_METHOD(mysqlnd_conn_data, warning_count),
3020    MYSQLND_METHOD(mysqlnd_conn_data, field_count),
3021
3022    MYSQLND_METHOD(mysqlnd_conn_data, server_status),
3023
3024    MYSQLND_METHOD(mysqlnd_conn_data, set_server_option),
3025    MYSQLND_METHOD(mysqlnd_conn_data, set_client_option),
3026    MYSQLND_METHOD(mysqlnd_conn_data, free_contents),
3027    MYSQLND_METHOD(mysqlnd_conn_data, free_options),
3028
3029    MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, dtor),
3030
3031    mysqlnd_query_read_result_set_header,
3032
3033    MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_reference),
3034    MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, free_reference),
3035    MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_state),
3036    MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, set_state),
3037
3038    MYSQLND_METHOD(mysqlnd_conn_data, simple_command),
3039    MYSQLND_METHOD(mysqlnd_conn_data, simple_command_handle_response),
3040    MYSQLND_METHOD(mysqlnd_conn_data, restart_psession),
3041    MYSQLND_METHOD(mysqlnd_conn_data, end_psession),
3042    MYSQLND_METHOD(mysqlnd_conn_data, send_close),
3043
3044    MYSQLND_METHOD(mysqlnd_conn_data, ssl_set),
3045    mysqlnd_result_init,
3046    MYSQLND_METHOD(mysqlnd_conn_data, set_autocommit),
3047    MYSQLND_METHOD(mysqlnd_conn_data, tx_commit),
3048    MYSQLND_METHOD(mysqlnd_conn_data, tx_rollback),
3049    MYSQLND_METHOD(mysqlnd_conn_data, tx_begin),
3050    MYSQLND_METHOD(mysqlnd_conn_data, tx_commit_or_rollback),
3051    MYSQLND_METHOD(mysqlnd_conn_data, tx_cor_options_to_string),
3052    MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint),
3053    MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint_release),
3054
3055    MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start),
3056    MYSQLND_METHOD(mysqlnd_conn_data, local_tx_end),
3057    MYSQLND_METHOD(mysqlnd_conn_data, execute_init_commands),
3058    MYSQLND_METHOD(mysqlnd_conn_data, get_updated_connect_flags),
3059    MYSQLND_METHOD(mysqlnd_conn_data, connect_handshake),
3060    MYSQLND_METHOD(mysqlnd_conn_data, simple_command_send_request),
3061    MYSQLND_METHOD(mysqlnd_conn_data, fetch_auth_plugin_by_name),
3062
3063    MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d),
3064
3065    MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities),
3066    MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities)
3067MYSQLND_CLASS_METHODS_END;
3068
3069
3070/* {{{ mysqlnd_conn::get_reference */
3071static MYSQLND *
3072MYSQLND_METHOD(mysqlnd_conn, clone_object)(MYSQLND * const conn TSRMLS_DC)
3073{
3074    MYSQLND * ret;
3075    DBG_ENTER("mysqlnd_conn::get_reference");
3076    ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).clone_connection_object(conn TSRMLS_CC);
3077    DBG_RETURN(ret);
3078}
3079/* }}} */
3080
3081
3082/* {{{ mysqlnd_conn_data::dtor */
3083static void
3084MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND * conn TSRMLS_DC)
3085{
3086    DBG_ENTER("mysqlnd_conn::dtor");
3087    DBG_INF_FMT("conn=%llu", conn->data->thread_id);
3088
3089    conn->data->m->free_reference(conn->data TSRMLS_CC);
3090
3091    mnd_pefree(conn, conn->persistent);
3092
3093    DBG_VOID_RETURN;
3094}
3095/* }}} */
3096
3097
3098/* {{{ mysqlnd_conn_data::close */
3099static enum_func_status
3100MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn_handle, enum_connection_close_type close_type TSRMLS_DC)
3101{
3102    size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_methods, close);
3103    MYSQLND_CONN_DATA * conn = conn_handle->data;
3104    enum_func_status ret = FAIL;
3105
3106    DBG_ENTER("mysqlnd_conn::close");
3107    DBG_INF_FMT("conn=%llu", conn->thread_id);
3108
3109    if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) {
3110        if (CONN_GET_STATE(conn) >= CONN_READY) {
3111            static enum_mysqlnd_collected_stats close_type_to_stat_map[MYSQLND_CLOSE_LAST] = {
3112                STAT_CLOSE_EXPLICIT,
3113                STAT_CLOSE_IMPLICIT,
3114                STAT_CLOSE_DISCONNECT
3115            };
3116            MYSQLND_INC_CONN_STATISTIC(conn->stats, close_type_to_stat_map[close_type]);
3117        }
3118
3119        /*
3120          Close now, free_reference will try,
3121          if we are last, but that's not a problem.
3122        */
3123        ret = conn->m->send_close(conn TSRMLS_CC);
3124
3125        /* do it after free_reference/dtor and we might crash */
3126        conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC);
3127
3128        conn_handle->m->dtor(conn_handle TSRMLS_CC);
3129    }
3130    DBG_RETURN(ret);
3131}
3132/* }}} */
3133
3134
3135MYSQLND_CLASS_METHODS_START(mysqlnd_conn)
3136    MYSQLND_METHOD(mysqlnd_conn, connect),
3137    MYSQLND_METHOD(mysqlnd_conn, clone_object),
3138    MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor),
3139    MYSQLND_METHOD(mysqlnd_conn, close)
3140MYSQLND_CLASS_METHODS_END;
3141
3142
3143/* {{{ _mysqlnd_init */
3144PHPAPI MYSQLND *
3145_mysqlnd_init(unsigned int flags, zend_bool persistent TSRMLS_DC)
3146{
3147    MYSQLND * ret;
3148    DBG_ENTER("mysqlnd_init");
3149    ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_connection(persistent TSRMLS_CC);
3150    if (ret && ret->data) {
3151        ret->data->m->negotiate_client_api_capabilities(ret->data, flags TSRMLS_CC);
3152    }
3153    DBG_RETURN(ret);
3154}
3155/* }}} */
3156
3157/*
3158 * Local variables:
3159 * tab-width: 4
3160 * c-basic-offset: 4
3161 * End:
3162 * vim600: noet sw=4 ts=4 fdm=marker
3163 * vim<600: noet sw=4 ts=4
3164 */
3165