aboutsummaryrefslogtreecommitdiff
path: root/src/datastore
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2010-07-24 15:23:45 +0000
committerChristian Grothoff <christian@grothoff.org>2010-07-24 15:23:45 +0000
commit12d1bb08b090ea81140656e3b10a0bc095c4b8e1 (patch)
tree66e6aebe8cff3ac311f261df15ff68b6d2f926e4 /src/datastore
parent5b767735eab63f6dc171cbb5161c3ad600a09631 (diff)
downloadgnunet-12d1bb08b090ea81140656e3b10a0bc095c4b8e1.tar.gz
gnunet-12d1bb08b090ea81140656e3b10a0bc095c4b8e1.zip
init
Diffstat (limited to 'src/datastore')
-rw-r--r--src/datastore/plugin_datastore_mysql.c1786
1 files changed, 1786 insertions, 0 deletions
diff --git a/src/datastore/plugin_datastore_mysql.c b/src/datastore/plugin_datastore_mysql.c
new file mode 100644
index 000000000..03fe06b93
--- /dev/null
+++ b/src/datastore/plugin_datastore_mysql.c
@@ -0,0 +1,1786 @@
1/*
2 This file is part of GNUnet
3 (C) 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 datastore/plugin_datastore_mysql.c
23 * @brief mysql-based datastore backend
24 * @author Igor Wronsky
25 * @author Christian Grothoff
26 *
27 * NOTE: This db module does NOT work with mysql prior to 4.1 since
28 * it uses prepared statements. MySQL 5.0.46 promises to fix a bug
29 * in MyISAM that is causing us grief. At the time of this writing,
30 * that version is yet to be released. In anticipation, the code
31 * will use MyISAM with 5.0.46 (and higher). If you run such a
32 * version, please run "make check" to verify that the MySQL bug
33 * was actually fixed in your version (and if not, change the
34 * code below to use MyISAM for gn071).
35 *
36 * HIGHLIGHTS
37 *
38 * Pros
39 * + On up-to-date hardware where mysql can be used comfortably, this
40 * module will have better performance than the other db choices
41 * (according to our tests).
42 * + Its often possible to recover the mysql database from internal
43 * inconsistencies. The other db choices do not support repair!
44 * Cons
45 * - Memory usage (Comment: "I have 1G and it never caused me trouble")
46 * - Manual setup
47 *
48 * MANUAL SETUP INSTRUCTIONS
49 *
50 * 1) in /etc/gnunet.conf, set
51 * <pre>
52 *
53 * sqstore = "sqstore_mysql"
54 *
55 * </pre>
56 * 2) Then access mysql as root,
57 * <pre>
58 *
59 * $ mysql -u root -p
60 *
61 * </pre>
62 * and do the following. [You should replace $USER with the username
63 * that will be running the gnunetd process].
64 * <pre>
65 *
66 CREATE DATABASE gnunet;
67 GRANT select,insert,update,delete,create,alter,drop,create temporary tables
68 ON gnunet.* TO $USER@localhost;
69 SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
70 FLUSH PRIVILEGES;
71 *
72 * </pre>
73 * 3) In the $HOME directory of $USER, create a ".my.cnf" file
74 * with the following lines
75 * <pre>
76
77 [client]
78 user=$USER
79 password=$the_password_you_like
80
81 * </pre>
82 *
83 * Thats it. Note that .my.cnf file is a security risk unless its on
84 * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
85 * link. Even greater security risk can be achieved by setting no
86 * password for $USER. Luckily $USER has only priviledges to mess
87 * up GNUnet's tables, nothing else (unless you give him more,
88 * of course).<p>
89 *
90 * 4) Still, perhaps you should briefly try if the DB connection
91 * works. First, login as $USER. Then use,
92 *
93 * <pre>
94 * $ mysql -u $USER -p $the_password_you_like
95 * mysql> use gnunet;
96 * </pre>
97 *
98 * If you get the message &quot;Database changed&quot; it probably works.
99 *
100 * [If you get &quot;ERROR 2002: Can't connect to local MySQL server
101 * through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
102 * &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
103 * so there may be some additional trouble depending on your mysql setup.]
104 *
105 * REPAIRING TABLES
106 *
107 * - Its probably healthy to check your tables for inconsistencies
108 * every now and then.
109 * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
110 * databases have been corrupted.
111 * - The tables can be verified/fixed in two ways;
112 * 1) by running mysqlcheck -A, or
113 * 2) by executing (inside of mysql using the GNUnet database):
114 * mysql> REPAIR TABLE gn080;
115 * mysql> REPAIR TABLE gn072;
116 *
117 * PROBLEMS?
118 *
119 * If you have problems related to the mysql module, your best
120 * friend is probably the mysql manual. The first thing to check
121 * is that mysql is basically operational, that you can connect
122 * to it, create tables, issue queries etc.
123 *
124 */
125
126#include "platform.h"
127#include "plugin_datastore.h"
128
129#define DEBUG_MYSQL GNUNET_NO
130
131#define MAX_DATUM_SIZE 65536
132
133/**
134 * Maximum number of supported parameters for a prepared
135 * statement. Increase if needed.
136 */
137#define MAX_PARAM 16
138
139/**
140 * Die with an error message that indicates
141 * a failure of the command 'cmd' with the message given
142 * by strerror(errno).
143 */
144#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);
145
146/**
147 * Log an error message at log-level 'level' that indicates
148 * a failure of the command 'cmd' on file 'filename'
149 * with the message given by strerror(errno).
150 */
151#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);
152
153
154/* warning, slighly crazy mysql statements ahead. Essentially, MySQL does not handle
155 "OR" very well, so we need to use UNION instead. And UNION does not
156 automatically apply a LIMIT on the outermost clause, so we need to
157 repeat ourselves quite a bit. All hail the performance gods (and thanks
158 to #mysql on freenode) */
159#define SELECT_IT_LOW_PRIORITY "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey > ?) "\
160 "ORDER BY prio ASC,vkey ASC LIMIT 1) "\
161 "UNION "\
162 "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio > ? AND vkey != ?)"\
163 "ORDER BY prio ASC,vkey ASC LIMIT 1)"\
164 "ORDER BY prio ASC,vkey ASC LIMIT 1"
165
166#define SELECT_IT_NON_ANONYMOUS "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey < ?)"\
167 " AND anonLevel=0 ORDER BY prio DESC,vkey DESC LIMIT 1) "\
168 "UNION "\
169 "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio < ? AND vkey != ?)"\
170 " AND anonLevel=0 ORDER BY prio DESC,vkey DESC LIMIT 1) "\
171 "ORDER BY prio DESC,vkey DESC LIMIT 1"
172
173#define SELECT_IT_EXPIRATION_TIME "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey > ?) "\
174 "ORDER BY expire ASC,vkey ASC LIMIT 1) "\
175 "UNION "\
176 "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire > ? AND vkey != ?) "\
177 "ORDER BY expire ASC,vkey ASC LIMIT 1)"\
178 "ORDER BY expire ASC,vkey ASC LIMIT 1"
179
180
181#define SELECT_IT_MIGRATION_ORDER "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey < ?)"\
182 " AND expire > ? AND type!=3"\
183 " ORDER BY expire DESC,vkey DESC LIMIT 1) "\
184 "UNION "\
185 "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire < ? AND vkey != ?)"\
186 " AND expire > ? AND type!=3"\
187 " ORDER BY expire DESC,vkey DESC LIMIT 1)"\
188 "ORDER BY expire DESC,vkey DESC LIMIT 1"
189
190#define SELECT_SIZE "SELECT sum(size) FROM gn080"
191
192
193struct GNUNET_MysqlStatementHandle
194{
195 struct GNUNET_MysqlStatementHandle *next;
196
197 struct GNUNET_MysqlStatementHandle *prev;
198
199 char *query;
200
201 MYSQL_STMT *statement;
202
203 int valid;
204
205};
206
207
208/**
209 * Context for all functions in this plugin.
210 */
211struct Plugin
212{
213 /**
214 * Our execution environment.
215 */
216 struct GNUNET_DATASTORE_PluginEnvironment *env;
217
218 MYSQL *dbf;
219
220 struct GNUNET_MysqlStatementHandle *shead;
221
222 struct GNUNET_MysqlStatementHandle *stail;
223
224 /**
225 * Filename of "my.cnf" (msyql configuration).
226 */
227 char *cnffile;
228
229 /**
230 * Statements dealing with gn072 table
231 */
232#define SELECT_VALUE "SELECT value FROM gn072 WHERE vkey=?"
233 struct GNUNET_MysqlStatementHandle *select_value;
234
235#define DELETE_VALUE "DELETE FROM gn072 WHERE vkey=?"o
236 struct GNUNET_MysqlStatementHandle *delete_value;
237
238#define INSERT_VALUE "INSERT INTO gn072 (value) VALUES (?)"
239 struct GNUNET_MysqlStatementHandle *insert_value;
240
241 /**
242 * Statements dealing with gn080 table
243 */
244#define INSERT_ENTRY "INSERT INTO gn080 (size,type,prio,anonLevel,expire,hash,vhash,vkey) VALUES (?,?,?,?,?,?,?,?)"
245 struct GNUNET_MysqlStatementHandle *insert_entry;
246
247#define DELETE_ENTRY_BY_VKEY "DELETE FROM gn080 WHERE vkey=?"
248 struct GNUNET_MysqlStatementHandle *delete_entry_by_vkey;
249
250#define SELECT_ENTRY_BY_HASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
251 struct GNUNET_MysqlStatementHandle *select_entry_by_hash;
252
253#define SELECT_ENTRY_BY_HASH_AND_VHASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
254 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_vhash;
255
256#define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
257 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_type;
258
259#define SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?"
260 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_vhash_and_type;
261
262#define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=?"
263 struct GNUNET_MysqlStatementHandle *count_entry_by_hash;
264
265#define COUNT_ENTRY_BY_HASH_AND_VHASH "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=?"
266 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_vhash;
267
268#define COUNT_ENTRY_BY_HASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=? AND type=?"
269 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_type;
270
271#define COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash) WHERE hash=? AND vhash=? AND type=?"
272 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_vhash_and_type;
273
274#define UPDATE_ENTRY "UPDATE gn080 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE vkey=?"
275 struct GNUNET_MysqlStatementHandle *update_entry;
276
277 struct GNUNET_MysqlStatementHandle *iter[4];
278
279 //static unsigned int stat_size;
280
281 /**
282 * Size of the mysql database on disk.
283 */
284 unsigned long long content_size;
285
286};
287
288
289/**
290 * Obtain the location of ".my.cnf".
291 * @return NULL on error
292 */
293static char *
294get_my_cnf_path (struct GNUNET_ConfigurationHandle *cfg)
295{
296 char *cnffile;
297 char *home_dir;
298 struct stat st;
299#ifndef WINDOWS
300 struct passwd *pw;
301#endif
302
303#ifndef WINDOWS
304 pw = getpwuid (getuid ());
305 if (!pw)
306 {
307 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
308 "getpwuid");
309 return NULL;
310 }
311 home_dir = GNUNET_strdup (pw->pw_dir);
312#else
313 home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
314 plibc_conv_to_win_path ("~/", home_dir);
315#endif
316 GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
317 GNUNET_free (home_dir);
318 GNUNET_CONFIUGRATION_get_value_filename (cfg,
319 "MYSQL", "CONFIG", cnffile,
320 &home_dir);
321 GNUNET_free (cnffile);
322 cnffile = home_dir;
323 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
324 _("Trying to use file `%s' for MySQL configuration.\n"),
325 cnffile);
326 if ((0 != STAT (cnffile, &st)) ||
327 (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
328 {
329 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
330 _("Could not access file `%s': %s\n"), cnffile,
331 STRERROR (errno));
332 GNUNET_free (cnffile);
333 return NULL;
334 }
335 return cnffile;
336}
337
338
339/**
340 * Close database connection and all prepared statements (we got a DB
341 * disconnect error).
342 */
343static int
344iclose (struct Plugin *plugin)
345{
346 struct GNUNET_MysqlStatementHandle *spos;
347
348 spos = plugin->shead;
349 while (spos != NULL)
350 {
351 if (spos->statement != NULL)
352 {
353 mysql_stmt_close (spos->statement);
354 spos->statement = NULL;
355 }
356 spos->valid = GNUNET_NO;
357 spos = spos->next;
358 }
359 if (plugin->dbf != NULL)
360 {
361 mysql_close (plugin->dbf);
362 plugin->dbf = NULL;
363 }
364 return GNUNET_OK;
365}
366
367
368/**
369 * Open the connection with the database (and initialize
370 * our default options).
371 *
372 * @return GNUNET_OK on success
373 */
374static int
375iopen (struct Plugin *ret)
376{
377 char *mysql_dbname;
378 char *mysql_server;
379 char *mysql_user;
380 char *mysql_password;
381 unsigned long long mysql_port;
382 my_bool reconnect;
383 unsigned int timeout;
384
385 ret->dbf = mysql_init (NULL);
386 if (ret->dbf == NULL)
387 return GNUNET_SYSERR;
388 if (ret->cnffile != NULL)
389 mysql_options (ret->dbf, MYSQL_READ_DEFAULT_FILE, ret->cnffile);
390 mysql_options (ret->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
391 reconnect = 0;
392 mysql_options (ret->dbf, MYSQL_OPT_RECONNECT, &reconnect);
393 mysql_options (ret->dbf,
394 MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
395 timeout = 60; /* in seconds */
396 mysql_options (ret->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
397 mysql_options (ret->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
398 mysql_dbname = NULL;
399 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
400 "MYSQL", "DATABASE"))
401 GNUNET_assert (GNUNET_OK ==
402 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
403 "MYSQL", "DATABASE",
404 &mysql_dbname));
405 else
406 mysql_dbname = GNUNET_strdup ("gnunet");
407 mysql_user = NULL;
408 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
409 "MYSQL", "USER"))
410 {
411 GNUNET_break (GNUNET_OK ==
412 GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
413 "MYSQL", "USER",
414 &mysql_user));
415 }
416 mysql_password = NULL;
417 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
418 "MYSQL", "PASSWORD"))
419 {
420 GNUNET_break (GNUNET_OK ==
421 GNUNET_CONFIGURATION_get_value_string (ret->cfg,
422 "MYSQL", "PASSWORD",
423 &mysql_password));
424 }
425 mysql_server = NULL;
426 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->cfg,
427 "MYSQL", "HOST"))
428 {
429 GNUNET_break (GNUNET_OK ==
430 GNUNET_CONFIGURATION_get_value_string (ret->cfg,
431 "MYSQL", "HOST", "",
432 &mysql_server));
433 }
434 mysql_port = 0;
435 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->cfg,
436 "MYSQL", "PORT"))
437 {
438 GNUNET_break (GNUNET_OK ==
439 GNUNET_CONFIGURATION_get_value_number (ret->cfg, "MYSQL",
440 "PORT", &mysql_port));
441 }
442
443 GNUNET_assert (mysql_dbname != NULL);
444 mysql_real_connect (ret->dbf, mysql_server, mysql_user, mysql_password,
445 mysql_dbname, (unsigned int) mysql_port, NULL, 0);
446 GNUNET_free (mysql_dbname);
447 if (mysql_error (ret->dbf)[0])
448 {
449 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
450 "mysql_real_connect", ret);
451 return GNUNET_SYSERR;
452 }
453 ret->valid = GNUNET_YES;
454 return GNUNET_OK;
455}
456
457
458/**
459 * Run the given MySQL statement.
460 *
461 * @return GNUNET_OK on success, GNUNET_SYSERR on error
462 */
463static int
464run_statement (struct Plugin *plugin,
465 const char *statement)
466{
467 if ((NULL == plugin->dbh) && (GNUNET_OK != iopen (plugin)))
468 return GNUNET_SYSERR;
469 mysql_query (plugin->dbf, statement);
470 if (mysql_error (plugin->dbf)[0])
471 {
472 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
473 "mysql_query", plugin);
474 iclose (plugin);
475 return GNUNET_SYSERR;
476 }
477 return GNUNET_OK;
478}
479
480
481/**
482 * Run the given MySQL SELECT statement. The statement
483 * must have only a single result (one column, one row).
484 *
485 * @return result on success, NULL on error
486 */
487static char *
488run_statement_select (struct Plugin *plugin,
489 const char *statement)
490{
491 MYSQL_RES *sql_res;
492 MYSQL_ROW sql_row;
493 char *ret;
494
495 if ((NULL == plugin->dbh) && (GNUNET_OK != iopen (plugin)))
496 return NULL;
497 mysql_query (plugin->dbf, statement);
498 if ((mysql_error (plugin->dbf)[0]) ||
499 (!(sql_res = mysql_use_result (plugin->dbf))) ||
500 (!(sql_row = mysql_fetch_row (sql_res))))
501 {
502 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
503 "mysql_query", plugin);
504 return NULL;
505 }
506 if ((mysql_num_fields (sql_res) != 1) || (sql_row[0] == NULL))
507 {
508 GNUNET_break (mysql_num_fields (sql_res) == 1);
509 if (sql_res != NULL)
510 mysql_free_result (sql_res);
511 return NULL;
512 }
513 ret = GNUNET_strdup (sql_row[0]);
514 mysql_free_result (sql_res);
515 return ret;
516}
517
518
519/**
520 * Create a prepared statement.
521 *
522 * @return NULL on error
523 */
524static struct GNUNET_MysqlStatementHandle *
525prepared_statement_create (struct Plugin *plugin,
526 const char *statement)
527{
528 struct GNUNET_MysqlStatementHandle *ret;
529
530 ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
531 ret->query = GNUNET_strdup (statement);
532 GNUNET_CONTAINER_DLL_insert (plugin->shead,
533 plugin->stail,
534 ret);
535 return ret;
536}
537
538
539/**
540 * Prepare a statement for running.
541 *
542 * @return GNUNET_OK on success
543 */
544static int
545prepare_statement (struct Plugin *plugin,
546 struct GNUNET_MysqlStatementHandle *ret)
547{
548 if (GNUNET_YES == ret->valid)
549 return GNUNET_OK;
550 if ((NULL == plugin->dbh) &&
551 (GNUNET_OK != iopen (plugin)))
552 return GNUNET_SYSERR;
553 ret->statement = mysql_stmt_init (plugin->dbf);
554 if (ret->statement == NULL)
555 {
556 iclose (plugin);
557 return GNUNET_SYSERR;
558 }
559 if (mysql_stmt_prepare (ret->statement,
560 ret->query,
561 strlen (ret->query)))
562 {
563 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
564 "mysql_stmt_prepare",
565 plugin);
566 mysql_stmt_close (ret->statement);
567 ret->statement = NULL;
568 iclose (plugin);
569 return GNUNET_SYSERR;
570 }
571 ret->valid = GNUNET_YES;
572 return GNUNET_OK;
573}
574
575
576/**
577 * Free a prepared statement.
578 */
579static void
580prepared_statement_destroy (struct Plugin *plugin,
581 struct GNUNET_MysqlStatementHandle
582 *s)
583{
584 GNUNET_CONTAINER_DLL_remove (plugin->shead,
585 plugin->stail,
586 s);
587 if (s->valid)
588 mysql_stmt_close (s->statement);
589 GNUNET_free (s->query);
590 GNUNET_free (s);
591}
592
593
594/**
595 * Bind the parameters for the given MySQL statement
596 * and run it.
597 *
598 * @param s statement to bind and run
599 * @param ap arguments for the binding
600 * @return GNUNET_SYSERR on error, GNUNET_OK on success
601 */
602static int
603init_params (struct Plugin *plugin,
604 struct GNUNET_MysqlStatementHandle *s,
605 va_list ap)
606{
607 MYSQL_BIND qbind[MAX_PARAM];
608 unsigned int pc;
609 unsigned int off;
610 enum enum_field_types ft;
611
612 pc = mysql_stmt_param_count (s->statement);
613 if (pc > MAX_PARAM)
614 {
615 /* increase internal constant! */
616 GNUNET_break (0);
617 return GNUNET_SYSERR;
618 }
619 memset (qbind, 0, sizeof (qbind));
620 off = 0;
621 ft = 0;
622 while ((pc > 0) && (-1 != (ft = va_arg (ap, enum enum_field_types))))
623 {
624 qbind[off].buffer_type = ft;
625 switch (ft)
626 {
627 case MYSQL_TYPE_FLOAT:
628 qbind[off].buffer = va_arg (ap, float *);
629 break;
630 case MYSQL_TYPE_LONGLONG:
631 qbind[off].buffer = va_arg (ap, unsigned long long *);
632 qbind[off].is_unsigned = va_arg (ap, int);
633 break;
634 case MYSQL_TYPE_LONG:
635 qbind[off].buffer = va_arg (ap, unsigned int *);
636 qbind[off].is_unsigned = va_arg (ap, int);
637 break;
638 case MYSQL_TYPE_VAR_STRING:
639 case MYSQL_TYPE_STRING:
640 case MYSQL_TYPE_BLOB:
641 qbind[off].buffer = va_arg (ap, void *);
642 qbind[off].buffer_length = va_arg (ap, unsigned long);
643 qbind[off].length = va_arg (ap, unsigned long *);
644 break;
645 default:
646 /* unsupported type */
647 GNUNET_break (0);
648 return GNUNET_SYSERR;
649 }
650 pc--;
651 off++;
652 }
653 if (!((pc == 0) && (ft != -1) && (va_arg (ap, int) == -1)))
654 {
655 GNUNET_break (0);
656 return GNUNET_SYSERR;
657 }
658 if (mysql_stmt_bind_param (s->statement, qbind))
659 {
660 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
661 _("`%s' failed at %s:%d with error: %s\n"),
662 "mysql_stmt_bind_param",
663 __FILE__, __LINE__, mysql_stmt_error (s->statement));
664 iclose (plugin);
665 return GNUNET_SYSERR;
666 }
667 if (mysql_stmt_execute (s->statement))
668 {
669 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
670 _("`%s' failed at %s:%d with error: %s\n"),
671 "mysql_stmt_execute",
672 __FILE__, __LINE__, mysql_stmt_error (s->statement));
673 iclose (plugin);
674 return GNUNET_SYSERR;
675 }
676 return GNUNET_OK;
677}
678
679/**
680 * Type of a callback that will be called for each
681 * data set returned from MySQL.
682 *
683 * @param cls user-defined argument
684 * @param num_values number of elements in values
685 * @param values values returned by MySQL
686 * @return GNUNET_OK to continue iterating, GNUNET_SYSERR to abort
687 */
688typedef int (*GNUNET_MysqlDataProcessor) (void *cls,
689 unsigned int num_values,
690 MYSQL_BIND * values);
691
692
693/**
694 * Run a prepared SELECT statement.
695 *
696 * @param result_size number of elements in results array
697 * @param results pointer to already initialized MYSQL_BIND
698 * array (of sufficient size) for passing results
699 * @param processor function to call on each result
700 * @param processor_cls extra argument to processor
701 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
702 * values (size + buffer-reference for pointers); terminated
703 * with "-1"
704 * @return GNUNET_SYSERR on error, otherwise
705 * the number of successfully affected (or queried) rows
706 */
707static int
708prepared_statement_run_select (struct Plugin *plugin,
709 struct GNUNET_MysqlStatementHandle
710 *s,
711 unsigned int result_size,
712 MYSQL_BIND * results,
713 GNUNET_MysqlDataProcessor
714 processor, void *processor_cls,
715 ...)
716{
717 va_list ap;
718 int ret;
719 unsigned int rsize;
720 int total;
721
722 if (GNUNET_OK != prepare_statement (plugin, s))
723 {
724 GNUNET_break (0);
725 return GNUNET_SYSERR;
726 }
727 va_start (ap, processor_cls);
728 if (GNUNET_OK != init_params (plugin, s, ap))
729 {
730 GNUNET_break (0);
731 va_end (ap);
732 return GNUNET_SYSERR;
733 }
734 va_end (ap);
735 rsize = mysql_stmt_field_count (s->statement);
736 if (rsize > result_size)
737 {
738 GNUNET_break (0);
739 return GNUNET_SYSERR;
740 }
741 if (mysql_stmt_bind_result (s->statement, results))
742 {
743 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
744 _("`%s' failed at %s:%d with error: %s\n"),
745 "mysql_stmt_bind_result",
746 __FILE__, __LINE__, mysql_stmt_error (s->statement));
747 iclose (plugin);
748 return GNUNET_SYSERR;
749 }
750
751 total = 0;
752 while (1)
753 {
754 ret = mysql_stmt_fetch (s->statement);
755 if (ret == MYSQL_NO_DATA)
756 break;
757 if (ret != 0)
758 {
759 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
760 _("`%s' failed at %s:%d with error: %s\n"),
761 "mysql_stmt_fetch",
762 __FILE__, __LINE__, mysql_stmt_error (s->statement));
763 iclose (plugin);
764 return GNUNET_SYSERR;
765 }
766 if (processor != NULL)
767 if (GNUNET_OK != processor (processor_cls, rsize, results))
768 break;
769 total++;
770 }
771 mysql_stmt_reset (s->statement);
772 return total;
773}
774
775
776/**
777 * Run a prepared statement that does NOT produce results.
778 *
779 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
780 * values (size + buffer-reference for pointers); terminated
781 * with "-1"
782 * @param insert_id NULL or address where to store the row ID of whatever
783 * was inserted (only for INSERT statements!)
784 * @return GNUNET_SYSERR on error, otherwise
785 * the number of successfully affected rows
786 */
787static int
788prepared_statement_run (struct Plugin *plugin,
789 struct GNUNET_MysqlStatementHandle *s,
790 unsigned long long *insert_id, ...)
791{
792 va_list ap;
793 int affected;
794
795 if (GNUNET_OK != prepare_statement (plugin, s))
796 return GNUNET_SYSERR;
797 va_start (ap, insert_id);
798 if (GNUNET_OK != init_params (plugin, s, ap))
799 {
800 va_end (ap);
801 return GNUNET_SYSERR;
802 }
803 va_end (ap);
804 affected = mysql_stmt_affected_rows (s->statement);
805 if (NULL != insert_id)
806 *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
807 mysql_stmt_reset (s->statement);
808 return affected;
809}
810
811
812
813
814/**
815 * Delete an value from the gn072 table.
816 *
817 * @param vkey vkey identifying the value to delete
818 * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
819 */
820static int
821do_delete_value (unsigned long long vkey)
822{
823 int ret;
824
825 ret = GNUNET_MYSQL_prepared_statement_run (delete_value,
826 NULL,
827 MYSQL_TYPE_LONGLONG,
828 &vkey, GNUNET_YES, -1);
829 if (ret > 0)
830 ret = GNUNET_OK;
831 return ret;
832}
833
834/**
835 * Insert a value into the gn072 table.
836 *
837 * @param value the value to insert
838 * @param size size of the value
839 * @param vkey vkey identifying the value henceforth (set)
840 * @return GNUNET_OK on success, GNUNET_SYSERR on error
841 */
842static int
843do_insert_value (const void *value, unsigned int size,
844 unsigned long long *vkey)
845{
846 unsigned long length = size;
847
848 return GNUNET_MYSQL_prepared_statement_run (insert_value,
849 vkey,
850 MYSQL_TYPE_BLOB,
851 value, length, &length, -1);
852}
853
854/**
855 * Delete an entry from the gn080 table.
856 *
857 * @param vkey vkey identifying the entry to delete
858 * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
859 */
860static int
861do_delete_entry_by_vkey (unsigned long long vkey)
862{
863 int ret;
864
865 ret = GNUNET_MYSQL_prepared_statement_run (delete_entry_by_vkey,
866 NULL,
867 MYSQL_TYPE_LONGLONG,
868 &vkey, GNUNET_YES, -1);
869 if (ret > 0)
870 ret = GNUNET_OK;
871 return ret;
872}
873
874static int
875return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
876{
877 return GNUNET_OK;
878}
879
880/**
881 * Given a full (SELECT *) result set from gn080 table,
882 * assemble it into a GNUNET_DatastoreValue representation.
883 *
884 * Call *without* holding the lock, but while within
885 * mysql_thread_start/end.
886 *
887 * @param result location where mysql_stmt_fetch stored the results
888 * @return NULL on error
889 */
890static GNUNET_DatastoreValue *
891assembleDatum (MYSQL_BIND * result)
892{
893 GNUNET_DatastoreValue *datum;
894 unsigned int contentSize;
895 unsigned int type;
896 unsigned int prio;
897 unsigned int level;
898 unsigned long long exp;
899 unsigned long long vkey;
900 unsigned long length;
901 MYSQL_BIND rbind[1];
902 int ret;
903
904 if ((result[0].buffer_type != MYSQL_TYPE_LONG) ||
905 (!result[0].is_unsigned) ||
906 (result[1].buffer_type != MYSQL_TYPE_LONG) ||
907 (!result[1].is_unsigned) ||
908 (result[2].buffer_type != MYSQL_TYPE_LONG) ||
909 (!result[2].is_unsigned) ||
910 (result[3].buffer_type != MYSQL_TYPE_LONG) ||
911 (!result[3].is_unsigned) ||
912 (result[4].buffer_type != MYSQL_TYPE_LONGLONG) ||
913 (!result[4].is_unsigned) ||
914 (result[5].buffer_type != MYSQL_TYPE_BLOB) ||
915 (result[5].buffer_length != sizeof (GNUNET_HashCode)) ||
916 (*result[5].length != sizeof (GNUNET_HashCode)) ||
917 (result[6].buffer_type != MYSQL_TYPE_LONGLONG) ||
918 (!result[6].is_unsigned))
919 {
920 GNUNET_break (0);
921 return NULL; /* error */
922 }
923
924 contentSize = *(unsigned int *) result[0].buffer;
925 if (contentSize < sizeof (GNUNET_DatastoreValue))
926 return NULL; /* error */
927 if (contentSize > GNUNET_MAX_BUFFER_SIZE)
928 {
929 GNUNET_break (0); /* far too big */
930 return NULL;
931 }
932 contentSize -= sizeof (GNUNET_DatastoreValue);
933 type = *(unsigned int *) result[1].buffer;
934 prio = *(unsigned int *) result[2].buffer;
935 level = *(unsigned int *) result[3].buffer;
936 exp = *(unsigned long long *) result[4].buffer;
937 vkey = *(unsigned long long *) result[6].buffer;
938 datum = GNUNET_malloc (sizeof (GNUNET_DatastoreValue) + contentSize);
939 datum->size = htonl (contentSize + sizeof (GNUNET_DatastoreValue));
940 datum->type = htonl (type);
941 datum->priority = htonl (prio);
942 datum->anonymity_level = htonl (level);
943 datum->expiration_time = GNUNET_htonll (exp);
944
945 /* now do query on gn072 */
946 length = contentSize;
947 memset (rbind, 0, sizeof (rbind));
948 rbind[0].buffer_type = MYSQL_TYPE_BLOB;
949 rbind[0].buffer_length = contentSize;
950 rbind[0].length = &length;
951 rbind[0].buffer = &datum[1];
952 ret = GNUNET_MYSQL_prepared_statement_run_select (select_value,
953 1,
954 rbind,
955 &return_ok,
956 NULL,
957 MYSQL_TYPE_LONGLONG,
958 &vkey, GNUNET_YES, -1);
959 GNUNET_break (ret <= 1); /* should only have one result! */
960 if (ret > 0)
961 ret = GNUNET_OK;
962 if ((ret != GNUNET_OK) ||
963 (rbind[0].buffer_length != contentSize) || (length != contentSize))
964 {
965 GNUNET_break (ret != 0); /* should have one result! */
966 GNUNET_break (length == contentSize); /* length should match! */
967 GNUNET_break (rbind[0].buffer_length == contentSize); /* length should be internally consistent! */
968 do_delete_value (vkey);
969 if (ret != 0)
970 do_delete_entry_by_vkey (vkey);
971 content_size -= ntohl (datum->size);
972 GNUNET_free (datum);
973 return NULL;
974 }
975 return datum;
976}
977
978
979/**
980 * Iterate over the items in the datastore
981 * using the given query to select and order
982 * the items.
983 *
984 * @param type entries of which type should be considered?
985 * Use 0 for any type.
986 * @param iter never NULL
987 * @param is_asc are we using ascending order?
988 * @return the number of results, GNUNET_SYSERR if the
989 * iter is non-NULL and aborted the iteration
990 */
991static int
992iterateHelper (struct Plugin *plugin,
993 unsigned int type,
994 int is_asc,
995 unsigned int iter_select, GNUNET_DatastoreValueIterator dviter,
996 void *closure)
997{
998 GNUNET_DatastoreValue *datum;
999 int count;
1000 int ret;
1001 unsigned int last_prio;
1002 unsigned long long last_expire;
1003 unsigned long long last_vkey;
1004 unsigned int size;
1005 unsigned int rtype;
1006 unsigned int prio;
1007 unsigned int level;
1008 unsigned long long expiration;
1009 unsigned long long vkey;
1010 unsigned long hashSize;
1011 GNUNET_HashCode key;
1012 GNUNET_CronTime now;
1013 MYSQL_BIND rbind[7];
1014
1015 if (is_asc)
1016 {
1017 last_prio = 0;
1018 last_vkey = 0;
1019 last_expire = 0;
1020 }
1021 else
1022 {
1023 last_prio = 0x7FFFFFFFL;
1024 last_vkey = 0x7FFFFFFFFFFFFFFFLL; /* MySQL only supports 63 bits */
1025 last_expire = 0x7FFFFFFFFFFFFFFFLL; /* MySQL only supports 63 bits */
1026 }
1027 hashSize = sizeof (GNUNET_HashCode);
1028 memset (rbind, 0, sizeof (rbind));
1029 rbind[0].buffer_type = MYSQL_TYPE_LONG;
1030 rbind[0].buffer = &size;
1031 rbind[0].is_unsigned = 1;
1032 rbind[1].buffer_type = MYSQL_TYPE_LONG;
1033 rbind[1].buffer = &rtype;
1034 rbind[1].is_unsigned = 1;
1035 rbind[2].buffer_type = MYSQL_TYPE_LONG;
1036 rbind[2].buffer = &prio;
1037 rbind[2].is_unsigned = 1;
1038 rbind[3].buffer_type = MYSQL_TYPE_LONG;
1039 rbind[3].buffer = &level;
1040 rbind[3].is_unsigned = 1;
1041 rbind[4].buffer_type = MYSQL_TYPE_LONGLONG;
1042 rbind[4].buffer = &expiration;
1043 rbind[4].is_unsigned = 1;
1044 rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1045 rbind[5].buffer = &key;
1046 rbind[5].buffer_length = hashSize;
1047 rbind[5].length = &hashSize;
1048 rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1049 rbind[6].buffer = &vkey;
1050 rbind[6].is_unsigned = GNUNET_YES;
1051
1052 now = GNUNET_get_time ();
1053 count = 0;
1054 while (1)
1055 {
1056 switch (iter_select)
1057 {
1058 case 0:
1059 case 1:
1060 ret = prepared_statement_run_select (iter[iter_select],
1061 7,
1062 rbind,
1063 &return_ok,
1064 NULL,
1065 MYSQL_TYPE_LONG,
1066 &last_prio,
1067 GNUNET_YES,
1068 MYSQL_TYPE_LONGLONG,
1069 &last_vkey,
1070 GNUNET_YES,
1071 MYSQL_TYPE_LONG,
1072 &last_prio,
1073 GNUNET_YES,
1074 MYSQL_TYPE_LONGLONG,
1075 &last_vkey,
1076 GNUNET_YES, -1);
1077 break;
1078 case 2:
1079 ret = prepared_statement_run_select (iter[iter_select],
1080 7,
1081 rbind,
1082 &return_ok,
1083 NULL,
1084 MYSQL_TYPE_LONGLONG,
1085 &last_expire,
1086 GNUNET_YES,
1087 MYSQL_TYPE_LONGLONG,
1088 &last_vkey,
1089 GNUNET_YES,
1090 MYSQL_TYPE_LONGLONG,
1091 &last_expire,
1092 GNUNET_YES,
1093 MYSQL_TYPE_LONGLONG,
1094 &last_vkey,
1095 GNUNET_YES, -1);
1096 break;
1097 case 3:
1098 ret = prepared_statement_run_select (iter[iter_select],
1099 7,
1100 rbind,
1101 &return_ok,
1102 NULL,
1103 MYSQL_TYPE_LONGLONG,
1104 &last_expire,
1105 GNUNET_YES,
1106 MYSQL_TYPE_LONGLONG,
1107 &last_vkey,
1108 GNUNET_YES,
1109 MYSQL_TYPE_LONGLONG,
1110 &now,
1111 GNUNET_YES,
1112 MYSQL_TYPE_LONGLONG,
1113 &last_expire,
1114 GNUNET_YES,
1115 MYSQL_TYPE_LONGLONG,
1116 &last_vkey,
1117 GNUNET_YES,
1118 MYSQL_TYPE_LONGLONG,
1119 &now,
1120 GNUNET_YES, -1);
1121 break;
1122 default:
1123 GNUNET_break (0);
1124 return GNUNET_SYSERR;
1125 }
1126 if (ret != GNUNET_OK)
1127 break;
1128 last_vkey = vkey;
1129 last_prio = prio;
1130 last_expire = expiration;
1131 count++;
1132 if (dviter != NULL)
1133 {
1134 datum = assembleDatum (rbind);
1135 if (datum == NULL)
1136 continue;
1137 ret = dviter (&key, datum, closure, vkey);
1138 if (ret == GNUNET_SYSERR)
1139 {
1140 GNUNET_free (datum);
1141 break;
1142 }
1143 if (ret == GNUNET_NO)
1144 {
1145 do_delete_value (vkey);
1146 do_delete_entry_by_vkey (vkey);
1147 content_size -= ntohl (datum->size);
1148 }
1149 GNUNET_free (datum);
1150 }
1151 }
1152 return count;
1153}
1154
1155
1156
1157/**
1158 * Get an estimate of how much space the database is
1159 * currently using.
1160 *
1161 * @param cls our "struct Plugin*"
1162 * @return number of bytes used on disk
1163 */
1164static unsigned long long
1165mysql_plugin_get_size (void *cls)
1166{
1167 struct Plugin *plugin = cls;
1168 return plugin->content_size;
1169}
1170
1171
1172/**
1173 * Store an item in the datastore.
1174 *
1175 * @param cls closure
1176 * @param key key for the item
1177 * @param size number of bytes in data
1178 * @param data content stored
1179 * @param type type of the content
1180 * @param priority priority of the content
1181 * @param anonymity anonymity-level for the content
1182 * @param expiration expiration time for the content
1183 * @param msg set to error message
1184 * @return GNUNET_OK on success
1185 */
1186static int
1187mysql_plugin_put (void *cls,
1188 const GNUNET_HashCode * key,
1189 uint32_t size,
1190 const void *data,
1191 enum GNUNET_BLOCK_Type type,
1192 uint32_t priority,
1193 uint32_t anonymity,
1194 struct GNUNET_TIME_Absolute expiration,
1195 char **msg)
1196{
1197 struct Plugin *plugin = cls;
1198
1199 unsigned long contentSize;
1200 unsigned long hashSize;
1201 unsigned long hashSize2;
1202 unsigned int size;
1203 unsigned int type;
1204 unsigned int prio;
1205 unsigned int level;
1206 unsigned long long expiration;
1207 unsigned long long vkey;
1208 GNUNET_HashCode vhash;
1209
1210 if (((ntohl (value->size) < sizeof (GNUNET_DatastoreValue))) ||
1211 ((ntohl (value->size) - sizeof (GNUNET_DatastoreValue)) >
1212 MAX_DATUM_SIZE))
1213 {
1214 GNUNET_break (0);
1215 return GNUNET_SYSERR;
1216 }
1217 hashSize = sizeof (GNUNET_HashCode);
1218 hashSize2 = sizeof (GNUNET_HashCode);
1219 size = ntohl (value->size);
1220 type = ntohl (value->type);
1221 prio = ntohl (value->priority);
1222 level = ntohl (value->anonymity_level);
1223 expiration = GNUNET_ntohll (value->expiration_time);
1224 contentSize = ntohl (value->size) - sizeof (GNUNET_DatastoreValue);
1225 GNUNET_hash (&value[1], contentSize, &vhash);
1226
1227 if (GNUNET_OK != do_insert_value (&value[1], contentSize, &vkey))
1228 return GNUNET_SYSERR;
1229 if (GNUNET_OK !=
1230 prepared_statement_run (insert_entry,
1231 NULL,
1232 MYSQL_TYPE_LONG,
1233 &size,
1234 GNUNET_YES,
1235 MYSQL_TYPE_LONG,
1236 &type,
1237 GNUNET_YES,
1238 MYSQL_TYPE_LONG,
1239 &prio,
1240 GNUNET_YES,
1241 MYSQL_TYPE_LONG,
1242 &level,
1243 GNUNET_YES,
1244 MYSQL_TYPE_LONGLONG,
1245 &expiration,
1246 GNUNET_YES,
1247 MYSQL_TYPE_BLOB,
1248 key,
1249 hashSize,
1250 &hashSize,
1251 MYSQL_TYPE_BLOB,
1252 &vhash,
1253 hashSize2,
1254 &hashSize2,
1255 MYSQL_TYPE_LONGLONG,
1256 &vkey, GNUNET_YES, -1))
1257 {
1258 do_delete_value (vkey);
1259 return GNUNET_SYSERR;
1260 }
1261 plugin->content_size += ntohl (value->size);
1262 return GNUNET_OK;
1263}
1264
1265
1266/**
1267 * Function invoked on behalf of a "PluginIterator"
1268 * asking the database plugin to call the iterator
1269 * with the next item.
1270 *
1271 * @param next_cls whatever argument was given
1272 * to the PluginIterator as "next_cls".
1273 * @param end_it set to GNUNET_YES if we
1274 * should terminate the iteration early
1275 * (iterator should be still called once more
1276 * to signal the end of the iteration).
1277 */
1278static void
1279mysql_plugin_next_request (void *next_cls,
1280 int end_it)
1281{
1282 struct Plugin *plugin = cls;
1283 GNUNET_break (0);
1284}
1285
1286
1287/**
1288 * Iterate over the results for a particular key
1289 * in the datastore.
1290 *
1291 * @param cls closure
1292 * @param key maybe NULL (to match all entries)
1293 * @param vhash hash of the value, maybe NULL (to
1294 * match all values that have the right key).
1295 * Note that for DBlocks there is no difference
1296 * betwen key and vhash, but for other blocks
1297 * there may be!
1298 * @param type entries of which type are relevant?
1299 * Use 0 for any type.
1300 * @param iter function to call on each matching value;
1301 * will be called once with a NULL value at the end
1302 * @param iter_cls closure for iter
1303 */
1304static void
1305mysql_plugin_get (void *cls,
1306 const GNUNET_HashCode * key,
1307 const GNUNET_HashCode * vhash,
1308 enum GNUNET_BLOCK_Type type,
1309 PluginIterator iter, void *iter_cls)
1310{
1311 struct Plugin *plugin = cls;
1312 int count;
1313 unsigned long long total;
1314 int off;
1315 int ret;
1316 unsigned int size;
1317 unsigned int rtype;
1318 unsigned int prio;
1319 unsigned int level;
1320 unsigned int limit_off;
1321 unsigned long long expiration;
1322 unsigned long long vkey;
1323 unsigned long long last_vkey;
1324 GNUNET_DatastoreValue *datum;
1325 GNUNET_HashCode key;
1326 unsigned long hashSize;
1327 unsigned long hashSize2;
1328 MYSQL_BIND rbind[7];
1329
1330 if (key == NULL)
1331 return iterateLowPriority (type, iter, closure);
1332 hashSize = sizeof (GNUNET_HashCode);
1333 hashSize2 = sizeof (GNUNET_HashCode);
1334 memset (rbind, 0, sizeof (rbind));
1335 total = -1;
1336 rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
1337 rbind[0].buffer = &total;
1338 rbind[0].is_unsigned = GNUNET_YES;
1339 if (type != 0)
1340 {
1341 if (vhash != NULL)
1342 {
1343 ret =
1344 prepared_statement_run_select
1345 (count_entry_by_hash_vhash_and_type, 1, rbind, &return_ok, NULL,
1346 MYSQL_TYPE_BLOB, key, hashSize2, &hashSize2, MYSQL_TYPE_BLOB,
1347 vhash, hashSize2, &hashSize2, MYSQL_TYPE_LONG, &type, GNUNET_YES,
1348 -1);
1349 }
1350 else
1351 {
1352 ret =
1353 prepared_statement_run_select
1354 (count_entry_by_hash_and_type, 1, rbind, &return_ok, NULL,
1355 MYSQL_TYPE_BLOB, key, hashSize2, &hashSize2, MYSQL_TYPE_LONG,
1356 &type, GNUNET_YES, -1);
1357
1358 }
1359 }
1360 else
1361 {
1362 if (vhash != NULL)
1363 {
1364 ret =
1365 prepared_statement_run_select
1366 (count_entry_by_hash_and_vhash, 1, rbind, &return_ok, NULL,
1367 MYSQL_TYPE_BLOB, key, hashSize2, &hashSize2, MYSQL_TYPE_BLOB,
1368 vhash, hashSize2, &hashSize2, -1);
1369
1370 }
1371 else
1372 {
1373 ret =
1374 GNUNET_MYSQL_prepared_statement_run_select (count_entry_by_hash,
1375 1, rbind, &return_ok,
1376 NULL, MYSQL_TYPE_BLOB,
1377 key, hashSize2,
1378 &hashSize2, -1);
1379 }
1380 }
1381 if ((ret != GNUNET_OK) || (-1 == total))
1382 return GNUNET_SYSERR;
1383 if ((iter == NULL) || (total == 0))
1384 return (int) total;
1385
1386 last_vkey = 0;
1387 count = 0;
1388 off = GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, total);
1389
1390 memset (rbind, 0, sizeof (rbind));
1391 rbind[0].buffer_type = MYSQL_TYPE_LONG;
1392 rbind[0].buffer = &size;
1393 rbind[0].is_unsigned = GNUNET_YES;
1394 rbind[1].buffer_type = MYSQL_TYPE_LONG;
1395 rbind[1].buffer = &rtype;
1396 rbind[1].is_unsigned = GNUNET_YES;
1397 rbind[2].buffer_type = MYSQL_TYPE_LONG;
1398 rbind[2].buffer = &prio;
1399 rbind[2].is_unsigned = GNUNET_YES;
1400 rbind[3].buffer_type = MYSQL_TYPE_LONG;
1401 rbind[3].buffer = &level;
1402 rbind[3].is_unsigned = GNUNET_YES;
1403 rbind[4].buffer_type = MYSQL_TYPE_LONGLONG;
1404 rbind[4].buffer = &expiration;
1405 rbind[4].is_unsigned = GNUNET_YES;
1406 rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1407 rbind[5].buffer = &key;
1408 rbind[5].buffer_length = hashSize;
1409 rbind[5].length = &hashSize;
1410 rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1411 rbind[6].buffer = &vkey;
1412 rbind[6].is_unsigned = GNUNET_YES;
1413 while (1)
1414 {
1415 if (count == 0)
1416 limit_off = off;
1417 else
1418 limit_off = 0;
1419 if (type != 0)
1420 {
1421 if (vhash != NULL)
1422 {
1423 ret =
1424 prepared_statement_run_select
1425 (select_entry_by_hash_vhash_and_type, 7, rbind, &return_ok,
1426 NULL, MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1427 MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2,
1428 MYSQL_TYPE_LONGLONG, &last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1429 &type, GNUNET_YES, MYSQL_TYPE_LONG, &limit_off, GNUNET_YES,
1430 -1);
1431 }
1432 else
1433 {
1434 ret =
1435 prepared_statement_run_select
1436 (select_entry_by_hash_and_type, 7, rbind, &return_ok, NULL,
1437 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1438 MYSQL_TYPE_LONGLONG, &last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1439 &type, GNUNET_YES, MYSQL_TYPE_LONG, &limit_off, GNUNET_YES,
1440 -1);
1441 }
1442 }
1443 else
1444 {
1445 if (vhash != NULL)
1446 {
1447 ret =
1448 prepared_statement_run_select
1449 (select_entry_by_hash_and_vhash, 7, rbind, &return_ok, NULL,
1450 MYSQL_TYPE_BLOB, key, hashSize, &hashSize, MYSQL_TYPE_BLOB,
1451 vhash, hashSize2, &hashSize2, MYSQL_TYPE_LONGLONG,
1452 &last_vkey, GNUNET_YES, MYSQL_TYPE_LONG, &limit_off,
1453 GNUNET_YES, -1);
1454 }
1455 else
1456 {
1457 ret =
1458 prepared_statement_run_select
1459 (select_entry_by_hash, 7, rbind, &return_ok, NULL,
1460 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1461 MYSQL_TYPE_LONGLONG, &last_vkey, GNUNET_YES, MYSQL_TYPE_LONG,
1462 &limit_off, GNUNET_YES, -1);
1463 }
1464 }
1465 if (ret != GNUNET_OK)
1466 break;
1467 last_vkey = vkey;
1468 datum = assembleDatum (rbind);
1469 if (datum == NULL)
1470 continue;
1471 count++;
1472 ret = iter (&key, datum, closure, vkey);
1473 if (ret == GNUNET_SYSERR)
1474 {
1475 GNUNET_free (datum);
1476 break;
1477 }
1478 if (ret == GNUNET_NO)
1479 {
1480 do_delete_value (vkey);
1481 do_delete_entry_by_vkey (vkey);
1482 content_size -= ntohl (datum->size);
1483 }
1484 GNUNET_free (datum);
1485 if (count + off == total)
1486 last_vkey = 0; /* back to start */
1487 if (count == total)
1488 break;
1489 }
1490}
1491
1492
1493/**
1494 * Update the priority for a particular key in the datastore. If
1495 * the expiration time in value is different than the time found in
1496 * the datastore, the higher value should be kept. For the
1497 * anonymity level, the lower value is to be used. The specified
1498 * priority should be added to the existing priority, ignoring the
1499 * priority in value.
1500 *
1501 * Note that it is possible for multiple values to match this put.
1502 * In that case, all of the respective values are updated.
1503 *
1504 * @param cls our "struct Plugin*"
1505 * @param uid unique identifier of the datum
1506 * @param delta by how much should the priority
1507 * change? If priority + delta < 0 the
1508 * priority should be set to 0 (never go
1509 * negative).
1510 * @param expire new expiration time should be the
1511 * MAX of any existing expiration time and
1512 * this value
1513 * @param msg set to error message
1514 * @return GNUNET_OK on success
1515 */
1516static int
1517mysql_plugin_update (void *cls,
1518 uint64_t uid,
1519 int delta,
1520 struct GNUNET_TIME_Absolute expire,
1521 char **msg)
1522{
1523 struct Plugin *plugin = cls;
1524 unsigned long long vkey = uid;
1525
1526 return prepared_statement_run (plugin,
1527 plugin->update_entry,
1528 NULL,
1529 MYSQL_TYPE_LONG,
1530 &delta,
1531 GNUNET_NO,
1532 MYSQL_TYPE_LONGLONG,
1533 &expire.value,
1534 GNUNET_YES,
1535 MYSQL_TYPE_LONGLONG,
1536 &expire.value,
1537 GNUNET_YES,
1538 MYSQL_TYPE_LONGLONG,
1539 &vkey,
1540 GNUNET_YES, -1);
1541}
1542
1543
1544/**
1545 * Select a subset of the items in the datastore and call
1546 * the given iterator for each of them.
1547 *
1548 * @param cls our "struct Plugin*"
1549 * @param type entries of which type should be considered?
1550 * Use 0 for any type.
1551 * @param iter function to call on each matching value;
1552 * will be called once with a NULL value at the end
1553 * @param iter_cls closure for iter
1554 */
1555static void
1556mysql_plugin_iter_low_priority (void *cls,
1557 enum GNUNET_BLOCK_Type type,
1558 PluginIterator iter,
1559 void *iter_cls)
1560{
1561 struct Plugin *plugin = cls;
1562 iterateHelper (plugin, type, GNUNET_YES,
1563 0, iter, iter_cls);
1564}
1565
1566
1567/**
1568 * Select a subset of the items in the datastore and call
1569 * the given iterator for each of them.
1570 *
1571 * @param cls our "struct Plugin*"
1572 * @param type entries of which type should be considered?
1573 * Use 0 for any type.
1574 * @param iter function to call on each matching value;
1575 * will be called once with a NULL value at the end
1576 * @param iter_cls closure for iter
1577 */
1578static void
1579mysql_plugin_iter_zero_anonymity (void *cls,
1580 enum GNUNET_BLOCK_Type type,
1581 PluginIterator iter,
1582 void *iter_cls)
1583{
1584 struct Plugin *plugin = cls;
1585 iterateHelper (plugin, type, GNUNET_NO, 1, iter, closure);
1586}
1587
1588
1589/**
1590 * Select a subset of the items in the datastore and call
1591 * the given iterator for each of them.
1592 *
1593 * @param cls our "struct Plugin*"
1594 * @param type entries of which type should be considered?
1595 * Use 0 for any type.
1596 * @param iter function to call on each matching value;
1597 * will be called once with a NULL value at the end
1598 * @param iter_cls closure for iter
1599 */
1600static void
1601mysql_plugin_iter_ascending_expiration (void *cls,
1602 enum GNUNET_BLOCK_Type type,
1603 PluginIterator iter,
1604 void *iter_cls)
1605{
1606 struct Plugin *plugin = cls;
1607 iterateHelper (plugin, type, GNUNET_YES, 2, iter, closure);
1608}
1609
1610
1611/**
1612 * Select a subset of the items in the datastore and call
1613 * the given iterator for each of them.
1614 *
1615 * @param cls our "struct Plugin*"
1616 * @param type entries of which type should be considered?
1617 * Use 0 for any type.
1618 * @param iter function to call on each matching value;
1619 * will be called once with a NULL value at the end
1620 * @param iter_cls closure for iter
1621 */
1622static void
1623mysql_plugin_iter_migration_order (void *cls,
1624 enum GNUNET_BLOCK_Type type,
1625 PluginIterator iter,
1626 void *iter_cls)
1627{
1628 struct Plugin *plugin = cls;
1629 iterateHelper (plugin, 0, GNUNET_NO, 3, iter, closure);
1630}
1631
1632
1633/**
1634 * Select a subset of the items in the datastore and call
1635 * the given iterator for each of them.
1636 *
1637 * @param cls our "struct Plugin*"
1638 * @param type entries of which type should be considered?
1639 * Use 0 for any type.
1640 * @param iter function to call on each matching value;
1641 * will be called once with a NULL value at the end
1642 * @param iter_cls closure for iter
1643 */
1644static void
1645mysql_plugin_iter_all_now (void *cls,
1646 enum GNUNET_BLOCK_Type type,
1647 PluginIterator iter,
1648 void *iter_cls)
1649{
1650 struct Plugin *plugin = cls;
1651 iterateHelper (plugin, 0, GNUNET_YES, 0, iter, closure);
1652}
1653
1654
1655/**
1656 * Drop database.
1657 */
1658static void
1659mysql_plugin_drop (void *cls)
1660{
1661 struct Plugin *plugin = cls;
1662
1663 if ((GNUNET_OK != run_statement (plugin,
1664 "DROP TABLE gn080")) ||
1665 (GNUNET_OK != run_statement (plugin,
1666 "DROP TABLE gn072")))
1667 return; /* error */
1668 plugin->content_size = 0;
1669}
1670
1671
1672/**
1673 * Entry point for the plugin.
1674 *
1675 * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
1676 * @return our "struct Plugin*"
1677 */
1678void *
1679libgnunet_plugin_datastore_mysql_init (void *cls)
1680{
1681 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1682 struct GNUNET_DATASTORE_PluginFunctions *api;
1683 struct Plugin *plugin;
1684
1685 plugin = GNUNET_malloc (sizeof (struct Plugin));
1686 plugin->env = env;
1687 plugin->cnffile = get_my_cnf_path (env->cfg);
1688 if (GNUNET_OK != iopen (plugin))
1689 {
1690 iclose (plugin);
1691 GNUNET_free_non_null (plugin->cnffile);
1692 GNUNET_free (plugin);
1693 return NULL;
1694 }
1695#define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
1696#define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
1697 if (MRUNS ("CREATE TABLE IF NOT EXISTS gn080 ("
1698 " size INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1699 " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1700 " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1701 " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1702 " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1703 " hash BINARY(64) NOT NULL DEFAULT '',"
1704 " vhash BINARY(64) NOT NULL DEFAULT '',"
1705 " vkey BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1706 " INDEX hash (hash(64)),"
1707 " INDEX hash_vhash_vkey (hash(64),vhash(64),vkey),"
1708 " INDEX hash_vkey (hash(64),vkey),"
1709 " INDEX vkey (vkey),"
1710 " INDEX prio (prio,vkey),"
1711 " INDEX expire (expire,vkey,type),"
1712 " INDEX anonLevel (anonLevel,prio,vkey,type)"
1713 ") ENGINE=InnoDB") ||
1714 MRUNS ("CREATE TABLE IF NOT EXISTS gn072 ("
1715 " vkey BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,"
1716 " value BLOB NOT NULL DEFAULT '') ENGINE=MyISAM") ||
1717 MRUNS ("SET AUTOCOMMIT = 1") ||
1718 PINIT (plugin->select_value, SELECT_VALUE) ||
1719 PINIT (plugin->delete_value, DELETE_VALUE) ||
1720 PINIT (plugin->insert_value, INSERT_VALUE) ||
1721 PINIT (plugin->insert_entry, INSERT_ENTRY) ||
1722 PINIT (plugin->delete_entry_by_vkey, DELETE_ENTRY_BY_VKEY) ||
1723 PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
1724 PINIT (plugin->select_entry_by_hash_and_vhash, SELECT_ENTRY_BY_HASH_AND_VHASH)
1725 || PINIT (plugin->select_entry_by_hash_and_type, SELECT_ENTRY_BY_HASH_AND_TYPE)
1726 || PINIT (plugin->select_entry_by_hash_vhash_and_type,
1727 SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1728 || PINIT (plugin->count_entry_by_hash, COUNT_ENTRY_BY_HASH)
1729 || PINIT (plugin->count_entry_by_hash_and_vhash, COUNT_ENTRY_BY_HASH_AND_VHASH)
1730 || PINIT (plugin->count_entry_by_hash_and_type, COUNT_ENTRY_BY_HASH_AND_TYPE)
1731 || PINIT (plugin->count_entry_by_hash_vhash_and_type,
1732 COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1733 || PINIT (plugin->update_entry, UPDATE_ENTRY)
1734 || PINIT (plugin->iter[0], SELECT_IT_LOW_PRIORITY)
1735 || PINIT (plugin->iter[1], SELECT_IT_NON_ANONYMOUS)
1736 || PINIT (plugin->iter[2], SELECT_IT_EXPIRATION_TIME)
1737 || PINIT (plugin->iter[3], SELECT_IT_MIGRATION_ORDER))
1738 {
1739 GNUNET_MYSQL_database_close (db);
1740 db = NULL;
1741 return GNUNET_SYSERR;
1742 }
1743#undef PINIT
1744#undef MRUNS
1745
1746
1747 api = GNUNET_malloc (sizeof (struct GNUNET_DATASTORE_PluginFunctions));
1748 api->cls = plugin;
1749 api->get_size = &mysql_plugin_get_size;
1750 api->put = &mysql_plugin_put;
1751 api->next_request = &mysql_plugin_next_request;
1752 api->get = &mysql_plugin_get;
1753 api->update = &mysql_plugin_update;
1754 api->iter_low_priority = &mysql_plugin_iter_low_priority;
1755 api->iter_zero_anonymity = &mysql_plugin_iter_zero_anonymity;
1756 api->iter_ascending_expiration = &mysql_plugin_iter_ascending_expiration;
1757 api->iter_migration_order = &mysql_plugin_iter_migration_order;
1758 api->iter_all_now = &mysql_plugin_iter_all_now;
1759 api->drop = &mysql_plugin_drop;
1760 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1761 "mysql", _("Mysql database running\n"));
1762 return api;
1763}
1764
1765
1766/**
1767 * Exit point from the plugin.
1768 * @param cls our "struct Plugin*"
1769 * @return always NULL
1770 */
1771void *
1772libgnunet_plugin_datastore_mysql_done (void *cls)
1773{
1774 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1775 struct Plugin *plugin = api->cls;
1776
1777 iclose (plugin);
1778 GNUNET_free_non_null (plugin->cnffile);
1779 GNUNET_free (plugin);
1780 GNUNET_free (plugin);
1781 GNUNET_free (api);
1782 mysql_library_end ();
1783 return NULL;
1784}
1785
1786/* end of plugin_datastore_mysql.c */