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