1 /*
2 * mod_mariadb for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2019, Andrey Volk <andywolk@gmail.com>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is ported from FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Anthony Minessale II <anthm@freeswitch.org>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 * Andrey Volk <andywolk@gmail.com>
26 *
27 * mod_mariadb.c -- MariaDB (MySQL) FreeSWITCH module
28 *
29 */
30
31 #include <switch.h>
32
33 #ifndef _WIN32
34 #include <poll.h>
35 #else
36 #include <winsock2.h>
37 #endif
38
39 #include <mysql.h>
40
41 #include "mariadb_dsn.hpp"
42
43 switch_loadable_module_interface_t *MODULE_INTERFACE;
44
45 SWITCH_MODULE_LOAD_FUNCTION(mod_mariadb_load);
46 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_mariadb_shutdown);
47 SWITCH_MODULE_DEFINITION(mod_mariadb, mod_mariadb_load, mod_mariadb_shutdown, NULL);
48
49 MYSQL *mariadb_dsn_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd,
50 const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag);
51 void mariadb_dsn_close(MYSQL *mysql);
52 int mariadb_db_set_connection(MYSQL *mysql, enum enum_server_command command, const char *arg,
53 size_t length, my_bool skipp_check, void *opt_arg);
54 my_bool mariadb_db_dsn_reconnect(MYSQL *mysql);
55
56 my_bool reconnect = 1;
57
58 #define DEFAULT_MARIADB_RETRIES 120
59
60 #ifndef MIN
61 #define MIN(a,b) (((a) < (b)) ? (a) : (b))
62 #endif
63
64 typedef enum {
65 MARIADB_STATE_INIT,
66 MARIADB_STATE_DOWN,
67 MARIADB_STATE_CONNECTED,
68 MARIADB_STATE_ERROR
69 } mariadb_state_t;
70
71 struct mariadb_handle {
72 char *dsn;
73 char *sql;
74 MYSQL con;
75 my_socket sock;
76 mariadb_state_t state;
77 int affected_rows;
78 int num_retries;
79 switch_bool_t auto_commit;
80 switch_bool_t in_txn;
81 int stored_results;
82 };
83
84 typedef struct mariadb_handle mariadb_handle_t;
85
86 struct mariadb_result {
87 MYSQL_RES *result;
88 char *err;
89 int rows;
90 int cols;
91 };
92
93 typedef struct mariadb_result mariadb_result_t;
94
95 switch_status_t mariadb_handle_destroy(switch_database_interface_handle_t **dih);
96 switch_status_t mariadb_flush(mariadb_handle_t *handle);
97 static int db_is_up(mariadb_handle_t *handle);
98 static char *mariadb_handle_get_error(mariadb_handle_t *handle);
99
100 #define mariadb_handle_exec_base(handle, sql, err) mariadb_handle_exec_base_detailed(__FILE__, (char * )__SWITCH_FUNC__, __LINE__, handle, sql, err)
101 #define mariadb_next_result(h, r) mariadb_next_result_timed(h, r, 10000)
102 #define mariadb_finish_results(handle) mariadb_finish_results_real(__FILE__, (char * )__SWITCH_FUNC__, __LINE__, handle)
103
104 static char *supported_prefixes[4] = { 0 };
105
wait_for_mysql(mariadb_handle_t * handle,int status,int msec)106 static int wait_for_mysql(mariadb_handle_t *handle, int status, int msec)
107 {
108 int res = -1;
109 #ifdef _WIN32
110 /*
111 On Windows, select() must be used due to a bug in WSAPoll()
112 which is supposed to be identical to BSD's poll(), but it is not,
113 "Windows 8 Bugs 309411 � WSAPoll does not report failed connections":
114 https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/18769abd-fca0-4d3c-9884-1a38ce27ae90/wsapoll-and-nonblocking-connects-to-nonexistent-ports?forum=wsk
115 */
116
117 fd_set rs, ws, es;
118 struct timeval tv, *timeout = NULL;
119
120 FD_ZERO(&rs);
121 FD_ZERO(&ws);
122 FD_ZERO(&es);
123
124 if (status & MYSQL_WAIT_READ) FD_SET(handle->sock, &rs);
125 if (status & MYSQL_WAIT_WRITE) FD_SET(handle->sock, &ws);
126 if (status & MYSQL_WAIT_EXCEPT) FD_SET(handle->sock, &es);
127
128 if (status & MYSQL_WAIT_TIMEOUT) {
129 tv.tv_sec = MIN(mysql_get_timeout_value(&handle->con), (unsigned int)msec * 1000);
130 tv.tv_usec = 0;
131 timeout = &tv;
132 }
133
134 res = select(1, &rs, &ws, &es, timeout);
135 #else
136 struct pollfd pfd = { 0 };
137 int timeout = -1;
138
139 pfd.fd = handle->sock;
140 pfd.events |= (status & MYSQL_WAIT_READ ? POLLIN : 0);
141 pfd.events |= (status & MYSQL_WAIT_WRITE ? POLLOUT : 0);
142 pfd.events |= (status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0);
143
144 if (status & MYSQL_WAIT_TIMEOUT) {
145 timeout = MIN(mysql_get_timeout_value(&handle->con), (unsigned int)msec * 1000);
146 }
147
148 do {
149 res = poll(&pfd, 1, timeout);
150 } while (res == -1 && errno == EINTR);
151 #endif
152
153 if (res == 0) goto timeout;
154 else if (res < 0) goto error;
155
156 status = 0;
157
158 #ifdef _WIN32
159 if (FD_ISSET(handle->sock, &rs)) status |= MYSQL_WAIT_READ;
160 if (FD_ISSET(handle->sock, &ws)) status |= MYSQL_WAIT_WRITE;
161 if (FD_ISSET(handle->sock, &es)) status |= MYSQL_WAIT_EXCEPT;
162 #else
163 if (pfd.revents & POLLIN) status |= MYSQL_WAIT_READ;
164 if (pfd.revents & POLLOUT) status |= MYSQL_WAIT_WRITE;
165 if (pfd.revents & POLLPRI) status |= MYSQL_WAIT_EXCEPT;
166 #endif
167
168 return status;
169
170 error:
171 // en error
172 timeout:
173 return MYSQL_WAIT_TIMEOUT;
174 }
175
176
db_is_up(mariadb_handle_t * handle)177 static int db_is_up(mariadb_handle_t *handle)
178 {
179 int ret = 0;
180 switch_event_t *event;
181 char *err_str = NULL;
182 int max_tries = DEFAULT_MARIADB_RETRIES;
183 int code = 0, recon = 0;
184
185 if (handle) {
186 max_tries = handle->num_retries;
187 if (max_tries < 1)
188 max_tries = DEFAULT_MARIADB_RETRIES;
189 }
190
191 top:
192
193 if (!handle) {
194 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "No DB Handle\n");
195 goto done;
196 }
197
198 if (mysql_ping(&handle->con) != 0) {
199 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "mysql_ping returned bad connection [Error: %s]; reconnecting...\n", mysql_error(&handle->con));
200 handle->state = MARIADB_STATE_ERROR;
201 if (mariadb_reconnect(&handle->con) != 0) {
202 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "mariadb_reconnect returned bad connection -- reconnection failed! [Error: %s]\n", mysql_error(&handle->con));
203 goto error;
204 }
205 handle->state = MARIADB_STATE_CONNECTED;
206 handle->sock = mysql_get_socket(&handle->con);
207 }
208
209 ret = 1;
210 goto done;
211
212 error:
213 err_str = mariadb_handle_get_error(handle);
214
215 if (mysql_ping(&handle->con) != 0) {
216 handle->state = MARIADB_STATE_ERROR;
217 if (mariadb_reconnect(&handle->con) == 0) {
218 handle->state = MARIADB_STATE_CONNECTED;
219 recon = SWITCH_STATUS_SUCCESS;
220 handle->sock = mysql_get_socket(&handle->con);
221 }
222 }
223
224 max_tries--;
225
226 if (switch_event_create(&event, SWITCH_EVENT_TRAP) == SWITCH_STATUS_SUCCESS) {
227 switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Failure-Message", "The sql server is not responding for DSN %s [%s][%d]",
228 switch_str_nil(handle->dsn), switch_str_nil(err_str), code);
229 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "The sql server is not responding for DSN %s [%s][%d]\n",
230 switch_str_nil(handle->dsn), switch_str_nil(err_str), code);
231
232 if (recon == SWITCH_STATUS_SUCCESS) {
233 switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Additional-Info", "The connection has been re-established");
234 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "The connection has been re-established\n");
235 } else {
236 switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Additional-Info", "The connection could not be re-established");
237 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "The connection could not be re-established\n");
238 }
239 if (!max_tries) {
240 switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Additional-Info", "Giving up!");
241 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Giving up!\n");
242 }
243
244 switch_event_fire(&event);
245 }
246
247 if (!max_tries) {
248 goto done;
249 }
250
251 switch_safe_free(err_str);
252 switch_yield(1000000);
253 goto top;
254
255 done:
256
257 switch_safe_free(err_str);
258
259 return ret;
260 }
261
mariadb_SQLEndTran(mariadb_handle_t * handle,switch_bool_t commit)262 switch_status_t mariadb_SQLEndTran(mariadb_handle_t *handle, switch_bool_t commit)
263 {
264 char * err_str = NULL;
265
266 if (!handle)
267 return SWITCH_STATUS_FALSE;
268
269 if (commit) {
270 char *sql = "COMMIT";
271 handle->stored_results = 0;
272 if (mysql_query(&handle->con, sql)) {
273 err_str = mariadb_handle_get_error(handle);
274 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not commit transaction: %s\n", err_str);
275 switch_safe_free(err_str);
276 return SWITCH_STATUS_FALSE;
277 }
278 } else {
279 char *sql = "ROLLBACK";
280 handle->stored_results = 0;
281 if (mysql_query(&handle->con, sql)) {
282 err_str = mariadb_handle_get_error(handle);
283 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not rollback transaction: %s\n", err_str);
284 switch_safe_free(err_str);
285 return SWITCH_STATUS_FALSE;
286 }
287 }
288 handle->in_txn = SWITCH_FALSE;
289
290 return SWITCH_STATUS_SUCCESS;
291 }
292
mariadb_next_result_timed(mariadb_handle_t * handle,mariadb_result_t ** result_out,int msec)293 switch_status_t mariadb_next_result_timed(mariadb_handle_t *handle, mariadb_result_t **result_out, int msec)
294 {
295 int status = 0;
296 mariadb_result_t *res;
297 switch_time_t start;
298 switch_time_t ctime;
299 unsigned int usec = msec * 1000;
300 char *err_str;
301
302 if (!handle) {
303 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "**BUG** Null handle passed to mariadb_next_result.\n");
304 return SWITCH_STATUS_FALSE;
305 }
306
307 if (handle->stored_results) {
308 if ( (status = mysql_next_result(&handle->con)) ) {
309 if (status > 0) {
310 err_str = mariadb_handle_get_error(handle);
311 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "An error occurred trying to get next for query (%s): %s\n", handle->sql, err_str);
312 switch_safe_free(err_str);
313
314 goto error;
315 }
316
317 return SWITCH_STATUS_SUCCESS;
318 }
319 }
320
321 if (!(res = malloc(sizeof(mariadb_result_t)))) {
322 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Malloc failed!\n");
323 goto error;
324 }
325
326 memset(res, 0, sizeof(mariadb_result_t));
327
328 if ( (status = mysql_store_result_start(&res->result, &handle->con)) ) {
329 /* Wait for a result to become available, up to msec milliseconds */
330 start = switch_micro_time_now();
331 while ((ctime = switch_micro_time_now()) - start <= usec) {
332 int wait_time = (usec - (unsigned int)(ctime - start)) / 1000;
333
334 /* Wait for the mariadb socket to be ready for data reads. */
335 status = wait_for_mysql(handle, status, wait_time);
336 status = mysql_store_result_cont(&res->result, &handle->con, status);
337
338 if (!status)
339 break;
340 }
341 }
342
343 /* At this point, we know we can read a full result without blocking. */
344
345 if (res->result) {
346 *result_out = res;
347
348 res->rows = (int)mysql_num_rows(res->result);
349 handle->affected_rows = res->rows;
350 handle->stored_results++;
351 res->cols = mysql_num_fields(res->result);
352 } else {
353 if (mysql_field_count(&handle->con) != 0) {
354 // mysql_store_result_() should have returned data
355
356 err_str = mariadb_handle_get_error(handle);
357 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "An error occurred trying to use result for query (%s): %s\n", handle->sql, err_str);
358 switch_safe_free(err_str);
359 }
360
361 free(res);
362 res = NULL;
363 *result_out = NULL;
364 }
365
366 return SWITCH_STATUS_SUCCESS;
367
368 error:
369
370 /* Make sure the failed connection does not have any transactions marked as in progress */
371 mariadb_flush(handle);
372
373 /* Try to reconnect to the DB if we were dropped */
374 db_is_up(handle);
375
376 return SWITCH_STATUS_FALSE;
377 }
378
mariadb_free_result(mariadb_result_t ** result)379 void mariadb_free_result(mariadb_result_t **result)
380 {
381 if (!*result) {
382 return;
383 }
384
385 if ((*result)->result) {
386 mysql_free_result((*result)->result);
387 }
388
389 free(*result);
390 *result = NULL;
391 }
392
mariadb_finish_results_real(const char * file,const char * func,int line,mariadb_handle_t * handle)393 switch_status_t mariadb_finish_results_real(const char* file, const char* func, int line, mariadb_handle_t *handle)
394 {
395 char *err_str;
396 mariadb_result_t *res = NULL;
397 switch_status_t final_status = SWITCH_STATUS_SUCCESS;
398 int done = 0, status;
399
400 do {
401 mariadb_next_result(handle, &res);
402 if (res && res->err && !switch_stristr("already exists", res->err) && !switch_stristr("duplicate key name", res->err)) {
403 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "Error executing query:\n%s\n", res->err);
404 final_status = SWITCH_STATUS_FALSE;
405 }
406
407 if (!res) {
408 if (!mysql_more_results(&handle->con)) {
409 done = 1;
410 } else {
411 if ((status = mysql_next_result(&handle->con))) {
412 if (status > 0) {
413 err_str = mariadb_handle_get_error(handle);
414 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "An error occurred trying to get next for query (%s): %s\n", handle->sql, err_str);
415 switch_safe_free(err_str);
416
417 break;
418 }
419 }
420 }
421 } else if (res->result) {
422 handle->affected_rows = (int)mysql_affected_rows(&handle->con);
423 }
424
425 mariadb_free_result(&res);
426 } while (!done);
427
428 return final_status;
429 }
430
mariadb_handle_disconnect(mariadb_handle_t * handle)431 switch_status_t mariadb_handle_disconnect(mariadb_handle_t *handle)
432 {
433 if (!handle) {
434 return SWITCH_STATUS_FALSE;
435 }
436
437 if (handle->state == MARIADB_STATE_CONNECTED) {
438 mysql_close(&handle->con);
439 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "Disconnected from [%s]\n", handle->dsn);
440 }
441 switch_safe_free(handle->sql);
442 handle->state = MARIADB_STATE_DOWN;
443
444 return SWITCH_STATUS_SUCCESS;
445 }
446
mariadb_handle_get_error(mariadb_handle_t * handle)447 static char * mariadb_handle_get_error(mariadb_handle_t *handle)
448 {
449 char *err_str = NULL;
450
451 if (!handle) {
452 return NULL;
453 }
454
455 switch_strdup(err_str, mysql_error(&handle->con));
456
457 return err_str;
458 }
459
mariadb_handle_connect(mariadb_handle_t * handle)460 switch_status_t mariadb_handle_connect(mariadb_handle_t *handle)
461 {
462 if (!handle) {
463 return SWITCH_STATUS_FALSE;
464 }
465
466 if (handle->state == MARIADB_STATE_CONNECTED) {
467 mariadb_handle_disconnect(handle);
468 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Re-connecting %s\n", handle->dsn);
469 }
470
471 if (handle->state == MARIADB_STATE_CONNECTED) {
472 mariadb_handle_disconnect(handle);
473 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Re-connecting %s\n", handle->dsn);
474 }
475
476 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Connecting %s\n", handle->dsn);
477 mysql_init(&handle->con);
478
479 /* Enable non-blocking operation */
480 /* https://mariadb.com/kb/en/library/using-the-non-blocking-library */
481 mysql_options(&handle->con, MYSQL_OPT_NONBLOCK, 0);
482
483 /* Enable automatic reconnect with the mariadb_reconnect function, without this that function does not work */
484 mysql_options(&handle->con, MYSQL_OPT_RECONNECT, &reconnect);
485
486 /* set timeouts to 300 microseconds */
487 /*int default_timeout = 3;
488 mysql_options(&handle->con, MYSQL_OPT_READ_TIMEOUT, &default_timeout);
489 mysql_options(&handle->con, MYSQL_OPT_CONNECT_TIMEOUT, &default_timeout);
490 mysql_options(&handle->con, MYSQL_OPT_WRITE_TIMEOUT, &default_timeout);*/
491
492 if (!mysql_dsn_connect(&handle->con, handle->dsn, CLIENT_MULTI_STATEMENTS)) {
493 char *err_str;
494 if ((err_str = mariadb_handle_get_error(handle))) {
495 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s\n", err_str);
496 switch_safe_free(err_str);
497 } else {
498 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to connect to the database [%s]\n", handle->dsn);
499 mariadb_handle_disconnect(handle);
500 }
501 return SWITCH_STATUS_FALSE;
502 }
503
504 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "Connected to [%s]\n", handle->dsn);
505 handle->state = MARIADB_STATE_CONNECTED;
506 handle->sock = mysql_get_socket(&handle->con);
507
508 return SWITCH_STATUS_SUCCESS;
509 }
510
mariadb_handle_new(switch_cache_db_database_interface_options_t database_interface_options,switch_database_interface_handle_t ** dih)511 switch_status_t mariadb_handle_new(switch_cache_db_database_interface_options_t database_interface_options, switch_database_interface_handle_t **dih)
512 {
513 mariadb_handle_t *new_handle = NULL;
514
515 if (!(*dih = malloc(sizeof(**dih)))) {
516 goto err;
517 }
518
519 if (!(new_handle = malloc(sizeof(*new_handle)))) {
520 goto err;
521 }
522
523 memset(new_handle, 0, sizeof(*new_handle));
524
525 if (!(new_handle->dsn = strdup(database_interface_options.connection_string))) {
526 goto err;
527 }
528
529 new_handle->sock = 0;
530 new_handle->state = MARIADB_STATE_INIT;
531 new_handle->affected_rows = 0;
532 new_handle->num_retries = DEFAULT_MARIADB_RETRIES;
533 new_handle->auto_commit = SWITCH_TRUE;
534 new_handle->in_txn = SWITCH_FALSE;
535 new_handle->stored_results = 0;
536
537 (*dih)->handle = new_handle;
538
539 if (mariadb_handle_connect(new_handle) != SWITCH_STATUS_SUCCESS) {
540 if (mariadb_handle_destroy(dih) != SWITCH_STATUS_SUCCESS) {
541 goto err;
542 }
543 }
544
545 return SWITCH_STATUS_SUCCESS;
546
547 err:
548 switch_safe_free(*dih);
549
550 if (new_handle) {
551 switch_safe_free(new_handle->dsn);
552 switch_safe_free(new_handle);
553 }
554
555 return SWITCH_STATUS_FALSE;
556 }
557
mariadb_handle_destroy(switch_database_interface_handle_t ** dih)558 switch_status_t mariadb_handle_destroy(switch_database_interface_handle_t **dih)
559 {
560 mariadb_handle_t *handle = NULL;
561
562 if (!dih) {
563 return SWITCH_STATUS_FALSE;
564 }
565
566 handle = (*dih)->handle;
567
568 if (handle) {
569 mariadb_handle_disconnect(handle);
570
571 switch_safe_free(handle->dsn);
572 free(handle);
573 }
574
575 switch_safe_free(*dih);
576
577 return SWITCH_STATUS_SUCCESS;
578 }
579
database_flush(switch_database_interface_handle_t * dih)580 switch_status_t database_flush(switch_database_interface_handle_t *dih)
581 {
582 mariadb_handle_t *handle;
583
584 if (!dih) {
585 return SWITCH_STATUS_FALSE;
586 }
587
588 handle = dih->handle;
589
590 return mariadb_flush(handle);
591 }
592
mariadb_flush(mariadb_handle_t * handle)593 switch_status_t mariadb_flush(mariadb_handle_t *handle)
594 {
595 MYSQL_RES *tmp = NULL;
596 int x = 0;
597
598 if (!handle) {
599 return SWITCH_STATUS_FALSE;
600 }
601
602 if (handle->stored_results) {
603 if (mysql_next_result(&handle->con) != 0) {
604 goto done;
605 }
606 }
607
608 /* Make sure the query is fully cleared */
609 while ((tmp = mysql_store_result(&handle->con)) != NULL) {
610 mysql_free_result(tmp);
611 x++;
612
613 if (mysql_next_result(&handle->con) != 0)
614 break;
615 }
616
617 if (x) {
618 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "Flushing %d results\n", x);
619 }
620
621 done:
622
623 return SWITCH_STATUS_SUCCESS;
624 }
625
mariadb_send_query(mariadb_handle_t * handle,const char * sql)626 switch_status_t mariadb_send_query(mariadb_handle_t *handle, const char* sql)
627 {
628 char *err_str;
629 int ret;
630
631 switch_safe_free(handle->sql);
632 handle->sql = strdup(sql);
633 handle->stored_results = 0;
634 ret = mysql_real_query(&handle->con, sql, (unsigned long)strlen(sql));
635 if (ret) {
636 err_str = mariadb_handle_get_error(handle);
637 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to send query (%s) to database: %s\n", sql, err_str);
638 switch_safe_free(err_str);
639 mariadb_finish_results(handle);
640 goto error;
641 }
642
643 return SWITCH_STATUS_SUCCESS;
644
645 error:
646 return SWITCH_STATUS_FALSE;
647 }
648
mariadb_handle_exec_base_detailed(const char * file,const char * func,int line,mariadb_handle_t * handle,const char * sql,char ** err)649 switch_status_t mariadb_handle_exec_base_detailed(const char *file, const char *func, int line,
650 mariadb_handle_t *handle, const char *sql, char **err)
651 {
652 char *err_str = NULL, *er = NULL;
653
654 mariadb_flush(handle);
655 handle->affected_rows = 0;
656
657 if (!db_is_up(handle)) {
658 er = strdup("Database is not up!");
659 goto error;
660 }
661
662 if (handle->auto_commit == SWITCH_FALSE && handle->in_txn == SWITCH_FALSE) {
663 if (mariadb_send_query(handle, "BEGIN") != SWITCH_STATUS_SUCCESS) {
664 er = strdup("Error sending BEGIN!");
665 if (mariadb_finish_results(handle) != SWITCH_STATUS_SUCCESS) {
666 db_is_up(handle); /* If finish_results failed, maybe the db went dead */
667 }
668 goto error;
669 }
670
671 if (mariadb_finish_results(handle) != SWITCH_STATUS_SUCCESS) {
672 db_is_up(handle);
673 er = strdup("Error sending BEGIN!");
674 goto error;
675 }
676 handle->in_txn = SWITCH_TRUE;
677 }
678
679 if (mariadb_send_query(handle, sql) != SWITCH_STATUS_SUCCESS) {
680 er = strdup("Error sending query!");
681 if (mariadb_finish_results(handle) != SWITCH_STATUS_SUCCESS) {
682 db_is_up(handle);
683 }
684 goto error;
685 }
686
687 return SWITCH_STATUS_SUCCESS;
688
689 error:
690 err_str = mariadb_handle_get_error(handle);
691
692 if (zstr(err_str)) {
693 switch_safe_free(err_str);
694 err_str = (er) ? er : strdup((char *)"SQL ERROR!");
695 } else {
696 switch_safe_free(er);
697 }
698
699 if (err_str) {
700 if (!switch_stristr("already exists", err_str) && !switch_stristr("duplicate key name", err_str)) {
701 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(err_str));
702 }
703
704 if (err) {
705 *err = err_str;
706 } else {
707 free(err_str);
708 }
709 }
710
711 return SWITCH_STATUS_FALSE;
712 }
713
mariadb_handle_exec_detailed(const char * file,const char * func,int line,mariadb_handle_t * handle,const char * sql,char ** err)714 switch_status_t mariadb_handle_exec_detailed(const char *file, const char *func, int line,
715 mariadb_handle_t *handle, const char *sql, char **err)
716 {
717 if (mariadb_handle_exec_base_detailed(file, func, line, handle, sql, err) == SWITCH_STATUS_FALSE) {
718 goto error;
719 }
720
721 return mariadb_finish_results(handle);
722
723 error:
724 return SWITCH_STATUS_FALSE;
725 }
726
database_handle_exec_detailed(const char * file,const char * func,int line,switch_database_interface_handle_t * dih,const char * sql,char ** err)727 switch_status_t database_handle_exec_detailed(const char *file, const char *func, int line,
728 switch_database_interface_handle_t *dih, const char *sql, char **err)
729 {
730 mariadb_handle_t *handle;
731
732 if (!dih) {
733 return SWITCH_STATUS_FALSE;
734 }
735
736 handle = dih->handle;
737
738 return mariadb_handle_exec_detailed(file, func, line, handle, sql, err);
739 }
740
database_handle_exec_string(switch_database_interface_handle_t * dih,const char * sql,char * resbuf,size_t len,char ** err)741 switch_status_t database_handle_exec_string(switch_database_interface_handle_t *dih, const char *sql, char *resbuf, size_t len, char **err)
742 {
743 mariadb_handle_t *handle;
744 switch_status_t sstatus = SWITCH_STATUS_SUCCESS;
745 mariadb_result_t *result = NULL;
746 MYSQL_ROW row;
747
748 if (!dih) {
749 return SWITCH_STATUS_FALSE;
750 }
751
752 handle = dih->handle;
753
754 if (!handle)
755 return SWITCH_STATUS_FALSE;
756
757 handle->affected_rows = 0;
758
759 if (mariadb_handle_exec_base(handle, sql, err) == SWITCH_STATUS_FALSE) {
760 goto error;
761 }
762
763 if (mariadb_next_result(handle, &result) == SWITCH_STATUS_FALSE) {
764 goto error;
765 }
766
767 if (!result) {
768 sstatus = SWITCH_STATUS_FALSE;
769 goto done;
770 }
771
772 if (!result->result) {
773 sstatus = SWITCH_STATUS_FALSE;
774 goto done;
775 }
776
777 if (handle->affected_rows <= 0) {
778 goto done;
779 }
780
781 row = mysql_fetch_row(result->result);
782 if (row) {
783 strncpy(resbuf, row[0], len);
784 } else {
785 resbuf[0] = '\0';
786 }
787
788 done:
789
790 mariadb_free_result(&result);
791 if (mariadb_finish_results(handle) != SWITCH_STATUS_SUCCESS) {
792 sstatus = SWITCH_STATUS_FALSE;
793 }
794
795 return sstatus;
796
797 error:
798 return SWITCH_STATUS_FALSE;
799 }
800
database_SQLSetAutoCommitAttr(switch_database_interface_handle_t * dih,switch_bool_t on)801 switch_status_t database_SQLSetAutoCommitAttr(switch_database_interface_handle_t *dih, switch_bool_t on)
802 {
803 mariadb_handle_t *handle;
804
805 if (!dih) {
806 return SWITCH_STATUS_FALSE;
807 }
808
809 handle = dih->handle;
810
811 if (!handle)
812 return SWITCH_STATUS_FALSE;
813
814 if (on) {
815 handle->auto_commit = SWITCH_TRUE;
816 } else {
817 handle->auto_commit = SWITCH_FALSE;
818 }
819
820 return SWITCH_STATUS_SUCCESS;
821 }
822
mariadb_handle_affected_rows(switch_database_interface_handle_t * dih,int * affected_rows)823 switch_status_t mariadb_handle_affected_rows(switch_database_interface_handle_t *dih, int *affected_rows)
824 {
825 mariadb_handle_t *handle = NULL;
826
827 if (!dih) {
828 return SWITCH_STATUS_FALSE;
829 }
830
831 handle = dih->handle;
832
833 if (!handle)
834 return SWITCH_STATUS_FALSE;
835
836 *affected_rows = handle->affected_rows;
837
838 return SWITCH_STATUS_SUCCESS;
839 }
840
database_commit(switch_database_interface_handle_t * dih)841 switch_status_t database_commit(switch_database_interface_handle_t *dih)
842 {
843 switch_status_t result;
844
845 mariadb_handle_t *handle;
846
847 if (!dih) {
848 return SWITCH_STATUS_FALSE;
849 }
850
851 handle = dih->handle;
852
853 if (!handle)
854 return SWITCH_STATUS_FALSE;
855
856 result = mariadb_SQLEndTran(handle, SWITCH_TRUE);
857 result = database_SQLSetAutoCommitAttr(dih, SWITCH_TRUE) && result;
858 result = mariadb_finish_results(handle) && result;
859
860 return result;
861 }
862
database_rollback(switch_database_interface_handle_t * dih)863 switch_status_t database_rollback(switch_database_interface_handle_t *dih)
864 {
865 switch_status_t result;
866 mariadb_handle_t *handle;
867
868 if (!dih) {
869 return SWITCH_STATUS_FALSE;
870 }
871
872 handle = dih->handle;
873
874 if (!handle) {
875 return SWITCH_STATUS_FALSE;
876 }
877
878 result = mariadb_SQLEndTran(handle, SWITCH_FALSE);
879 result = database_SQLSetAutoCommitAttr(dih, SWITCH_TRUE) && result;
880 result = mariadb_finish_results(handle) && result;
881
882 return result;
883 }
884
mariadb_handle_callback_exec_detailed(const char * file,const char * func,int line,switch_database_interface_handle_t * dih,const char * sql,switch_core_db_callback_func_t callback,void * pdata,char ** err)885 switch_status_t mariadb_handle_callback_exec_detailed(const char *file, const char *func, int line,
886 switch_database_interface_handle_t *dih, const char *sql, switch_core_db_callback_func_t callback, void *pdata, char **err)
887 {
888 mariadb_handle_t *handle;
889 char *err_str = NULL;
890 int row = 0, col = 0, err_cnt = 0;
891 mariadb_result_t *result = NULL;
892
893 if (!dih) {
894 return SWITCH_STATUS_FALSE;
895 }
896
897 handle = dih->handle;
898
899 if (!handle) {
900 return SWITCH_STATUS_FALSE;
901 }
902
903 handle->affected_rows = 0;
904
905 switch_assert(callback != NULL);
906
907 if (mariadb_handle_exec_base(handle, sql, err) == SWITCH_STATUS_FALSE) {
908 goto error;
909 }
910
911 if (mariadb_next_result(handle, &result) == SWITCH_STATUS_FALSE) {
912 err_cnt++;
913 err_str = mariadb_handle_get_error(handle);
914 if (result && !zstr(result->err)) {
915 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(result->err));
916 }
917 if (!zstr(err_str)) {
918 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(err_str));
919 }
920 switch_safe_free(err_str);
921 err_str = NULL;
922 }
923
924 while (result != NULL) {
925 for (row = 0; row < result->rows; ++row) {
926 char **names;
927 char **vals;
928 MYSQL_ROW data_row;
929
930 names = calloc(result->cols, sizeof(*names));
931 vals = calloc(result->cols, sizeof(*vals));
932
933 switch_assert(names && vals);
934
935 data_row = mysql_fetch_row(result->result);
936
937 for (col = 0; col < result->cols; ++col) {
938 unsigned long *lengths;
939 MYSQL_FIELD *field = mysql_fetch_field_direct(result->result, col);
940 if (field) {
941 names[col] = malloc(field->name_length +1);
942 names[col][field->name_length] = '\0';
943 strncpy(names[col], field->name, field->name_length);
944
945 lengths = mysql_fetch_lengths(result->result);
946 vals[col] = malloc(lengths[col] + 1);
947 vals[col][lengths[col]] = '\0';
948
949 if (data_row) {
950 strncpy(vals[col], data_row[col], lengths[col]);
951 } else {
952 vals[col][0] = '\0';
953 }
954 } else {
955 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: Column number %d out of range\n", col);
956 }
957 }
958
959 if (callback(pdata, result->cols, vals, names)) {
960 mariadb_finish_results(handle);
961 row = result->rows;
962 }
963
964 for (col = 0; col < result->cols; ++col) {
965 free(names[col]);
966 free(vals[col]);
967 }
968
969 free(names);
970 free(vals);
971 }
972
973 mariadb_free_result(&result);
974 if (mariadb_next_result(handle, &result) == SWITCH_STATUS_FALSE) {
975 err_cnt++;
976 err_str = mariadb_handle_get_error(handle);
977 if (result && !zstr(result->err)) {
978 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(result->err));
979 }
980 if (!zstr(err_str)) {
981 switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(err_str));
982 }
983 switch_safe_free(err_str);
984 err_str = NULL;
985 }
986 }
987 if (err_cnt) {
988 goto error;
989 }
990
991 return SWITCH_STATUS_SUCCESS;
992
993 error:
994 return SWITCH_STATUS_FALSE;
995 }
996
SWITCH_MODULE_LOAD_FUNCTION(mod_mariadb_load)997 SWITCH_MODULE_LOAD_FUNCTION(mod_mariadb_load)
998 {
999 switch_database_interface_t *database_interface;
1000
1001 supported_prefixes[0] = (char *)"mariadb";
1002 supported_prefixes[1] = (char *)"maria";
1003 supported_prefixes[2] = (char *)"mysql";
1004
1005 /* connect my internal structure to the blank pointer passed to me */
1006 *module_interface = switch_loadable_module_create_module_interface(pool, modname);
1007 MODULE_INTERFACE = *module_interface;
1008
1009 database_interface = (switch_database_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_DATABASE_INTERFACE);
1010 database_interface->interface_name = modname;
1011 /*
1012 MariaDB and MySQL both have row size limits
1013 https://mariadb.com/kb/en/library/innodb-limitations/#limitations-on-size
1014 https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html
1015
1016 Setting database flag to SWITCH_DATABASE_FLAG_ROW_SIZE_LIMIT
1017 will allow FreeSWITCH Core to properly create wide tables such as the channel table
1018 which size exeeds the limit in the case of a multi-byte charset like utf8 (1-4 bytes per character).
1019 */
1020 database_interface->flags = SWITCH_DATABASE_FLAG_ROW_SIZE_LIMIT;
1021 database_interface->prefixes = supported_prefixes;
1022 database_interface->handle_new = mariadb_handle_new;
1023 database_interface->handle_destroy = mariadb_handle_destroy;
1024 database_interface->flush = database_flush;
1025 database_interface->exec_detailed = database_handle_exec_detailed;
1026 database_interface->exec_string = database_handle_exec_string;
1027 database_interface->affected_rows = mariadb_handle_affected_rows;
1028 database_interface->sql_set_auto_commit_attr = database_SQLSetAutoCommitAttr;
1029 database_interface->commit = database_commit;
1030 database_interface->rollback = database_rollback;
1031 database_interface->callback_exec_detailed = mariadb_handle_callback_exec_detailed;
1032
1033 /* indicate that the module should continue to be loaded */
1034 return SWITCH_STATUS_SUCCESS;
1035 }
1036
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_mariadb_shutdown)1037 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_mariadb_shutdown)
1038 {
1039 return SWITCH_STATUS_UNLOAD;
1040 }
1041
1042 /* For Emacs:
1043 * Local Variables:
1044 * mode:c
1045 * indent-tabs-mode:t
1046 * tab-width:4
1047 * c-basic-offset:4
1048 * End:
1049 * For VIM:
1050 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
1051 */
1052