aboutsummaryrefslogtreecommitdiff
path: root/src/mysql
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2012-03-21 09:43:54 +0000
committerChristian Grothoff <christian@grothoff.org>2012-03-21 09:43:54 +0000
commit3343e64659d6e8ff5ed6a74faac7226e55fc1c55 (patch)
tree0e4a00025d80fd39e043019dccbd67f8c483328d /src/mysql
parent422593416288a25c3fa062fffd81525aa50830d0 (diff)
downloadgnunet-3343e64659d6e8ff5ed6a74faac7226e55fc1c55.tar.gz
gnunet-3343e64659d6e8ff5ed6a74faac7226e55fc1c55.zip
creating mysql helper library for the various mysql backends
Diffstat (limited to 'src/mysql')
-rw-r--r--src/mysql/Makefile.am20
-rw-r--r--src/mysql/mysql.c661
2 files changed, 681 insertions, 0 deletions
diff --git a/src/mysql/Makefile.am b/src/mysql/Makefile.am
new file mode 100644
index 000000000..1711e7572
--- /dev/null
+++ b/src/mysql/Makefile.am
@@ -0,0 +1,20 @@
1INCLUDES = -I$(top_srcdir)/src/include
2
3if MINGW
4 WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols
5endif
6
7if USE_COVERAGE
8 AM_CFLAGS = --coverage
9endif
10
11lib_LTLIBRARIES = libgnunetmysql.la
12
13libgnunetmysql_la_SOURCES = \
14 mysql.c
15libgnunetmysql_la_LIBADD = -lmysqlclient \
16 $(top_builddir)/src/util/libgnunetutil.la
17libgnunetmysql_la_LDFLAGS = \
18 $(GN_LIB_LDFLAGS) \
19 -version-info 0:0:0
20
diff --git a/src/mysql/mysql.c b/src/mysql/mysql.c
new file mode 100644
index 000000000..0fc98308c
--- /dev/null
+++ b/src/mysql/mysql.c
@@ -0,0 +1,661 @@
1/*
2 This file is part of GNUnet
3 (C) 2012 Christian Grothoff (and other contributing authors)
4
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19*/
20/**
21 * @file mysql/mysql.c
22 * @brief library to help with access to a MySQL database
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include <mysql/mysql.h>
27#include "gnunet_mysql_lib.h"
28
29/**
30 * Maximum number of supported parameters for a prepared
31 * statement. Increase if needed.
32 */
33#define MAX_PARAM 16
34
35
36/**
37 * Die with an error message that indicates
38 * a failure of the command 'cmd' with the message given
39 * by strerror(errno).
40 */
41#define DIE_MYSQL(cmd, dbh) do { GNUNET_log_from (GNUNET_ERROR_TYPE__ERROR, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); GNUNET_abort(); } while(0);
42
43/**
44 * Log an error message at log-level 'level' that indicates
45 * a failure of the command 'cmd' on file 'filename'
46 * with the message given by strerror(errno).
47 */
48#define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log_from (level, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);
49
50
51/**
52 * Mysql context.
53 */
54struct GNUNET_MYSQL_Context
55{
56
57 /**
58 * Our configuration.
59 */
60 const struct GNUNET_CONFIGURATION_Handle *cfg;
61
62 /**
63 * Our section.
64 */
65 const char *section;
66
67 /**
68 * Handle to the mysql database.
69 */
70 MYSQL *dbf;
71
72 /**
73 * Head of list of our prepared statements.
74 */
75 struct GNUNET_MYSQL_StatementHandle *shead;
76
77 /**
78 * Tail of list of our prepared statements.
79 */
80 struct GNUNET_MYSQL_StatementHandle *stail;
81
82 /**
83 * Filename of "my.cnf" (msyql configuration).
84 */
85 char *cnffile;
86
87};
88
89
90/**
91 * Handle for a prepared statement.
92 */
93struct GNUNET_MYSQL_StatementHandle
94{
95
96 /**
97 * Kept in a DLL.
98 */
99 struct GNUNET_MYSQL_StatementHandle *next;
100
101 /**
102 * Kept in a DLL.
103 */
104 struct GNUNET_MYSQL_StatementHandle *prev;
105
106 /**
107 * Original query string.
108 */
109 char *query;
110
111 /**
112 * Handle to MySQL prepared statement.
113 */
114 MYSQL_STMT *statement;
115
116 /**
117 * Is the MySQL prepared statement valid, or do we need to re-initialize it?
118 */
119 int valid;
120
121};
122
123
124/**
125 * Obtain the location of ".my.cnf".
126 *
127 * @param cfg our configuration
128 * @return NULL on error
129 */
130static char *
131get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
132 const char *section)
133{
134 char *cnffile;
135 char *home_dir;
136 struct stat st;
137
138#ifndef WINDOWS
139 struct passwd *pw;
140#endif
141 int configured;
142
143#ifndef WINDOWS
144 pw = getpwuid (getuid ());
145 if (!pw)
146 {
147 GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_ERROR, "mysql", "getpwuid");
148 return NULL;
149 }
150 if (GNUNET_YES ==
151 GNUNET_CONFIGURATION_have_value (cfg, section, "CONFIG"))
152 {
153 GNUNET_assert (GNUNET_OK ==
154 GNUNET_CONFIGURATION_get_value_filename (cfg,
155 section,
156 "CONFIG",
157 &cnffile));
158 configured = GNUNET_YES;
159 }
160 else
161 {
162 home_dir = GNUNET_strdup (pw->pw_dir);
163 GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
164 GNUNET_free (home_dir);
165 configured = GNUNET_NO;
166 }
167#else
168 home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
169 plibc_conv_to_win_path ("~/", home_dir);
170 GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
171 GNUNET_free (home_dir);
172 configured = GNUNET_NO;
173#endif
174 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
175 "mysql",
176 _("Trying to use file `%s' for MySQL configuration.\n"), cnffile);
177 if ((0 != STAT (cnffile, &st)) || (0 != ACCESS (cnffile, R_OK)) ||
178 (!S_ISREG (st.st_mode)))
179 {
180 if (configured == GNUNET_YES)
181 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
182 "mysql",
183 _("Could not access file `%s': %s\n"), cnffile,
184 STRERROR (errno));
185 GNUNET_free (cnffile);
186 return NULL;
187 }
188 return cnffile;
189}
190
191
192/**
193 * Open the connection with the database (and initialize
194 * our default options).
195 *
196 * @param mc database context to initialze
197 * @return GNUNET_OK on success
198 */
199static int
200iopen (struct GNUNET_MYSQL_Context *mc)
201{
202 char *mysql_dbname;
203 char *mysql_server;
204 char *mysql_user;
205 char *mysql_password;
206 unsigned long long mysql_port;
207 my_bool reconnect;
208 unsigned int timeout;
209
210 mc->dbf = mysql_init (NULL);
211 if (mc->dbf == NULL)
212 return GNUNET_SYSERR;
213 if (mc->cnffile != NULL)
214 mysql_options (mc->dbf, MYSQL_READ_DEFAULT_FILE, mc->cnffile);
215 mysql_options (mc->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
216 reconnect = 0;
217 mysql_options (mc->dbf, MYSQL_OPT_RECONNECT, &reconnect);
218 mysql_options (mc->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
219 mysql_options (mc->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
220 timeout = 60; /* in seconds */
221 mysql_options (mc->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
222 mysql_options (mc->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
223 mysql_dbname = NULL;
224 if (GNUNET_YES ==
225 GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
226 "DATABASE"))
227 GNUNET_assert (GNUNET_OK ==
228 GNUNET_CONFIGURATION_get_value_string (mc->cfg,
229 mc->section,
230 "DATABASE",
231 &mysql_dbname));
232 else
233 mysql_dbname = GNUNET_strdup ("gnunet");
234 mysql_user = NULL;
235 if (GNUNET_YES ==
236 GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
237 "USER"))
238 {
239 GNUNET_assert (GNUNET_OK ==
240 GNUNET_CONFIGURATION_get_value_string (mc->cfg,
241 mc->section,
242 "USER", &mysql_user));
243 }
244 mysql_password = NULL;
245 if (GNUNET_YES ==
246 GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
247 "PASSWORD"))
248 {
249 GNUNET_assert (GNUNET_OK ==
250 GNUNET_CONFIGURATION_get_value_string (mc->cfg,
251 mc->section,
252 "PASSWORD",
253 &mysql_password));
254 }
255 mysql_server = NULL;
256 if (GNUNET_YES ==
257 GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
258 "HOST"))
259 {
260 GNUNET_assert (GNUNET_OK ==
261 GNUNET_CONFIGURATION_get_value_string (mc->cfg,
262 mc->section,
263 "HOST",
264 &mysql_server));
265 }
266 mysql_port = 0;
267 if (GNUNET_YES ==
268 GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section,
269 "PORT"))
270 {
271 GNUNET_assert (GNUNET_OK ==
272 GNUNET_CONFIGURATION_get_value_number (mc->cfg,
273 mc->section,
274 "PORT", &mysql_port));
275 }
276
277 GNUNET_assert (mysql_dbname != NULL);
278 mysql_real_connect (mc->dbf, mysql_server, mysql_user, mysql_password,
279 mysql_dbname, (unsigned int) mysql_port, NULL,
280 CLIENT_IGNORE_SIGPIPE);
281 GNUNET_free_non_null (mysql_server);
282 GNUNET_free_non_null (mysql_user);
283 GNUNET_free_non_null (mysql_password);
284 GNUNET_free (mysql_dbname);
285 if (mysql_error (mc->dbf)[0])
286 {
287 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", mc);
288 return GNUNET_SYSERR;
289 }
290 return GNUNET_OK;
291}
292
293
294/**
295 * Create a mysql context.
296 *
297 * @param cfg configuration
298 * @param section configuration section to use to get MySQL configuration options
299 * @return the mysql context
300 */
301struct GNUNET_MYSQL_Context *
302GNUNET_MYSQL_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
303 const char *section)
304{
305 struct GNUNET_MYSQL_Context *mc;
306
307 mc = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_Context));
308 mc->cfg = cfg;
309 mc->section = section;
310 mc->cnffile = get_my_cnf_path (cfg, section);
311
312 return mc;
313}
314
315
316/**
317 * Close database connection and all prepared statements (we got a DB
318 * disconnect error).
319 *
320 * @param mc mysql context
321 */
322static void
323iclose (struct GNUNET_MYSQL_Context *mc)
324{
325 struct GNUNET_MYSQL_StatementHandle *sh;
326
327 for (sh = mc->shead; NULL != sh; sh = sh->next)
328 {
329 if (GNUNET_YES == sh->valid)
330 {
331 mysql_stmt_close (sh->statement);
332 sh->valid = GNUNET_NO;
333 }
334 sh->statement = NULL;
335 }
336 if (NULL != mc->dbf)
337 {
338 mysql_close (mc->dbf);
339 mc->dbf = NULL;
340 }
341}
342
343
344/**
345 * Destroy a mysql context. Also frees all associated prepared statements.
346 *
347 * @param mc context to destroy
348 */
349void
350GNUNET_MYSQL_context_destroy (struct GNUNET_MYSQL_Context *mc)
351{
352 struct GNUNET_MYSQL_StatementHandle *sh;
353
354 iclose (mc);
355 while (NULL != (sh = mc->shead))
356 {
357 GNUNET_CONTAINER_DLL_remove (mc->shead, mc->stail, sh);
358 GNUNET_free (sh->query);
359 GNUNET_free (sh);
360 }
361 GNUNET_free (mc);
362}
363
364
365/**
366 * Prepare a statement. Prepared statements are automatically discarded
367 * when the MySQL context is destroyed.
368 *
369 * @param mc mysql context
370 * @param query query text
371 * @return prepared statement, NULL on error
372 */
373struct GNUNET_MYSQL_StatementHandle *
374GNUNET_MYSQL_statement_prepare (struct GNUNET_MYSQL_Context *mc,
375 const char *query)
376{
377 struct GNUNET_MYSQL_StatementHandle *sh;
378
379 sh = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_StatementHandle));
380 sh->query = GNUNET_strdup (query);
381 GNUNET_CONTAINER_DLL_insert (mc->shead, mc->stail, sh);
382 return sh;
383}
384
385
386/**
387 * Run a SQL statement.
388 *
389 * @param mc mysql context
390 * @param sql SQL statement to run
391 * @return GNUNET_OK on success
392 * GNUNET_SYSERR if there was a problem
393 */
394int
395GNUNET_MYSQL_statement_run (struct GNUNET_MYSQL_Context *mc,
396 const char *sql)
397{
398 if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
399 return GNUNET_SYSERR;
400 mysql_query (mc->dbf, sql);
401 if (mysql_error (mc->dbf)[0])
402 {
403 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_query", mc);
404 iclose (mc);
405 return GNUNET_SYSERR;
406 }
407 return GNUNET_OK;
408}
409
410
411/**
412 * Prepare a statement for running.
413 *
414 * @param mc mysql context
415 * @param sh statement handle to prepare
416 * @return GNUNET_OK on success
417 */
418static int
419prepare_statement (struct GNUNET_MYSQL_Context *mc,
420 struct GNUNET_MYSQL_StatementHandle *sh)
421{
422 if (GNUNET_YES == sh->valid)
423 return GNUNET_OK;
424 if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
425 return GNUNET_SYSERR;
426 sh->statement = mysql_stmt_init (mc->dbf);
427 if (NULL == sh->statement)
428 {
429 iclose (mc);
430 return GNUNET_SYSERR;
431 }
432 if (0 != mysql_stmt_prepare (sh->statement, sh->query, strlen (sh->query)))
433 {
434 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", mc);
435 mysql_stmt_close (sh->statement);
436 sh->statement = NULL;
437 iclose (mc);
438 return GNUNET_SYSERR;
439 }
440 sh->valid = GNUNET_YES;
441 return GNUNET_OK;
442}
443
444
445/**
446 * Bind the parameters for the given MySQL statement
447 * and run it.
448 *
449 * @param mc mysql context
450 * @param sh statement to bind and run
451 * @param ap arguments for the binding
452 * @return GNUNET_SYSERR on error, GNUNET_OK on success
453 */
454static int
455init_params (struct GNUNET_MYSQL_Context *mc,
456 struct GNUNET_MYSQL_StatementHandle *sh,
457 va_list ap)
458{
459 MYSQL_BIND qbind[MAX_PARAM];
460 unsigned int pc;
461 unsigned int off;
462 enum enum_field_types ft;
463
464 pc = mysql_stmt_param_count (sh->statement);
465 if (pc > MAX_PARAM)
466 {
467 /* increase internal constant! */
468 GNUNET_break (0);
469 return GNUNET_SYSERR;
470 }
471 memset (qbind, 0, sizeof (qbind));
472 off = 0;
473 ft = 0;
474 while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
475 {
476 qbind[off].buffer_type = ft;
477 switch (ft)
478 {
479 case MYSQL_TYPE_FLOAT:
480 qbind[off].buffer = va_arg (ap, float *);
481
482 break;
483 case MYSQL_TYPE_LONGLONG:
484 qbind[off].buffer = va_arg (ap, unsigned long long *);
485 qbind[off].is_unsigned = va_arg (ap, int);
486
487 break;
488 case MYSQL_TYPE_LONG:
489 qbind[off].buffer = va_arg (ap, unsigned int *);
490 qbind[off].is_unsigned = va_arg (ap, int);
491
492 break;
493 case MYSQL_TYPE_VAR_STRING:
494 case MYSQL_TYPE_STRING:
495 case MYSQL_TYPE_BLOB:
496 qbind[off].buffer = va_arg (ap, void *);
497 qbind[off].buffer_length = va_arg (ap, unsigned long);
498 qbind[off].length = va_arg (ap, unsigned long *);
499
500 break;
501 default:
502 /* unsupported type */
503 GNUNET_break (0);
504 return GNUNET_SYSERR;
505 }
506 pc--;
507 off++;
508 }
509 if (!((pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1)))
510 {
511 GNUNET_break (0);
512 return GNUNET_SYSERR;
513 }
514 if (mysql_stmt_bind_param (sh->statement, qbind))
515 {
516 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
517 "mysql",
518 _("`%s' failed at %s:%d with error: %s\n"),
519 "mysql_stmt_bind_param", __FILE__, __LINE__,
520 mysql_stmt_error (sh->statement));
521 iclose (mc);
522 return GNUNET_SYSERR;
523 }
524 if (mysql_stmt_execute (sh->statement))
525 {
526 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
527 "mysql",
528 _("`%s' failed at %s:%d with error: %s\n"),
529 "mysql_stmt_execute", __FILE__, __LINE__,
530 mysql_stmt_error (sh->statement));
531 iclose (mc);
532 return GNUNET_SYSERR;
533 }
534 return GNUNET_OK;
535}
536
537
538/**
539 * Run a prepared SELECT statement.
540 *
541 * @param mc mysql context
542 * @param sh handle to SELECT statment
543 * @param result_size number of elements in results array
544 * @param results pointer to already initialized MYSQL_BIND
545 * array (of sufficient size) for passing results
546 * @param processor function to call on each result
547 * @param processor_cls extra argument to processor
548 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
549 * values (size + buffer-reference for pointers); terminated
550 * with "-1"
551 * @return GNUNET_SYSERR on error, otherwise
552 * the number of successfully affected (or queried) rows
553 */
554int
555GNUNET_MYSQL_statement_run_prepared_select (struct GNUNET_MYSQL_Context *mc,
556 struct GNUNET_MYSQL_StatementHandle *sh,
557 unsigned int result_size, MYSQL_BIND * results,
558 GNUNET_MYSQL_DataProcessor processor,
559 void *processor_cls, ...)
560{
561 va_list ap;
562 int ret;
563 unsigned int rsize;
564 int total;
565
566 if (GNUNET_OK != prepare_statement (mc, sh))
567 {
568 GNUNET_break (0);
569 return GNUNET_SYSERR;
570 }
571 va_start (ap, processor_cls);
572 if (GNUNET_OK != init_params (mc, sh, ap))
573 {
574 GNUNET_break (0);
575 va_end (ap);
576 return GNUNET_SYSERR;
577 }
578 va_end (ap);
579 rsize = mysql_stmt_field_count (sh->statement);
580 if (rsize > result_size)
581 {
582 GNUNET_break (0);
583 return GNUNET_SYSERR;
584 }
585 if (mysql_stmt_bind_result (sh->statement, results))
586 {
587 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
588 "mysql",
589 _("`%s' failed at %s:%d with error: %s\n"),
590 "mysql_stmt_bind_result", __FILE__, __LINE__,
591 mysql_stmt_error (sh->statement));
592 iclose (mc);
593 return GNUNET_SYSERR;
594 }
595
596 total = 0;
597 while (1)
598 {
599 ret = mysql_stmt_fetch (sh->statement);
600 if (ret == MYSQL_NO_DATA)
601 break;
602 if (ret != 0)
603 {
604 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
605 "mysql",
606 _("`%s' failed at %s:%d with error: %s\n"),
607 "mysql_stmt_fetch", __FILE__, __LINE__,
608 mysql_stmt_error (sh->statement));
609 iclose (mc);
610 return GNUNET_SYSERR;
611 }
612 if (processor != NULL)
613 if (GNUNET_OK != processor (processor_cls, rsize, results))
614 break;
615 total++;
616 }
617 mysql_stmt_reset (sh->statement);
618 return total;
619}
620
621
622/**
623 * Run a prepared statement that does NOT produce results.
624 *
625 * @param mc mysql context
626 * @param sh handle to statment
627 * @param insert_id NULL or address where to store the row ID of whatever
628 * was inserted (only for INSERT statements!)
629 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
630 * values (size + buffer-reference for pointers); terminated
631 * with "-1"
632 * @return GNUNET_SYSERR on error, otherwise
633 * the number of successfully affected rows
634 */
635int
636GNUNET_MYSQL_statement_run_prepared (struct GNUNET_MYSQL_Context *mc,
637 struct GNUNET_MYSQL_StatementHandle *sh,
638 unsigned long long *insert_id, ...)
639{
640 va_list ap;
641 int affected;
642
643 if (GNUNET_OK != prepare_statement (mc, sh))
644 return GNUNET_SYSERR;
645 va_start (ap, insert_id);
646 if (GNUNET_OK != init_params (mc, sh, ap))
647 {
648 va_end (ap);
649 return GNUNET_SYSERR;
650 }
651 va_end (ap);
652 affected = mysql_stmt_affected_rows (sh->statement);
653 if (NULL != insert_id)
654 *insert_id = (unsigned long long) mysql_stmt_insert_id (sh->statement);
655 mysql_stmt_reset (sh->statement);
656 return affected;
657}
658
659
660/* end of mysql.c */
661