aboutsummaryrefslogtreecommitdiff
path: root/src/datacache/plugin_datacache_mysql.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/datacache/plugin_datacache_mysql.c')
-rw-r--r--src/datacache/plugin_datacache_mysql.c1092
1 files changed, 1092 insertions, 0 deletions
diff --git a/src/datacache/plugin_datacache_mysql.c b/src/datacache/plugin_datacache_mysql.c
new file mode 100644
index 000000000..4559a9d39
--- /dev/null
+++ b/src/datacache/plugin_datacache_mysql.c
@@ -0,0 +1,1092 @@
1/*
2 This file is part of GNUnet
3 (C) 2006, 2009, 2010 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/**
22 * @file datacache/plugin_datacache_mysql.c
23 * @brief mysql for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
25 *
26 * SETUP INSTRUCTIONS:
27 *
28 * 1) Access mysql as root,
29 * <pre>
30 *
31 * $ mysql -u root -p
32 *
33 * </pre>
34 * and do the following. [You should replace $USER with the username
35 * that will be running the gnunetd process].
36 * <pre>
37 *
38 CREATE DATABASE gnunet;
39 GRANT select,insert,update,delete,create,alter,drop,create temporary tables
40 ON gnunet.* TO $USER@localhost;
41 SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
42 FLUSH PRIVILEGES;
43 *
44 * </pre>
45 * 2) In the $HOME directory of $USER, create a ".my.cnf" file
46 * with the following lines
47 * <pre>
48
49 [client]
50 user=$USER
51 password=$the_password_you_like
52
53 * </pre>
54 *
55 * Thats it -- now you can configure your datastores in GNUnet to
56 * use MySQL. Note that .my.cnf file is a security risk unless its on
57 * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
58 * link. Even greater security risk can be achieved by setting no
59 * password for $USER. Luckily $USER has only priviledges to mess
60 * up GNUnet's tables, nothing else (unless you give him more,
61 * of course).<p>
62 *
63 * 3) Still, perhaps you should briefly try if the DB connection
64 * works. First, login as $USER. Then use,
65 *
66 * <pre>
67 * $ mysql -u $USER -p $the_password_you_like
68 * mysql> use gnunet;
69 * </pre>
70 *
71 * If you get the message &quot;Database changed&quot; it probably works.
72 *
73 * [If you get &quot;ERROR 2002: Can't connect to local MySQL server
74 * through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
75 * &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
76 * so there may be some additional trouble depending on your mysql setup.]
77 *
78 * REPAIRING TABLES:
79 * - Its probably healthy to check your tables for inconsistencies
80 * every now and then.
81 * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
82 * databases have been corrupted.
83 * - The tables can be verified/fixed in two ways;
84 * 1) by running mysqlcheck -A, or
85 * 2) by executing (inside of mysql using the GNUnet database):
86 * mysql> SHOW TABLES;
87 * mysql> REPAIR TABLE gnXXX;
88 *
89 * Make sure to replace XXX with the actual names of all tables.
90 *
91 * PROBLEMS?
92 *
93 * If you have problems related to the mysql module, your best
94 * friend is probably the mysql manual. The first thing to check
95 * is that mysql is basically operational, that you can connect
96 * to it, create tables, issue queries etc.
97 */
98#include "platform.h"
99#include "gnunet_util_lib.h"
100#include "plugin_datacache.h"
101#include <mysql/mysql.h>
102
103#define DEBUG_DATACACHE_MYSQL GNUNET_NO
104
105/**
106 * Estimate of the per-entry overhead (including indices).
107 */
108#define OVERHEAD ((4*2+4*2+8*2+8*2+sizeof(GNUNET_HashCode)*5+8))
109
110/**
111 * Maximum number of supported parameters for a prepared
112 * statement. Increase if needed.
113 */
114#define MAX_PARAM 16
115
116/**
117 * Die with an error message that indicates
118 * a failure of the command 'cmd' with the message given
119 * by strerror(errno).
120 */
121#define DIE_MYSQL(cmd, dbh) do { GNUNET_log(GNUNET_ERROR_TYPE__ERROR, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); abort(); } while(0);
122
123/**
124 * Log an error message at log-level 'level' that indicates
125 * a failure of the command 'cmd' on file 'filename'
126 * with the message given by strerror(errno).
127 */
128#define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log(level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);
129
130struct GNUNET_MysqlStatementHandle
131{
132 struct GNUNET_MysqlStatementHandle *next;
133
134 struct GNUNET_MysqlStatementHandle *prev;
135
136 char *query;
137
138 MYSQL_STMT *statement;
139
140 int valid;
141
142};
143
144
145/**
146 * Context for all functions in this plugin.
147 */
148struct Plugin
149{
150 /**
151 * Our execution environment.
152 */
153 struct GNUNET_DATACACHE_PluginEnvironment *env;
154
155 /**
156 * Handle to the mysql database.
157 */
158 MYSQL *dbf;
159
160 struct GNUNET_MysqlStatementHandle *shead;
161
162 struct GNUNET_MysqlStatementHandle *stail;
163
164 /**
165 * Filename of "my.cnf" (msyql configuration).
166 */
167 char *cnffile;
168
169#define SELECT_VALUE_STMT "SELECT value,expire FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ? LIMIT 1 OFFSET ?"
170 struct GNUNET_MysqlStatementHandle *select_value;
171
172#define COUNT_VALUE_STMT "SELECT count(*) FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ?"
173 struct GNUNET_MysqlStatementHandle *count_value;
174
175#define SELECT_OLD_VALUE_STMT "SELECT hash, vhash, type, value FROM gn080dstore FORCE INDEX (expireidx) ORDER BY puttime ASC LIMIT 1"
176 struct GNUNET_MysqlStatementHandle *select_old_value;
177
178#define DELETE_VALUE_STMT "DELETE FROM gn080dstore WHERE hash = ? AND vhash = ? AND type = ? AND value = ?"
179 struct GNUNET_MysqlStatementHandle *delete_value;
180
181#define INSERT_VALUE_STMT "INSERT INTO gn080dstore (type, puttime, expire, hash, vhash, value) "\
182 "VALUES (?, ?, ?, ?, ?, ?)"
183 struct GNUNET_MysqlStatementHandle *insert_value;
184
185#define UPDATE_VALUE_STMT "UPDATE gn080dstore FORCE INDEX (allidx) SET puttime=?, expire=? "\
186 "WHERE hash=? AND vhash=? AND type=?"
187 struct GNUNET_MysqlStatementHandle *update_value;
188
189};
190
191
192/**
193 * Obtain the location of ".my.cnf".
194 * @return NULL on error
195 */
196static char *
197get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg)
198{
199 char *cnffile;
200 char *home_dir;
201 struct stat st;
202#ifndef WINDOWS
203 struct passwd *pw;
204#endif
205 int configured;
206
207#ifndef WINDOWS
208 pw = getpwuid (getuid ());
209 if (!pw)
210 {
211 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
212 "getpwuid");
213 return NULL;
214 }
215 if (GNUNET_YES ==
216 GNUNET_CONFIGURATION_have_value (cfg,
217 "datacache-mysql", "CONFIG"))
218 {
219 GNUNET_assert (GNUNET_OK ==
220 GNUNET_CONFIGURATION_get_value_filename (cfg,
221 "datacache-mysql", "CONFIG", &cnffile));
222 configured = GNUNET_YES;
223 }
224 else
225 {
226 home_dir = GNUNET_strdup (pw->pw_dir);
227#else
228 home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
229 plibc_conv_to_win_path ("~/", home_dir);
230#endif
231 GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
232 GNUNET_free (home_dir);
233 configured = GNUNET_NO;
234 }
235 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
236 _("Trying to use file `%s' for MySQL configuration.\n"),
237 cnffile);
238 if ((0 != STAT (cnffile, &st)) ||
239 (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
240 {
241 if (configured == GNUNET_YES)
242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
243 _("Could not access file `%s': %s\n"), cnffile,
244 STRERROR (errno));
245 GNUNET_free (cnffile);
246 return NULL;
247 }
248 return cnffile;
249}
250
251
252/**
253 * Free a prepared statement.
254 */
255static void
256prepared_statement_destroy (struct Plugin *plugin,
257 struct GNUNET_MysqlStatementHandle
258 *s)
259{
260 GNUNET_CONTAINER_DLL_remove (plugin->shead,
261 plugin->stail,
262 s);
263 if (s->valid)
264 mysql_stmt_close (s->statement);
265 GNUNET_free (s->query);
266 GNUNET_free (s);
267}
268
269
270/**
271 * Close database connection and all prepared statements (we got a DB
272 * disconnect error).
273 */
274static int
275iclose (struct Plugin *plugin)
276{
277 struct GNUNET_MysqlStatementHandle *spos;
278
279 spos = plugin->shead;
280 while (NULL != plugin->shead)
281 prepared_statement_destroy (plugin,
282 plugin->shead);
283 if (plugin->dbf != NULL)
284 {
285 mysql_close (plugin->dbf);
286 plugin->dbf = NULL;
287 }
288 return GNUNET_OK;
289}
290
291
292/**
293 * Open the connection with the database (and initialize
294 * our default options).
295 *
296 * @return GNUNET_OK on success
297 */
298static int
299iopen (struct Plugin *ret)
300{
301 char *mysql_dbname;
302 char *mysql_server;
303 char *mysql_user;
304 char *mysql_password;
305 unsigned long long mysql_port;
306 my_bool reconnect;
307 unsigned int timeout;
308
309 ret->dbf = mysql_init (NULL);
310 if (ret->dbf == NULL)
311 return GNUNET_SYSERR;
312 if (ret->cnffile != NULL)
313 mysql_options (ret->dbf, MYSQL_READ_DEFAULT_FILE, ret->cnffile);
314 mysql_options (ret->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
315 reconnect = 0;
316 mysql_options (ret->dbf, MYSQL_OPT_RECONNECT, &reconnect);
317 mysql_options (ret->dbf,
318 MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
319 mysql_options(ret->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
320 timeout = 60; /* in seconds */
321 mysql_options (ret->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
322 mysql_options (ret->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
323 mysql_dbname = NULL;
324 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
325 "datacache-mysql", "DATABASE"))
326 GNUNET_assert (GNUNET_OK ==
327 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
328 "datacache-mysql", "DATABASE",
329 &mysql_dbname));
330 else
331 mysql_dbname = GNUNET_strdup ("gnunet");
332 mysql_user = NULL;
333 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
334 "datacache-mysql", "USER"))
335 {
336 GNUNET_assert (GNUNET_OK ==
337 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
338 "datacache-mysql", "USER",
339 &mysql_user));
340 }
341 mysql_password = NULL;
342 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
343 "datacache-mysql", "PASSWORD"))
344 {
345 GNUNET_assert (GNUNET_OK ==
346 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
347 "datacache-mysql", "PASSWORD",
348 &mysql_password));
349 }
350 mysql_server = NULL;
351 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
352 "datacache-mysql", "HOST"))
353 {
354 GNUNET_assert (GNUNET_OK ==
355 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
356 "datacache-mysql", "HOST",
357 &mysql_server));
358 }
359 mysql_port = 0;
360 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
361 "datacache-mysql", "PORT"))
362 {
363 GNUNET_assert (GNUNET_OK ==
364 GNUNET_CONFIGURATION_get_value_number (ret->env->cfg, "datacache-mysql",
365 "PORT", &mysql_port));
366 }
367
368 GNUNET_assert (mysql_dbname != NULL);
369 mysql_real_connect (ret->dbf, mysql_server, mysql_user, mysql_password,
370 mysql_dbname, (unsigned int) mysql_port, NULL,
371 CLIENT_IGNORE_SIGPIPE);
372 GNUNET_free_non_null (mysql_server);
373 GNUNET_free_non_null (mysql_user);
374 GNUNET_free_non_null (mysql_password);
375 GNUNET_free (mysql_dbname);
376 if (mysql_error (ret->dbf)[0])
377 {
378 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
379 "mysql_real_connect", ret);
380 return GNUNET_SYSERR;
381 }
382 return GNUNET_OK;
383}
384
385
386/**
387 * Run the given MySQL statement.
388 *
389 * @return GNUNET_OK on success, GNUNET_SYSERR on error
390 */
391static int
392run_statement (struct Plugin *plugin,
393 const char *statement)
394{
395 if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
396 return GNUNET_SYSERR;
397 mysql_query (plugin->dbf, statement);
398 if (mysql_error (plugin->dbf)[0])
399 {
400 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
401 "mysql_query", plugin);
402 iclose (plugin);
403 return GNUNET_SYSERR;
404 }
405 return GNUNET_OK;
406}
407
408/**
409 * Create a prepared statement.
410 *
411 * @return NULL on error
412 */
413static struct GNUNET_MysqlStatementHandle *
414prepared_statement_create (struct Plugin *plugin,
415 const char *statement)
416{
417 struct GNUNET_MysqlStatementHandle *ret;
418
419 ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
420 ret->query = GNUNET_strdup (statement);
421 GNUNET_CONTAINER_DLL_insert (plugin->shead,
422 plugin->stail,
423 ret);
424 return ret;
425}
426
427
428/**
429 * Prepare a statement for running.
430 *
431 * @return GNUNET_OK on success
432 */
433static int
434prepare_statement (struct Plugin *plugin,
435 struct GNUNET_MysqlStatementHandle *ret)
436{
437 if (GNUNET_YES == ret->valid)
438 return GNUNET_OK;
439 if ((NULL == plugin->dbf) &&
440 (GNUNET_OK != iopen (plugin)))
441 return GNUNET_SYSERR;
442 ret->statement = mysql_stmt_init (plugin->dbf);
443 if (ret->statement == NULL)
444 {
445 iclose (plugin);
446 return GNUNET_SYSERR;
447 }
448 if (mysql_stmt_prepare (ret->statement,
449 ret->query,
450 strlen (ret->query)))
451 {
452 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
453 "mysql_stmt_prepare",
454 plugin);
455 mysql_stmt_close (ret->statement);
456 ret->statement = NULL;
457 iclose (plugin);
458 return GNUNET_SYSERR;
459 }
460 ret->valid = GNUNET_YES;
461 return GNUNET_OK;
462
463}
464
465
466/**
467 * Bind the parameters for the given MySQL statement
468 * and run it.
469 *
470 * @param s statement to bind and run
471 * @param ap arguments for the binding
472 * @return GNUNET_SYSERR on error, GNUNET_OK on success
473 */
474static int
475init_params (struct Plugin *plugin,
476 struct GNUNET_MysqlStatementHandle *s,
477 va_list ap)
478{
479 MYSQL_BIND qbind[MAX_PARAM];
480 unsigned int pc;
481 unsigned int off;
482 enum enum_field_types ft;
483
484 pc = mysql_stmt_param_count (s->statement);
485 if (pc > MAX_PARAM)
486 {
487 /* increase internal constant! */
488 GNUNET_break (0);
489 return GNUNET_SYSERR;
490 }
491 memset (qbind, 0, sizeof (qbind));
492 off = 0;
493 ft = 0;
494 while ((pc > 0) && (-1 != (ft = va_arg (ap, enum enum_field_types))))
495 {
496 qbind[off].buffer_type = ft;
497 switch (ft)
498 {
499 case MYSQL_TYPE_FLOAT:
500 qbind[off].buffer = va_arg (ap, float *);
501 break;
502 case MYSQL_TYPE_LONGLONG:
503 qbind[off].buffer = va_arg (ap, unsigned long long *);
504 qbind[off].is_unsigned = va_arg (ap, int);
505 break;
506 case MYSQL_TYPE_LONG:
507 qbind[off].buffer = va_arg (ap, unsigned int *);
508 qbind[off].is_unsigned = va_arg (ap, int);
509 break;
510 case MYSQL_TYPE_VAR_STRING:
511 case MYSQL_TYPE_STRING:
512 case MYSQL_TYPE_BLOB:
513 qbind[off].buffer = va_arg (ap, void *);
514 qbind[off].buffer_length = va_arg (ap, unsigned long);
515 qbind[off].length = va_arg (ap, unsigned long *);
516 break;
517 default:
518 /* unsupported type */
519 GNUNET_break (0);
520 return GNUNET_SYSERR;
521 }
522 pc--;
523 off++;
524 }
525 if (!((pc == 0) && (ft != -1) && (va_arg (ap, int) == -1)))
526 {
527 GNUNET_break (0);
528 return GNUNET_SYSERR;
529 }
530 if (mysql_stmt_bind_param (s->statement, qbind))
531 {
532 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
533 _("`%s' failed at %s:%d with error: %s\n"),
534 "mysql_stmt_bind_param",
535 __FILE__, __LINE__, mysql_stmt_error (s->statement));
536 iclose (plugin);
537 return GNUNET_SYSERR;
538 }
539 if (mysql_stmt_execute (s->statement))
540 {
541 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
542 _("`%s' failed at %s:%d with error: %s\n"),
543 "mysql_stmt_execute",
544 __FILE__, __LINE__, mysql_stmt_error (s->statement));
545 iclose (plugin);
546 return GNUNET_SYSERR;
547 }
548 return GNUNET_OK;
549}
550
551/**
552 * Type of a callback that will be called for each
553 * data set returned from MySQL.
554 *
555 * @param cls user-defined argument
556 * @param num_values number of elements in values
557 * @param values values returned by MySQL
558 * @return GNUNET_OK to continue iterating, GNUNET_SYSERR to abort
559 */
560typedef int (*GNUNET_MysqlDataProcessor) (void *cls,
561 unsigned int num_values,
562 MYSQL_BIND * values);
563
564
565/**
566 * Run a prepared SELECT statement.
567 *
568 * @param result_size number of elements in results array
569 * @param results pointer to already initialized MYSQL_BIND
570 * array (of sufficient size) for passing results
571 * @param processor function to call on each result
572 * @param processor_cls extra argument to processor
573 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
574 * values (size + buffer-reference for pointers); terminated
575 * with "-1"
576 * @return GNUNET_SYSERR on error, otherwise
577 * the number of successfully affected (or queried) rows
578 */
579static int
580prepared_statement_run_select (struct Plugin *plugin,
581 struct GNUNET_MysqlStatementHandle
582 *s,
583 unsigned int result_size,
584 MYSQL_BIND * results,
585 GNUNET_MysqlDataProcessor
586 processor, void *processor_cls,
587 ...)
588{
589 va_list ap;
590 int ret;
591 unsigned int rsize;
592 int total;
593
594 if (GNUNET_OK != prepare_statement (plugin, s))
595 {
596 GNUNET_break (0);
597 return GNUNET_SYSERR;
598 }
599 va_start (ap, processor_cls);
600 if (GNUNET_OK != init_params (plugin, s, ap))
601 {
602 GNUNET_break (0);
603 va_end (ap);
604 return GNUNET_SYSERR;
605 }
606 va_end (ap);
607 rsize = mysql_stmt_field_count (s->statement);
608 if (rsize > result_size)
609 {
610 GNUNET_break (0);
611 return GNUNET_SYSERR;
612 }
613 if (mysql_stmt_bind_result (s->statement, results))
614 {
615 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
616 _("`%s' failed at %s:%d with error: %s\n"),
617 "mysql_stmt_bind_result",
618 __FILE__, __LINE__, mysql_stmt_error (s->statement));
619 iclose (plugin);
620 return GNUNET_SYSERR;
621 }
622
623 total = 0;
624 while (1)
625 {
626 ret = mysql_stmt_fetch (s->statement);
627 if (ret == MYSQL_NO_DATA)
628 break;
629 if (ret != 0)
630 {
631 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
632 _("`%s' failed at %s:%d with error: %s\n"),
633 "mysql_stmt_fetch",
634 __FILE__, __LINE__, mysql_stmt_error (s->statement));
635 iclose (plugin);
636 return GNUNET_SYSERR;
637 }
638 if (processor != NULL)
639 if (GNUNET_OK != processor (processor_cls, rsize, results))
640 break;
641 total++;
642 }
643 mysql_stmt_reset (s->statement);
644 return total;
645}
646
647
648
649/**
650 * Run a prepared statement that does NOT produce results.
651 *
652 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
653 * values (size + buffer-reference for pointers); terminated
654 * with "-1"
655 * @param insert_id NULL or address where to store the row ID of whatever
656 * was inserted (only for INSERT statements!)
657 * @return GNUNET_SYSERR on error, otherwise
658 * the number of successfully affected rows
659 */
660static int
661prepared_statement_run (struct Plugin *plugin,
662 struct GNUNET_MysqlStatementHandle *s,
663 unsigned long long *insert_id, ...)
664{
665 va_list ap;
666 int affected;
667
668 if (GNUNET_OK != prepare_statement (plugin, s))
669 return GNUNET_SYSERR;
670 va_start (ap, insert_id);
671 if (GNUNET_OK != init_params (plugin, s, ap))
672 {
673 va_end (ap);
674 return GNUNET_SYSERR;
675 }
676 va_end (ap);
677 affected = mysql_stmt_affected_rows (s->statement);
678 if (NULL != insert_id)
679 *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
680 mysql_stmt_reset (s->statement);
681 return affected;
682}
683
684
685static int
686itable (struct Plugin *plugin)
687{
688#define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
689 if (MRUNS ("CREATE TEMPORARY TABLE gn080dstore ("
690 " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
691 " puttime BIGINT UNSIGNED NOT NULL DEFAULT 0,"
692 " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
693 " hash BINARY(64) NOT NULL DEFAULT '',"
694 " vhash BINARY(64) NOT NULL DEFAULT '',"
695 " value BLOB NOT NULL DEFAULT '',"
696 " INDEX hashidx (hash(64),type,expire),"
697 " INDEX allidx (hash(64),vhash(64),type),"
698 " INDEX expireidx (puttime)" ") ENGINE=InnoDB") ||
699 MRUNS ("SET AUTOCOMMIT = 1"))
700 return GNUNET_SYSERR;
701#undef MRUNS
702#define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
703 if (PINIT (plugin->select_value, SELECT_VALUE_STMT) ||
704 PINIT (plugin->count_value, COUNT_VALUE_STMT) ||
705 PINIT (plugin->select_old_value, SELECT_OLD_VALUE_STMT) ||
706 PINIT (plugin->delete_value, DELETE_VALUE_STMT) ||
707 PINIT (plugin->insert_value, INSERT_VALUE_STMT) ||
708 PINIT (plugin->update_value, UPDATE_VALUE_STMT))
709 return GNUNET_SYSERR;
710#undef PINIT
711 return GNUNET_OK;
712}
713
714
715/**
716 * Store an item in the datastore.
717 *
718 * @param cls closure (our "struct Plugin")
719 * @param key key to store data under
720 * @param size number of bytes in data
721 * @param data data to store
722 * @param type type of the value
723 * @param discard_time when to discard the value in any case
724 * @return 0 on error, number of bytes used otherwise
725 */
726static uint32_t
727mysql_plugin_put (void *cls,
728 const GNUNET_HashCode * key,
729 uint32_t size,
730 const char *data,
731 enum GNUNET_BLOCK_Type type,
732 struct GNUNET_TIME_Absolute discard_time)
733{
734 struct Plugin *plugin = cls;
735 struct GNUNET_TIME_Absolute now;
736 unsigned long k_length;
737 unsigned long h_length;
738 unsigned long v_length;
739 unsigned long long v_now;
740 unsigned long long v_discard_time;
741 unsigned int v_type;
742 GNUNET_HashCode vhash;
743 int ret;
744
745 if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE)
746 return GNUNET_SYSERR;
747 GNUNET_CRYPTO_hash (data, size, &vhash);
748 now = GNUNET_TIME_absolute_get ();
749
750 /* first try UPDATE */
751 h_length = sizeof (GNUNET_HashCode);
752 k_length = sizeof (GNUNET_HashCode);
753 v_length = size;
754 v_type = type;
755 v_now = (unsigned long long) now.value;
756 v_discard_time = (unsigned long long) discard_time.value;
757 if (GNUNET_OK ==
758 prepared_statement_run (plugin,
759 plugin->update_value,
760 NULL,
761 MYSQL_TYPE_LONGLONG,
762 &v_now,
763 GNUNET_YES,
764 MYSQL_TYPE_LONGLONG,
765 &v_discard_time,
766 GNUNET_YES,
767 MYSQL_TYPE_BLOB,
768 key,
769 sizeof (GNUNET_HashCode),
770 &k_length,
771 MYSQL_TYPE_BLOB,
772 &vhash,
773 sizeof (GNUNET_HashCode),
774 &h_length,
775 MYSQL_TYPE_LONG,
776 &v_type,
777 GNUNET_YES, -1))
778 return GNUNET_OK;
779
780 /* now try INSERT */
781 h_length = sizeof (GNUNET_HashCode);
782 k_length = sizeof (GNUNET_HashCode);
783 v_length = size;
784 if (GNUNET_OK !=
785 (ret = prepared_statement_run (plugin,
786 plugin->insert_value,
787 NULL,
788 MYSQL_TYPE_LONG,
789 &type,
790 GNUNET_YES,
791 MYSQL_TYPE_LONGLONG,
792 &v_now,
793 GNUNET_YES,
794 MYSQL_TYPE_LONGLONG,
795 &v_discard_time,
796 GNUNET_YES,
797 MYSQL_TYPE_BLOB,
798 key,
799 sizeof (GNUNET_HashCode),
800 &k_length,
801 MYSQL_TYPE_BLOB,
802 &vhash,
803 sizeof (GNUNET_HashCode),
804 &h_length,
805 MYSQL_TYPE_BLOB,
806 data,
807 (unsigned long) size,
808 &v_length, -1)))
809 {
810 if (ret == GNUNET_SYSERR)
811 itable (plugin);
812 return GNUNET_SYSERR;
813 }
814 return size + OVERHEAD;
815}
816
817
818static int
819return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
820{
821 return GNUNET_OK;
822}
823
824
825/**
826 * Iterate over the results for a particular key
827 * in the datastore.
828 *
829 * @param cls closure (our "struct Plugin")
830 * @param key
831 * @param type entries of which type are relevant?
832 * @param iter maybe NULL (to just count)
833 * @param iter_cls closure for iter
834 * @return the number of results found
835 */
836static unsigned int
837mysql_plugin_get (void *cls,
838 const GNUNET_HashCode * key,
839 enum GNUNET_BLOCK_Type type,
840 GNUNET_DATACACHE_Iterator iter,
841 void *iter_cls)
842{
843 struct Plugin *plugin = cls;
844 MYSQL_BIND rbind[3];
845 unsigned long h_length;
846 unsigned long v_length;
847 unsigned long long v_expire;
848 struct GNUNET_TIME_Absolute now;
849 struct GNUNET_TIME_Absolute expire;
850 unsigned int cnt;
851 unsigned long long total;
852 unsigned long long v_now;
853 unsigned int off;
854 unsigned int v_type;
855 int ret;
856 char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
857
858 now = GNUNET_TIME_absolute_get ();
859 h_length = sizeof (GNUNET_HashCode);
860 v_length = sizeof (buffer);
861 total = -1;
862 memset (rbind, 0, sizeof (rbind));
863 rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
864 rbind[0].buffer = &total;
865 rbind[0].is_unsigned = GNUNET_YES;
866 v_type = type;
867 v_now = (unsigned long long) now.value;
868 if ((GNUNET_OK !=
869 (ret = prepared_statement_run_select (plugin,
870 plugin->count_value,
871 1,
872 rbind,
873 return_ok,
874 NULL,
875 MYSQL_TYPE_BLOB,
876 key,
877 sizeof
878 (GNUNET_HashCode),
879 &h_length,
880 MYSQL_TYPE_LONG,
881 &v_type, GNUNET_YES,
882 MYSQL_TYPE_LONGLONG,
883 &v_now, GNUNET_YES,
884 -1)))
885 || (-1 == total))
886 {
887 if (ret == GNUNET_SYSERR)
888 itable (plugin);
889 return GNUNET_SYSERR;
890 }
891 if ((iter == NULL) || (total == 0))
892 return (int) total;
893
894 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
895 cnt = 0;
896 while (cnt < total)
897 {
898 memset (rbind, 0, sizeof (rbind));
899 rbind[0].buffer_type = MYSQL_TYPE_BLOB;
900 rbind[0].buffer_length = sizeof (buffer);
901 rbind[0].length = &v_length;
902 rbind[0].buffer = buffer;
903 rbind[1].buffer_type = MYSQL_TYPE_LONGLONG;
904 rbind[1].is_unsigned = 1;
905 rbind[1].buffer = &v_expire;
906 off = (off + 1) % total;
907 if (GNUNET_OK !=
908 (ret = prepared_statement_run_select (plugin,
909 plugin->select_value,
910 2,
911 rbind,
912 return_ok,
913 NULL,
914 MYSQL_TYPE_BLOB,
915 key,
916 sizeof
917 (GNUNET_HashCode),
918 &h_length,
919 MYSQL_TYPE_LONG,
920 &v_type,
921 GNUNET_YES,
922 MYSQL_TYPE_LONGLONG,
923 &v_now,
924 GNUNET_YES,
925 MYSQL_TYPE_LONG,
926 &off,
927 GNUNET_YES,
928 -1)))
929 {
930 if (ret == GNUNET_SYSERR)
931 itable (plugin);
932 return GNUNET_SYSERR;
933 }
934 cnt++;
935 expire.value = v_expire;
936 if (GNUNET_OK != iter (iter_cls,
937 expire,
938 key,
939 v_length, buffer,
940 type))
941 break;
942 }
943 return cnt;
944}
945
946
947/**
948 * Delete the entry with the lowest expiration value
949 * from the datacache right now.
950 *
951 * @param cls closure (our "struct Plugin")
952 * @return GNUNET_OK on success, GNUNET_SYSERR on error
953 */
954static int
955mysql_plugin_del (void *cls)
956{
957 struct Plugin *plugin = cls;
958
959 MYSQL_BIND rbind[5];
960 unsigned int v_type;
961 GNUNET_HashCode v_key;
962 GNUNET_HashCode vhash;
963 unsigned long k_length;
964 unsigned long h_length;
965 unsigned long v_length;
966 int ret;
967 char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
968
969 k_length = sizeof (GNUNET_HashCode);
970 h_length = sizeof (GNUNET_HashCode);
971 v_length = sizeof (buffer);
972 memset (rbind, 0, sizeof (rbind));
973 rbind[0].buffer_type = MYSQL_TYPE_BLOB;
974 rbind[0].buffer_length = sizeof (GNUNET_HashCode);
975 rbind[0].length = &k_length;
976 rbind[0].buffer = &v_key;
977 rbind[1].buffer_type = MYSQL_TYPE_BLOB;
978 rbind[1].buffer_length = sizeof (GNUNET_HashCode);
979 rbind[1].length = &h_length;
980 rbind[1].buffer = &vhash;
981 rbind[2].buffer_type = MYSQL_TYPE_LONG;
982 rbind[2].is_unsigned = 1;
983 rbind[2].buffer = &v_type;
984 rbind[3].buffer_type = MYSQL_TYPE_BLOB;
985 rbind[3].buffer_length = sizeof (buffer);
986 rbind[3].length = &v_length;
987 rbind[3].buffer = buffer;
988 if ((GNUNET_OK !=
989 (ret = prepared_statement_run_select (plugin,
990 plugin->select_old_value,
991 4,
992 rbind,
993 return_ok,
994 NULL,
995 -1))) ||
996 (GNUNET_OK !=
997 (ret = prepared_statement_run (plugin,
998 plugin->delete_value,
999 NULL,
1000 MYSQL_TYPE_BLOB,
1001 &v_key,
1002 sizeof (GNUNET_HashCode),
1003 &k_length,
1004 MYSQL_TYPE_BLOB,
1005 &vhash,
1006 sizeof (GNUNET_HashCode),
1007 &h_length,
1008 MYSQL_TYPE_LONG,
1009 &v_type,
1010 GNUNET_YES,
1011 MYSQL_TYPE_BLOB,
1012 buffer,
1013 (unsigned long)
1014 sizeof (buffer),
1015 &v_length, -1))))
1016 {
1017 if (ret == GNUNET_SYSERR)
1018 itable (plugin);
1019 return GNUNET_SYSERR;
1020 }
1021 plugin->env->delete_notify (plugin->env->cls,
1022 &v_key,
1023 v_length + OVERHEAD);
1024
1025 return GNUNET_OK;
1026}
1027
1028
1029/**
1030 * Entry point for the plugin.
1031 *
1032 * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
1033 * @return the plugin's closure (our "struct Plugin")
1034 */
1035void *
1036libgnunet_plugin_datacache_mysql_init (void *cls)
1037{
1038 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
1039 struct GNUNET_DATACACHE_PluginFunctions *api;
1040 struct Plugin *plugin;
1041
1042 plugin = GNUNET_malloc (sizeof (struct Plugin));
1043 plugin->env = env;
1044 plugin->cnffile = get_my_cnf_path (env->cfg);
1045 if (GNUNET_OK !=
1046 iopen (plugin))
1047 {
1048 GNUNET_free_non_null (plugin->cnffile);
1049 GNUNET_free (plugin);
1050 return NULL;
1051 }
1052 if (GNUNET_OK !=
1053 itable (plugin))
1054 {
1055 iclose (plugin);
1056 GNUNET_free_non_null (plugin->cnffile);
1057 GNUNET_free (plugin);
1058 return NULL;
1059 }
1060 api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
1061 api->cls = plugin;
1062 api->get = &mysql_plugin_get;
1063 api->put = &mysql_plugin_put;
1064 api->del = &mysql_plugin_del;
1065 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1066 "mysql", _("MySQL datacache running\n"));
1067 return api;
1068}
1069
1070
1071/**
1072 * Exit point from the plugin.
1073 *
1074 * @param cls closure (our "struct Plugin")
1075 * @return NULL
1076 */
1077void *
1078libgnunet_plugin_datacache_mysql_done (void *cls)
1079{
1080 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
1081 struct Plugin *plugin = api->cls;
1082
1083 iclose (plugin);
1084 GNUNET_free_non_null (plugin->cnffile);
1085 GNUNET_free (plugin);
1086 GNUNET_free (api);
1087 mysql_library_end ();
1088 return NULL;
1089}
1090
1091
1092/* end of plugin_datacache_mysql.c */