fdb6fd68498e8e9825983c5071c17534467a7543
[strongswan.git] / src / libstrongswan / plugins / mysql / mysql_database.c
1 /*
2 * Copyright (C) 2007 Martin Willi
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17 #include <string.h>
18 #include <mysql.h>
19
20 #include "mysql_database.h"
21
22 #include <utils/debug.h>
23 #include <utils/chunk.h>
24 #include <threading/thread_value.h>
25 #include <threading/mutex.h>
26 #include <collections/linked_list.h>
27
28 /* Older mysql.h headers do not define it, but we need it. It is not returned
29 * in in MySQL 4 by default, but by MySQL 5. To avoid this problem, we catch
30 * it in all cases. */
31 #ifndef MYSQL_DATA_TRUNCATED
32 #define MYSQL_DATA_TRUNCATED 101
33 #endif
34
35 typedef struct private_mysql_database_t private_mysql_database_t;
36
37 /**
38 * private data of mysql_database
39 */
40 struct private_mysql_database_t {
41
42 /**
43 * public functions
44 */
45 mysql_database_t public;
46
47 /**
48 * connection pool, contains conn_t
49 */
50 linked_list_t *pool;
51
52 /**
53 * mutex to lock pool
54 */
55 mutex_t *mutex;
56
57 /**
58 * hostname to connect to
59 */
60 char *host;
61
62 /**
63 * username to use
64 */
65 char *username;
66
67 /**
68 * password
69 */
70 char *password;
71
72 /**
73 * database name
74 */
75 char *database;
76
77 /**
78 * tcp port
79 */
80 int port;
81 };
82
83 typedef struct conn_t conn_t;
84
85 /**
86 * connection pool entry
87 */
88 struct conn_t {
89
90 /**
91 * MySQL database connection
92 */
93 MYSQL *mysql;
94
95 /**
96 * connection in use?
97 */
98 bool in_use;
99 };
100
101 /**
102 * Release a mysql connection
103 */
104 static void conn_release(private_mysql_database_t *this, conn_t *conn)
105 {
106 this->mutex->lock(this->mutex);
107 conn->in_use = FALSE;
108 this->mutex->unlock(this->mutex);
109 }
110
111 /**
112 * thread specific initialization flag
113 */
114 thread_value_t *initialized;
115
116 /**
117 * Initialize a thread for mysql usage
118 */
119 static void thread_initialize()
120 {
121 if (initialized->get(initialized) == NULL)
122 {
123 initialized->set(initialized, (void*)TRUE);
124 mysql_thread_init();
125 }
126 }
127
128 /**
129 * mysql library initialization function
130 */
131 bool mysql_database_init()
132 {
133 if (mysql_library_init(0, NULL, NULL))
134 {
135 return FALSE;
136 }
137 initialized = thread_value_create((thread_cleanup_t)mysql_thread_end);
138 return TRUE;
139 }
140
141 /**
142 * mysql library cleanup function
143 */
144 void mysql_database_deinit()
145 {
146 initialized->destroy(initialized);
147 mysql_thread_end();
148 mysql_library_end();
149 }
150
151 /**
152 * Destroy a mysql connection
153 */
154 static void conn_destroy(conn_t *this)
155 {
156 mysql_close(this->mysql);
157 free(this);
158 }
159
160 /**
161 * Acquire/Reuse a mysql connection
162 */
163 static conn_t *conn_get(private_mysql_database_t *this)
164 {
165 conn_t *current, *found = NULL;
166 enumerator_t *enumerator;
167
168 thread_initialize();
169
170 while (TRUE)
171 {
172 this->mutex->lock(this->mutex);
173 enumerator = this->pool->create_enumerator(this->pool);
174 while (enumerator->enumerate(enumerator, &current))
175 {
176 if (!current->in_use)
177 {
178 found = current;
179 found->in_use = TRUE;
180 break;
181 }
182 }
183 enumerator->destroy(enumerator);
184 this->mutex->unlock(this->mutex);
185 if (found)
186 { /* check connection if found, release if ping fails */
187 if (mysql_ping(found->mysql) == 0)
188 {
189 break;
190 }
191 this->mutex->lock(this->mutex);
192 this->pool->remove(this->pool, found, NULL);
193 this->mutex->unlock(this->mutex);
194 conn_destroy(found);
195 found = NULL;
196 continue;
197 }
198 break;
199 }
200 if (found == NULL)
201 {
202 INIT(found,
203 .in_use = TRUE,
204 .mysql = mysql_init(NULL),
205 );
206 if (!mysql_real_connect(found->mysql, this->host, this->username,
207 this->password, this->database, this->port,
208 NULL, 0))
209 {
210 DBG1(DBG_LIB, "connecting to mysql://%s:***@%s:%d/%s failed: %s",
211 this->username, this->host, this->port, this->database,
212 mysql_error(found->mysql));
213 conn_destroy(found);
214 found = NULL;
215 }
216 else
217 {
218 this->mutex->lock(this->mutex);
219 this->pool->insert_last(this->pool, found);
220 DBG2(DBG_LIB, "increased MySQL connection pool size to %d",
221 this->pool->get_count(this->pool));
222 this->mutex->unlock(this->mutex);
223 }
224 }
225 return found;
226 }
227
228 /**
229 * Create and run a MySQL stmt using a sql string and args
230 */
231 static MYSQL_STMT* run(MYSQL *mysql, char *sql, va_list *args)
232 {
233 MYSQL_STMT *stmt;
234 int params;
235
236 stmt = mysql_stmt_init(mysql);
237 if (stmt == NULL)
238 {
239 DBG1(DBG_LIB, "creating MySQL statement failed: %s",
240 mysql_error(mysql));
241 return NULL;
242 }
243 if (mysql_stmt_prepare(stmt, sql, strlen(sql)))
244 {
245 DBG1(DBG_LIB, "preparing MySQL statement failed: %s",
246 mysql_stmt_error(stmt));
247 mysql_stmt_close(stmt);
248 return NULL;
249 }
250 params = mysql_stmt_param_count(stmt);
251 if (params > 0)
252 {
253 int i;
254 MYSQL_BIND *bind;
255
256 bind = alloca(sizeof(MYSQL_BIND) * params);
257 memset(bind, 0, sizeof(MYSQL_BIND) * params);
258
259 for (i = 0; i < params; i++)
260 {
261 switch (va_arg(*args, db_type_t))
262 {
263 case DB_INT:
264 {
265 bind[i].buffer_type = MYSQL_TYPE_LONG;
266 bind[i].buffer = (char*)alloca(sizeof(int));
267 *(int*)bind[i].buffer = va_arg(*args, int);
268 bind[i].buffer_length = sizeof(int);
269 break;
270 }
271 case DB_UINT:
272 {
273 bind[i].buffer_type = MYSQL_TYPE_LONG;
274 bind[i].buffer = (char*)alloca(sizeof(u_int));
275 *(u_int*)bind[i].buffer = va_arg(*args, u_int);
276 bind[i].buffer_length = sizeof(u_int);
277 bind[i].is_unsigned = TRUE;
278 break;
279 }
280 case DB_TEXT:
281 {
282 bind[i].buffer_type = MYSQL_TYPE_STRING;;
283 bind[i].buffer = va_arg(*args, char*);
284 if (bind[i].buffer)
285 {
286 bind[i].buffer_length = strlen(bind[i].buffer);
287 }
288 break;
289 }
290 case DB_BLOB:
291 {
292 chunk_t chunk = va_arg(*args, chunk_t);
293 bind[i].buffer_type = MYSQL_TYPE_BLOB;
294 bind[i].buffer = chunk.ptr;
295 bind[i].buffer_length = chunk.len;
296 break;
297 }
298 case DB_DOUBLE:
299 {
300 bind[i].buffer_type = MYSQL_TYPE_DOUBLE;
301 bind[i].buffer = (char*)alloca(sizeof(double));
302 *(double*)bind[i].buffer = va_arg(*args, double);
303 bind[i].buffer_length = sizeof(double);
304 break;
305 }
306 case DB_NULL:
307 {
308 bind[i].buffer_type = MYSQL_TYPE_NULL;
309 break;
310 }
311 default:
312 DBG1(DBG_LIB, "invalid data type supplied");
313 mysql_stmt_close(stmt);
314 return NULL;
315 }
316 }
317 if (mysql_stmt_bind_param(stmt, bind))
318 {
319 DBG1(DBG_LIB, "binding MySQL param failed: %s",
320 mysql_stmt_error(stmt));
321 mysql_stmt_close(stmt);
322 return NULL;
323 }
324 }
325 if (mysql_stmt_execute(stmt))
326 {
327 DBG1(DBG_LIB, "executing MySQL statement failed: %s",
328 mysql_stmt_error(stmt));
329 mysql_stmt_close(stmt);
330 return NULL;
331 }
332 return stmt;
333 }
334
335 typedef struct {
336 /** implements enumerator_t */
337 enumerator_t public;
338 /** mysql database */
339 private_mysql_database_t *db;
340 /** associated MySQL statement */
341 MYSQL_STMT *stmt;
342 /** result bindings */
343 MYSQL_BIND *bind;
344 /** pooled connection handle */
345 conn_t *conn;
346 /** value for INT, UINT, double */
347 union {
348 void *p_void;;
349 int *p_int;
350 u_int *p_uint;
351 double *p_double;
352 } val;
353 /* length for TEXT and BLOB */
354 unsigned long *length;
355 } mysql_enumerator_t;
356
357 /**
358 * create a mysql enumerator
359 */
360 static void mysql_enumerator_destroy(mysql_enumerator_t *this)
361 {
362 int columns, i;
363
364 columns = mysql_stmt_field_count(this->stmt);
365
366 for (i = 0; i < columns; i++)
367 {
368 switch (this->bind[i].buffer_type)
369 {
370 case MYSQL_TYPE_STRING:
371 case MYSQL_TYPE_BLOB:
372 {
373 free(this->bind[i].buffer);
374 break;
375 }
376 default:
377 break;
378 }
379 }
380 mysql_stmt_close(this->stmt);
381 conn_release(this->db, this->conn);
382 free(this->bind);
383 free(this->val.p_void);
384 free(this->length);
385 free(this);
386 }
387
388 /**
389 * Implementation of database.query().enumerate
390 */
391 static bool mysql_enumerator_enumerate(mysql_enumerator_t *this, ...)
392 {
393 int i, columns;
394 va_list args;
395
396 columns = mysql_stmt_field_count(this->stmt);
397
398 /* free/reset data set of previous call */
399 for (i = 0; i < columns; i++)
400 {
401 switch (this->bind[i].buffer_type)
402 {
403 case MYSQL_TYPE_STRING:
404 case MYSQL_TYPE_BLOB:
405 {
406 free(this->bind[i].buffer);
407 this->bind[i].buffer = NULL;
408 this->bind[i].buffer_length = 0;
409 this->bind[i].length = &this->length[i];
410 this->length[i] = 0;
411 break;
412 }
413 default:
414 break;
415 }
416 }
417
418 switch (mysql_stmt_fetch(this->stmt))
419 {
420 case 0:
421 case MYSQL_DATA_TRUNCATED:
422 break;
423 case MYSQL_NO_DATA:
424 return FALSE;
425 default:
426 DBG1(DBG_LIB, "fetching MySQL row failed: %s",
427 mysql_stmt_error(this->stmt));
428 return FALSE;
429 }
430
431 va_start(args, this);
432 for (i = 0; i < columns; i++)
433 {
434 switch (this->bind[i].buffer_type)
435 {
436 case MYSQL_TYPE_LONG:
437 {
438 if (this->bind[i].is_unsigned)
439 {
440 u_int *value = va_arg(args, u_int*);
441 *value = this->val.p_uint[i];
442 }
443 else
444 {
445 int *value = va_arg(args, int*);
446 *value = this->val.p_int[i];
447 }
448 break;
449 }
450 case MYSQL_TYPE_STRING:
451 {
452 char **value = va_arg(args, char**);
453 this->bind[i].buffer = malloc(this->length[i]+1);
454 this->bind[i].buffer_length = this->length[i];
455 *value = this->bind[i].buffer;
456 mysql_stmt_fetch_column(this->stmt, &this->bind[i], i, 0);
457 ((char*)this->bind[i].buffer)[this->length[i]] = '\0';
458 break;
459 }
460 case MYSQL_TYPE_BLOB:
461 {
462 chunk_t *value = va_arg(args, chunk_t*);
463 this->bind[i].buffer = malloc(this->length[i]);
464 this->bind[i].buffer_length = this->length[i];
465 value->ptr = this->bind[i].buffer;
466 value->len = this->length[i];
467 mysql_stmt_fetch_column(this->stmt, &this->bind[i], i, 0);
468 break;
469 }
470 case MYSQL_TYPE_DOUBLE:
471 {
472 double *value = va_arg(args, double*);
473 *value = this->val.p_double[i];
474 break;
475 }
476 default:
477 break;
478 }
479 }
480 va_end(args);
481 return TRUE;
482 }
483
484 METHOD(database_t, query, enumerator_t*,
485 private_mysql_database_t *this, char *sql, ...)
486 {
487 MYSQL_STMT *stmt;
488 va_list args;
489 mysql_enumerator_t *enumerator = NULL;
490 conn_t *conn;
491
492 conn = conn_get(this);
493 if (!conn)
494 {
495 return NULL;
496 }
497
498 va_start(args, sql);
499 stmt = run(conn->mysql, sql, &args);
500 if (stmt)
501 {
502 int columns, i;
503
504 INIT(enumerator,
505 .public = {
506 .enumerate = (void*)mysql_enumerator_enumerate,
507 .destroy = (void*)mysql_enumerator_destroy,
508
509 },
510 .db = this,
511 .stmt = stmt,
512 .conn = conn,
513 );
514 columns = mysql_stmt_field_count(stmt);
515 enumerator->bind = calloc(columns, sizeof(MYSQL_BIND));
516 enumerator->length = calloc(columns, sizeof(unsigned long));
517 enumerator->val.p_void = calloc(columns, sizeof(enumerator->val));
518 for (i = 0; i < columns; i++)
519 {
520 switch (va_arg(args, db_type_t))
521 {
522 case DB_INT:
523 {
524 enumerator->bind[i].buffer_type = MYSQL_TYPE_LONG;
525 enumerator->bind[i].buffer = (char*)&enumerator->val.p_int[i];
526 break;
527 }
528 case DB_UINT:
529 {
530 enumerator->bind[i].buffer_type = MYSQL_TYPE_LONG;
531 enumerator->bind[i].buffer = (char*)&enumerator->val.p_uint[i];
532 enumerator->bind[i].is_unsigned = TRUE;
533 break;
534 }
535 case DB_TEXT:
536 {
537 enumerator->bind[i].buffer_type = MYSQL_TYPE_STRING;
538 enumerator->bind[i].length = &enumerator->length[i];
539 break;
540 }
541 case DB_BLOB:
542 {
543 enumerator->bind[i].buffer_type = MYSQL_TYPE_BLOB;
544 enumerator->bind[i].length = &enumerator->length[i];
545 break;
546 }
547 case DB_DOUBLE:
548 {
549 enumerator->bind[i].buffer_type = MYSQL_TYPE_DOUBLE;
550 enumerator->bind[i].buffer = (char*)&enumerator->val.p_double[i];
551 break;
552 }
553 default:
554 DBG1(DBG_LIB, "invalid result data type supplied");
555 mysql_enumerator_destroy(enumerator);
556 va_end(args);
557 return NULL;
558 }
559 }
560 if (mysql_stmt_bind_result(stmt, enumerator->bind))
561 {
562 DBG1(DBG_LIB, "binding MySQL result failed: %s",
563 mysql_stmt_error(stmt));
564 mysql_enumerator_destroy(enumerator);
565 enumerator = NULL;
566 }
567 }
568 else
569 {
570 conn_release(this, conn);
571 }
572 va_end(args);
573 return (enumerator_t*)enumerator;
574 }
575
576 METHOD(database_t, execute, int,
577 private_mysql_database_t *this, int *rowid, char *sql, ...)
578 {
579 MYSQL_STMT *stmt;
580 va_list args;
581 conn_t *conn;
582 int affected = -1;
583
584 conn = conn_get(this);
585 if (!conn)
586 {
587 return -1;
588 }
589 va_start(args, sql);
590 stmt = run(conn->mysql, sql, &args);
591 if (stmt)
592 {
593 if (rowid)
594 {
595 *rowid = mysql_stmt_insert_id(stmt);
596 }
597 affected = mysql_stmt_affected_rows(stmt);
598 mysql_stmt_close(stmt);
599 }
600 va_end(args);
601 conn_release(this, conn);
602 return affected;
603 }
604
605 METHOD(database_t, get_driver,db_driver_t,
606 private_mysql_database_t *this)
607 {
608 return DB_MYSQL;
609 }
610
611 METHOD(database_t, destroy, void,
612 private_mysql_database_t *this)
613 {
614 this->pool->destroy_function(this->pool, (void*)conn_destroy);
615 this->mutex->destroy(this->mutex);
616 free(this->host);
617 free(this->username);
618 free(this->password);
619 free(this->database);
620 free(this);
621 }
622
623 static bool parse_uri(private_mysql_database_t *this, char *uri)
624 {
625 char *username, *password, *host, *port = "0", *database, *pos;
626
627 /**
628 * parse mysql://username:pass@host:port/database uri
629 */
630 username = strdupa(uri + 8);
631 pos = strchr(username, ':');
632 if (pos)
633 {
634 *pos = '\0';
635 password = pos + 1;
636 pos = strrchr(password, '@');
637 if (pos)
638 {
639 *pos = '\0';
640 host = pos + 1;
641 pos = strrchr(host, ':');
642 if (pos)
643 {
644 *pos = '\0';
645 port = pos + 1;
646 pos = strchr(port, '/');
647 }
648 else
649 {
650 pos = strchr(host, '/');
651 }
652 if (pos)
653 {
654 *pos = '\0';
655 database = pos + 1;
656
657 this->host = strdup(host);
658 this->username = strdup(username);
659 this->password = strdup(password);
660 this->database = strdup(database);
661 this->port = atoi(port);
662 return TRUE;
663 }
664 }
665 }
666 DBG1(DBG_LIB, "parsing MySQL database uri '%s' failed", uri);
667 return FALSE;
668 }
669
670
671 /*
672 * see header file
673 */
674 mysql_database_t *mysql_database_create(char *uri)
675 {
676 conn_t *conn;
677 private_mysql_database_t *this;
678
679 if (!strpfx(uri, "mysql://"))
680 {
681 return NULL;
682 }
683
684 INIT(this,
685 .public = {
686 .db = {
687 .query = _query,
688 .execute = _execute,
689 .get_driver = _get_driver,
690 .destroy = _destroy,
691 },
692 },
693 );
694
695 if (!parse_uri(this, uri))
696 {
697 free(this);
698 return NULL;
699 }
700 this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
701 this->pool = linked_list_create();
702
703 /* check connectivity */
704 conn = conn_get(this);
705 if (!conn)
706 {
707 destroy(this);
708 return NULL;
709 }
710 conn_release(this, conn);
711 return &this->public;
712 }