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