2 This file is part of GNUnet
3 (C) 2009, 2010, 2011 Christian Grothoff (and other contributing authors)
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.
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.
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.
22 * @file datastore/plugin_datastore_mysql.c
23 * @brief mysql-based datastore backend
24 * @author Igor Wronsky
25 * @author Christian Grothoff
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).
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!
45 * - Memory usage (Comment: "I have 1G and it never caused me trouble")
48 * MANUAL SETUP INSTRUCTIONS
50 * 1) in /etc/gnunet.conf, set
55 * 2) Then access mysql as root,
59 * and do the following. [You should replace $USER with the username
60 * that will be running the gnunetd process].
62 CREATE DATABASE gnunet;
63 GRANT select,insert,update,delete,create,alter,drop,create temporary tables
64 ON gnunet.* TO $USER@localhost;
65 SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
68 * 3) In the $HOME directory of $USER, create a ".my.cnf" file
69 * with the following lines
73 password=$the_password_you_like
76 * Thats it. Note that .my.cnf file is a security risk unless its on
77 * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic
78 * link. Even greater security risk can be achieved by setting no
79 * password for $USER. Luckily $USER has only priviledges to mess
80 * up GNUnet's tables, nothing else (unless you give him more,
83 * 4) Still, perhaps you should briefly try if the DB connection
84 * works. First, login as $USER. Then use,
87 $ mysql -u $USER -p $the_password_you_like
91 * If you get the message "Database changed" it probably works.
93 * [If you get "ERROR 2002: Can't connect to local MySQL server
94 * through socket '/tmp/mysql.sock' (2)" it may be resolvable by
95 * "ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock"
96 * so there may be some additional trouble depending on your mysql setup.]
100 * - Its probably healthy to check your tables for inconsistencies
101 * every now and then.
102 * - If you get odd SEGVs on gnunetd startup, it might be that the mysql
103 * databases have been corrupted.
104 * - The tables can be verified/fixed in two ways;
105 * 1) by running mysqlcheck -A, or
106 * 2) by executing (inside of mysql using the GNUnet database):
108 mysql> REPAIR TABLE gn090;
113 * If you have problems related to the mysql module, your best
114 * friend is probably the mysql manual. The first thing to check
115 * is that mysql is basically operational, that you can connect
116 * to it, create tables, issue queries etc.
119 #include "platform.h"
120 #include "gnunet_datastore_plugin.h"
121 #include "gnunet_util_lib.h"
122 #include <mysql/mysql.h>
124 #define DEBUG_MYSQL GNUNET_NO
126 #define MAX_DATUM_SIZE 65536
129 * Maximum number of supported parameters for a prepared
130 * statement. Increase if needed.
135 * Die with an error message that indicates
136 * a failure of the command 'cmd' with the message given
137 * by strerror(errno).
139 #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);
142 * Log an error message at log-level 'level' that indicates
143 * a failure of the command 'cmd' on file 'filename'
144 * with the message given by strerror(errno).
146 #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);
149 struct GNUNET_MysqlStatementHandle
151 struct GNUNET_MysqlStatementHandle *next;
153 struct GNUNET_MysqlStatementHandle *prev;
157 MYSQL_STMT *statement;
165 * Context for all functions in this plugin.
170 * Our execution environment.
172 struct GNUNET_DATASTORE_PluginEnvironment *env;
175 * Handle to talk to MySQL.
180 * We keep all prepared statements in a DLL. This is the head.
182 struct GNUNET_MysqlStatementHandle *shead;
185 * We keep all prepared statements in a DLL. This is the tail.
187 struct GNUNET_MysqlStatementHandle *stail;
190 * Filename of "my.cnf" (msyql configuration).
195 * Prepared statements.
197 #define INSERT_ENTRY "INSERT INTO gn090 (repl,type,prio,anonLevel,expire,hash,vhash,value) VALUES (?,?,?,?,?,?,?,?)"
198 struct GNUNET_MysqlStatementHandle *insert_entry;
200 #define DELETE_ENTRY_BY_UID "DELETE FROM gn090 WHERE uid=?"
201 struct GNUNET_MysqlStatementHandle *delete_entry_by_uid;
203 #define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn090 WHERE hash=?"
204 struct GNUNET_MysqlStatementHandle *count_entry_by_hash;
206 #define SELECT_ENTRY_BY_HASH "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? ORDER BY uid LIMIT 1 OFFSET ?"
207 struct GNUNET_MysqlStatementHandle *select_entry_by_hash;
209 #define COUNT_ENTRY_BY_HASH_AND_VHASH "SELECT count(*) FROM gn090 WHERE hash=? AND vhash=?"
210 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_vhash;
212 #define SELECT_ENTRY_BY_HASH_AND_VHASH "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND vhash=? ORDER BY uid LIMIT 1 OFFSET ?"
213 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_vhash;
215 #define COUNT_ENTRY_BY_HASH_AND_TYPE "SELECT count(*) FROM gn090 WHERE hash=? AND type=?"
216 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_and_type;
218 #define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND type=? ORDER BY uid LIMIT 1 OFFSET ?"
219 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_and_type;
221 #define COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT count(*) FROM gn090 WHERE hash=? AND vhash=? AND type=?"
222 struct GNUNET_MysqlStatementHandle *count_entry_by_hash_vhash_and_type;
224 #define SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE hash=? AND vhash=? AND type=? ORDER BY uid ASC LIMIT 1 OFFSET ?"
225 struct GNUNET_MysqlStatementHandle *select_entry_by_hash_vhash_and_type;
227 #define UPDATE_ENTRY "UPDATE gn090 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE uid=?"
228 struct GNUNET_MysqlStatementHandle *update_entry;
230 #define DEC_REPL "UPDATE gn090 SET repl=GREATEST (0, repl - 1) WHERE uid=?"
231 struct GNUNET_MysqlStatementHandle *dec_repl;
233 #define SELECT_SIZE "SELECT SUM(BIT_LENGTH(value) DIV 8) FROM gn090"
234 struct GNUNET_MysqlStatementHandle *get_size;
236 #define SELECT_IT_NON_ANONYMOUS "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE anonLevel=0 AND type=? ORDER BY uid DESC LIMIT 1 OFFSET ?"
237 struct GNUNET_MysqlStatementHandle *zero_iter;
239 #define SELECT_IT_EXPIRATION "(SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE expire < ? ORDER BY prio ASC LIMIT 1) "\
241 "(SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 ORDER BY prio ASC LIMIT 1) "\
242 "ORDER BY expire ASC LIMIT 1"
243 struct GNUNET_MysqlStatementHandle *select_expiration;
245 #define SELECT_IT_REPLICATION "SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 ORDER BY repl DESC,RAND() LIMIT 1"
246 struct GNUNET_MysqlStatementHandle *select_replication;
252 * Obtain the location of ".my.cnf".
254 * @param cfg our configuration
255 * @return NULL on error
258 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg)
269 pw = getpwuid (getuid ());
272 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
277 GNUNET_CONFIGURATION_have_value (cfg,
278 "datastore-mysql", "CONFIG"))
280 GNUNET_assert (GNUNET_OK ==
281 GNUNET_CONFIGURATION_get_value_filename (cfg,
282 "datastore-mysql", "CONFIG", &cnffile));
283 configured = GNUNET_YES;
287 home_dir = GNUNET_strdup (pw->pw_dir);
289 home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
290 plibc_conv_to_win_path ("~/", home_dir);
292 GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
293 GNUNET_free (home_dir);
294 configured = GNUNET_NO;
296 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
297 _("Trying to use file `%s' for MySQL configuration.\n"),
299 if ((0 != STAT (cnffile, &st)) ||
300 (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
302 if (configured == GNUNET_YES)
303 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
304 _("Could not access file `%s': %s\n"), cnffile,
306 GNUNET_free (cnffile);
314 * Free a prepared statement.
316 * @param plugin plugin context
317 * @param s prepared statement
320 prepared_statement_destroy (struct Plugin *plugin,
321 struct GNUNET_MysqlStatementHandle *s)
323 GNUNET_CONTAINER_DLL_remove (plugin->shead,
327 mysql_stmt_close (s->statement);
328 GNUNET_free (s->query);
334 * Close database connection and all prepared statements (we got a DB
337 * @param plugin plugin context
340 iclose (struct Plugin *plugin)
342 while (NULL != plugin->shead)
343 prepared_statement_destroy (plugin,
345 if (plugin->dbf != NULL)
347 mysql_close (plugin->dbf);
355 * Open the connection with the database (and initialize
356 * our default options).
358 * @param plugin plugin context
359 * @return GNUNET_OK on success
362 iopen (struct Plugin *plugin)
367 char *mysql_password;
368 unsigned long long mysql_port;
370 unsigned int timeout;
372 plugin->dbf = mysql_init (NULL);
373 if (plugin->dbf == NULL)
374 return GNUNET_SYSERR;
375 if (plugin->cnffile != NULL)
376 mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_FILE, plugin->cnffile);
377 mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
379 mysql_options (plugin->dbf, MYSQL_OPT_RECONNECT, &reconnect);
380 mysql_options (plugin->dbf,
381 MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
382 mysql_options(plugin->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
383 timeout = 60; /* in seconds */
384 mysql_options (plugin->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
385 mysql_options (plugin->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
387 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
388 "datastore-mysql", "DATABASE"))
389 GNUNET_assert (GNUNET_OK ==
390 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
391 "datastore-mysql", "DATABASE",
394 mysql_dbname = GNUNET_strdup ("gnunet");
396 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
397 "datastore-mysql", "USER"))
399 GNUNET_assert (GNUNET_OK ==
400 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
401 "datastore-mysql", "USER",
404 mysql_password = NULL;
405 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
406 "datastore-mysql", "PASSWORD"))
408 GNUNET_assert (GNUNET_OK ==
409 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
410 "datastore-mysql", "PASSWORD",
414 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
415 "datastore-mysql", "HOST"))
417 GNUNET_assert (GNUNET_OK ==
418 GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
419 "datastore-mysql", "HOST",
423 if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
424 "datastore-mysql", "PORT"))
426 GNUNET_assert (GNUNET_OK ==
427 GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg, "datastore-mysql",
428 "PORT", &mysql_port));
431 GNUNET_assert (mysql_dbname != NULL);
432 mysql_real_connect (plugin->dbf,
434 mysql_user, mysql_password,
436 (unsigned int) mysql_port, NULL,
437 CLIENT_IGNORE_SIGPIPE);
438 GNUNET_free_non_null (mysql_server);
439 GNUNET_free_non_null (mysql_user);
440 GNUNET_free_non_null (mysql_password);
441 GNUNET_free (mysql_dbname);
442 if (mysql_error (plugin->dbf)[0])
444 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
445 "mysql_real_connect", plugin);
446 return GNUNET_SYSERR;
453 * Run the given MySQL statement.
455 * @param plugin plugin context
456 * @param statement SQL statement to run
457 * @return GNUNET_OK on success, GNUNET_SYSERR on error
460 run_statement (struct Plugin *plugin,
461 const char *statement)
463 if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
464 return GNUNET_SYSERR;
465 mysql_query (plugin->dbf, statement);
466 if (mysql_error (plugin->dbf)[0])
468 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
469 "mysql_query", plugin);
471 return GNUNET_SYSERR;
478 * Create a prepared statement.
480 * @param plugin plugin context
481 * @param statement SQL statement text to prepare
482 * @return NULL on error
484 static struct GNUNET_MysqlStatementHandle *
485 prepared_statement_create (struct Plugin *plugin,
486 const char *statement)
488 struct GNUNET_MysqlStatementHandle *ret;
490 ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
491 ret->query = GNUNET_strdup (statement);
492 GNUNET_CONTAINER_DLL_insert (plugin->shead,
500 * Prepare a statement for running.
502 * @param plugin plugin context
503 * @param ret handle to prepared statement
504 * @return GNUNET_OK on success
507 prepare_statement (struct Plugin *plugin,
508 struct GNUNET_MysqlStatementHandle *ret)
510 if (GNUNET_YES == ret->valid)
512 if ((NULL == plugin->dbf) &&
513 (GNUNET_OK != iopen (plugin)))
514 return GNUNET_SYSERR;
515 ret->statement = mysql_stmt_init (plugin->dbf);
516 if (ret->statement == NULL)
519 return GNUNET_SYSERR;
521 if (mysql_stmt_prepare (ret->statement,
523 strlen (ret->query)))
525 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
527 _("Failed to prepare statement `%s'\n"),
529 LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
530 "mysql_stmt_prepare",
532 mysql_stmt_close (ret->statement);
533 ret->statement = NULL;
535 return GNUNET_SYSERR;
537 ret->valid = GNUNET_YES;
544 * Bind the parameters for the given MySQL statement
547 * @param plugin plugin context
548 * @param s statement to bind and run
549 * @param ap arguments for the binding
550 * @return GNUNET_SYSERR on error, GNUNET_OK on success
553 init_params (struct Plugin *plugin,
554 struct GNUNET_MysqlStatementHandle *s,
557 MYSQL_BIND qbind[MAX_PARAM];
560 enum enum_field_types ft;
562 pc = mysql_stmt_param_count (s->statement);
565 /* increase internal constant! */
567 return GNUNET_SYSERR;
569 memset (qbind, 0, sizeof (qbind));
572 while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
574 qbind[off].buffer_type = ft;
577 case MYSQL_TYPE_FLOAT:
578 qbind[off].buffer = va_arg (ap, float *);
580 case MYSQL_TYPE_LONGLONG:
581 qbind[off].buffer = va_arg (ap, unsigned long long *);
582 qbind[off].is_unsigned = va_arg (ap, int);
584 case MYSQL_TYPE_LONG:
585 qbind[off].buffer = va_arg (ap, unsigned int *);
586 qbind[off].is_unsigned = va_arg (ap, int);
588 case MYSQL_TYPE_VAR_STRING:
589 case MYSQL_TYPE_STRING:
590 case MYSQL_TYPE_BLOB:
591 qbind[off].buffer = va_arg (ap, void *);
592 qbind[off].buffer_length = va_arg (ap, unsigned long);
593 qbind[off].length = va_arg (ap, unsigned long *);
596 /* unsupported type */
598 return GNUNET_SYSERR;
603 if (! ( (pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1) ) )
606 return GNUNET_SYSERR;
608 if (mysql_stmt_bind_param (s->statement, qbind))
610 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
611 _("`%s' failed at %s:%d with error: %s\n"),
612 "mysql_stmt_bind_param",
613 __FILE__, __LINE__, mysql_stmt_error (s->statement));
615 return GNUNET_SYSERR;
617 if (mysql_stmt_execute (s->statement))
619 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
620 _("`%s' for `%s' failed at %s:%d with error: %s\n"),
621 "mysql_stmt_execute",
623 __FILE__, __LINE__, mysql_stmt_error (s->statement));
625 return GNUNET_SYSERR;
632 * Run a prepared SELECT statement.
634 * @param plugin plugin context
635 * @param s statement to run
636 * @param result_size number of elements in results array
637 * @param results pointer to already initialized MYSQL_BIND
638 * array (of sufficient size) for passing results
639 * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
640 * values (size + buffer-reference for pointers); terminated
642 * @return GNUNET_SYSERR on error, otherwise GNUNET_OK or GNUNET_NO (no result)
645 prepared_statement_run_select_va (struct Plugin *plugin,
646 struct GNUNET_MysqlStatementHandle *s,
647 unsigned int result_size,
654 if (GNUNET_OK != prepare_statement (plugin, s))
657 return GNUNET_SYSERR;
659 if (GNUNET_OK != init_params (plugin, s, ap))
662 return GNUNET_SYSERR;
664 rsize = mysql_stmt_field_count (s->statement);
665 if (rsize > result_size)
668 return GNUNET_SYSERR;
670 if (mysql_stmt_bind_result (s->statement, results))
672 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
673 _("`%s' failed at %s:%d with error: %s\n"),
674 "mysql_stmt_bind_result",
675 __FILE__, __LINE__, mysql_stmt_error (s->statement));
677 return GNUNET_SYSERR;
679 ret = mysql_stmt_fetch (s->statement);
680 if (ret == MYSQL_NO_DATA)
684 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
685 _("`%s' failed at %s:%d with error: %s\n"),
687 __FILE__, __LINE__, mysql_stmt_error (s->statement));
689 return GNUNET_SYSERR;
691 mysql_stmt_reset (s->statement);
697 * Run a prepared SELECT statement.
699 * @param plugin plugin context
700 * @param s statement to run
701 * @param result_size number of elements in results array
702 * @param results pointer to already initialized MYSQL_BIND
703 * array (of sufficient size) for passing results
704 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
705 * values (size + buffer-reference for pointers); terminated
707 * @return GNUNET_SYSERR on error, otherwise
708 * the number of successfully affected (or queried) rows
711 prepared_statement_run_select (struct Plugin *plugin,
712 struct GNUNET_MysqlStatementHandle *s,
713 unsigned int result_size,
720 va_start (ap, results);
721 ret = prepared_statement_run_select_va (plugin, s,
722 result_size, results,
730 * Run a prepared statement that does NOT produce results.
732 * @param plugin plugin context
733 * @param s statement to run
734 * @param insert_id NULL or address where to store the row ID of whatever
735 * was inserted (only for INSERT statements!)
736 * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
737 * values (size + buffer-reference for pointers); terminated
739 * @return GNUNET_SYSERR on error, otherwise
740 * the number of successfully affected rows
743 prepared_statement_run (struct Plugin *plugin,
744 struct GNUNET_MysqlStatementHandle *s,
745 unsigned long long *insert_id, ...)
750 if (GNUNET_OK != prepare_statement (plugin, s))
751 return GNUNET_SYSERR;
752 va_start (ap, insert_id);
753 if (GNUNET_OK != init_params (plugin, s, ap))
756 return GNUNET_SYSERR;
759 affected = mysql_stmt_affected_rows (s->statement);
760 if (NULL != insert_id)
761 *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
762 mysql_stmt_reset (s->statement);
768 * Delete an entry from the gn090 table.
770 * @param plugin plugin context
771 * @param uid unique ID of the entry to delete
772 * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
775 do_delete_entry (struct Plugin *plugin,
776 unsigned long long uid)
781 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
782 "Deleting value %llu from gn090 table\n",
785 ret = prepared_statement_run (plugin,
786 plugin->delete_entry_by_uid,
788 MYSQL_TYPE_LONGLONG, &uid, GNUNET_YES,
792 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
793 "Deleting value %llu from gn090 table failed\n",
800 * Get an estimate of how much space the database is
803 * @param cls our "struct Plugin *"
804 * @return number of bytes used on disk
806 static unsigned long long
807 mysql_plugin_estimate_size (void *cls)
809 struct Plugin *plugin = cls;
813 memset (cbind, 0, sizeof (cbind));
815 cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
816 cbind[0].buffer = &total;
817 cbind[0].is_unsigned = GNUNET_NO;
819 prepared_statement_run_select (plugin,
829 * Store an item in the datastore.
832 * @param key key for the item
833 * @param size number of bytes in data
834 * @param data content stored
835 * @param type type of the content
836 * @param priority priority of the content
837 * @param anonymity anonymity-level for the content
838 * @param replication replication-level for the content
839 * @param expiration expiration time for the content
840 * @param msg set to error message
841 * @return GNUNET_OK on success
844 mysql_plugin_put (void *cls,
845 const GNUNET_HashCode * key,
848 enum GNUNET_BLOCK_Type type,
851 uint32_t replication,
852 struct GNUNET_TIME_Absolute expiration,
855 struct Plugin *plugin = cls;
856 unsigned int irepl = replication;
857 unsigned int ipriority = priority;
858 unsigned int ianonymity = anonymity;
859 unsigned long long lexpiration = expiration.abs_value;
860 unsigned long hashSize;
861 unsigned long hashSize2;
863 GNUNET_HashCode vhash;
865 if (size > MAX_DATUM_SIZE)
868 return GNUNET_SYSERR;
870 hashSize = sizeof (GNUNET_HashCode);
871 hashSize2 = sizeof (GNUNET_HashCode);
873 GNUNET_CRYPTO_hash (data, size, &vhash);
875 prepared_statement_run (plugin,
876 plugin->insert_entry,
878 MYSQL_TYPE_LONG, &irepl, GNUNET_YES,
879 MYSQL_TYPE_LONG, &type, GNUNET_YES,
880 MYSQL_TYPE_LONG, &ipriority, GNUNET_YES,
881 MYSQL_TYPE_LONG, &ianonymity, GNUNET_YES,
882 MYSQL_TYPE_LONGLONG, &lexpiration, GNUNET_YES,
883 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
884 MYSQL_TYPE_BLOB, &vhash, hashSize2, &hashSize2,
885 MYSQL_TYPE_BLOB, data, lsize, &lsize,
887 return GNUNET_SYSERR;
889 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
890 "Inserted value `%s' with size %u into gn090 table\n",
892 (unsigned int) size);
895 plugin->env->duc (plugin->env->cls,
902 * Update the priority for a particular key in the datastore. If
903 * the expiration time in value is different than the time found in
904 * the datastore, the higher value should be kept. For the
905 * anonymity level, the lower value is to be used. The specified
906 * priority should be added to the existing priority, ignoring the
909 * Note that it is possible for multiple values to match this put.
910 * In that case, all of the respective values are updated.
912 * @param cls our "struct Plugin*"
913 * @param uid unique identifier of the datum
914 * @param delta by how much should the priority
915 * change? If priority + delta < 0 the
916 * priority should be set to 0 (never go
918 * @param expire new expiration time should be the
919 * MAX of any existing expiration time and
921 * @param msg set to error message
922 * @return GNUNET_OK on success
925 mysql_plugin_update (void *cls,
928 struct GNUNET_TIME_Absolute expire,
931 struct Plugin *plugin = cls;
932 unsigned long long vkey = uid;
933 unsigned long long lexpire = expire.abs_value;
937 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
938 "Updating value %llu adding %d to priority and maxing exp at %llu\n",
943 ret = prepared_statement_run (plugin,
944 plugin->update_entry,
946 MYSQL_TYPE_LONG, &delta, GNUNET_NO,
947 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
948 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
949 MYSQL_TYPE_LONGLONG, &vkey, GNUNET_YES,
951 if (ret != GNUNET_OK)
953 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
954 "Failed to update value %llu\n",
962 * Run the given select statement and call 'proc' on the resulting
963 * values (which must be in particular positions).
965 * @param plugin the plugin handle
966 * @param stmt select statement to run
967 * @param proc function to call on result
968 * @param proc_cls closure for proc
969 * @param ... arguments to initialize stmt
972 execute_select (struct Plugin *plugin,
973 struct GNUNET_MysqlStatementHandle *stmt,
974 PluginDatumProcessor proc, void *proc_cls,
980 unsigned int priority;
981 unsigned int anonymity;
982 unsigned long long exp;
983 unsigned long hashSize;
985 unsigned long long uid;
986 char value[GNUNET_DATASTORE_MAX_VALUE_SIZE];
988 struct GNUNET_TIME_Absolute expiration;
991 hashSize = sizeof (GNUNET_HashCode);
992 memset (rbind, 0, sizeof (rbind));
993 rbind[0].buffer_type = MYSQL_TYPE_LONG;
994 rbind[0].buffer = &type;
995 rbind[0].is_unsigned = 1;
996 rbind[1].buffer_type = MYSQL_TYPE_LONG;
997 rbind[1].buffer = &priority;
998 rbind[1].is_unsigned = 1;
999 rbind[2].buffer_type = MYSQL_TYPE_LONG;
1000 rbind[2].buffer = &anonymity;
1001 rbind[2].is_unsigned = 1;
1002 rbind[3].buffer_type = MYSQL_TYPE_LONGLONG;
1003 rbind[3].buffer = &exp;
1004 rbind[3].is_unsigned = 1;
1005 rbind[4].buffer_type = MYSQL_TYPE_BLOB;
1006 rbind[4].buffer = &key;
1007 rbind[4].buffer_length = hashSize;
1008 rbind[4].length = &hashSize;
1009 rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1010 rbind[5].buffer = value;
1011 rbind[5].buffer_length = size = sizeof (value);
1012 rbind[5].length = &size;
1013 rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1014 rbind[6].buffer = &uid;
1015 rbind[6].is_unsigned = 1;
1017 va_start (ap, proc_cls);
1018 ret = prepared_statement_run_select_va (plugin,
1026 NULL, 0, NULL, 0, 0, 0,
1027 GNUNET_TIME_UNIT_ZERO_ABS, 0);
1030 GNUNET_assert (size <= sizeof(value));
1031 if ( (rbind[4].buffer_length != sizeof (GNUNET_HashCode)) ||
1032 (hashSize != sizeof (GNUNET_HashCode)) )
1036 NULL, 0, NULL, 0, 0, 0,
1037 GNUNET_TIME_UNIT_ZERO_ABS, 0);
1041 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1042 "Found %u-byte value under key `%s' with prio %u, anon %u, expire %llu selecting from gn090 table\n",
1043 (unsigned int) size,
1049 GNUNET_assert (size < MAX_DATUM_SIZE);
1050 expiration.abs_value = exp;
1051 ret = proc (proc_cls,
1054 type, priority, anonymity, expiration,
1056 if (ret == GNUNET_NO)
1058 do_delete_entry (plugin, uid);
1060 plugin->env->duc (plugin->env->cls,
1068 * Get one of the results for a particular key in the datastore.
1070 * @param cls closure
1071 * @param offset offset of the result (mod #num-results);
1072 * specific ordering does not matter for the offset
1073 * @param key key to match, never NULL
1074 * @param vhash hash of the value, maybe NULL (to
1075 * match all values that have the right key).
1076 * Note that for DBlocks there is no difference
1077 * betwen key and vhash, but for other blocks
1079 * @param type entries of which type are relevant?
1080 * Use 0 for any type.
1081 * @param proc function to call on each matching value; however,
1082 * after the first call to "proc", the plugin must wait
1083 * until "NextRequest" was called before giving the processor
1084 * the next item; finally, the "proc" should be called once
1085 * once with a NULL value at the end ("next_cls" should be NULL
1086 * for that last call)
1087 * @param proc_cls closure for proc
1090 mysql_plugin_get_key (void *cls,
1092 const GNUNET_HashCode *key,
1093 const GNUNET_HashCode *vhash,
1094 enum GNUNET_BLOCK_Type type,
1095 PluginDatumProcessor proc, void *proc_cls)
1097 struct Plugin *plugin = cls;
1099 MYSQL_BIND cbind[1];
1101 unsigned long hashSize;
1102 unsigned long hashSize2;
1103 unsigned long long off;
1105 GNUNET_assert (key != NULL);
1106 GNUNET_assert (NULL != proc);
1107 hashSize = sizeof (GNUNET_HashCode);
1108 hashSize2 = sizeof (GNUNET_HashCode);
1109 memset (cbind, 0, sizeof (cbind));
1111 cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
1112 cbind[0].buffer = &total;
1113 cbind[0].is_unsigned = GNUNET_NO;
1119 prepared_statement_run_select (plugin,
1120 plugin->count_entry_by_hash_vhash_and_type,
1122 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1123 MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2,
1124 MYSQL_TYPE_LONG, &type, GNUNET_YES,
1130 prepared_statement_run_select (plugin,
1131 plugin->count_entry_by_hash_and_type,
1133 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1134 MYSQL_TYPE_LONG, &type, GNUNET_YES,
1143 prepared_statement_run_select (plugin,
1144 plugin->count_entry_by_hash_and_vhash,
1146 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1147 MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2,
1154 prepared_statement_run_select (plugin,
1155 plugin->count_entry_by_hash,
1157 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1161 if ((ret != GNUNET_OK) || (0 >= total))
1164 NULL, 0, NULL, 0, 0, 0,
1165 GNUNET_TIME_UNIT_ZERO_ABS, 0);
1168 offset = offset % total;
1169 off = (unsigned long long) offset;
1171 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1172 "Obtaining %llu/%lld result for GET `%s'\n",
1178 if (type != GNUNET_BLOCK_TYPE_ANY)
1182 execute_select (plugin,
1183 plugin->select_entry_by_hash_vhash_and_type,
1185 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1186 MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize,
1187 MYSQL_TYPE_LONG, &type, GNUNET_YES,
1188 MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1193 execute_select (plugin,
1194 plugin->select_entry_by_hash_and_type,
1196 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1197 MYSQL_TYPE_LONG, &type, GNUNET_YES,
1198 MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1206 execute_select (plugin,
1207 plugin->select_entry_by_hash_and_vhash,
1209 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1210 MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize,
1211 MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1216 execute_select (plugin,
1217 plugin->select_entry_by_hash,
1219 MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1220 MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1228 * Get a zero-anonymity datum from the datastore.
1230 * @param cls our "struct Plugin*"
1231 * @param offset offset of the result
1232 * @param type entries of which type should be considered?
1233 * Use 0 for any type.
1234 * @param iter function to call on each matching value;
1235 * will be called once with a NULL value at the end
1236 * @param iter_cls closure for iter
1239 mysql_plugin_get_zero_anonymity (void *cls,
1241 enum GNUNET_BLOCK_Type type,
1242 PluginDatumProcessor proc, void *proc_cls)
1244 struct Plugin *plugin = cls;
1245 unsigned long long off;
1247 off = (unsigned long long) offset;
1248 execute_select (plugin,
1251 MYSQL_TYPE_LONG, &type, GNUNET_YES,
1252 MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1259 * Context for 'repl_proc' function.
1267 struct Plugin *plugin;
1270 * Function to call for the result (or the NULL).
1272 PluginDatumProcessor proc;
1282 * Wrapper for the processor for 'mysql_plugin_get_replication'.
1283 * Decrements the replication counter and calls the original
1286 * @param cls closure
1287 * @param key key for the content
1288 * @param size number of bytes in data
1289 * @param data content stored
1290 * @param type type of the content
1291 * @param priority priority of the content
1292 * @param anonymity anonymity-level for the content
1293 * @param expiration expiration time for the content
1294 * @param uid unique identifier for the datum;
1295 * maybe 0 if no unique identifier is available
1297 * @return GNUNET_SYSERR to abort the iteration, GNUNET_OK to continue
1298 * (continue on call to "next", of course),
1299 * GNUNET_NO to delete the item and continue (if supported)
1302 repl_proc (void *cls,
1303 const GNUNET_HashCode *key,
1306 enum GNUNET_BLOCK_Type type,
1309 struct GNUNET_TIME_Absolute expiration,
1312 struct ReplCtx *rc = cls;
1313 struct Plugin *plugin = rc->plugin;
1314 unsigned long long oid;
1318 ret = rc->proc (rc->proc_cls,
1321 type, priority, anonymity, expiration,
1325 oid = (unsigned long long) uid;
1326 iret = prepared_statement_run (plugin,
1329 MYSQL_TYPE_LONGLONG, &oid, GNUNET_YES,
1331 if (iret == GNUNET_SYSERR)
1333 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1334 "Failed to reduce replication counter\n");
1335 return GNUNET_SYSERR;
1343 * Get a random item for replication. Returns a single, not expired,
1344 * random item from those with the highest replication counters. The
1345 * item's replication counter is decremented by one IF it was positive
1346 * before. Call 'proc' with all values ZERO or NULL if the datastore
1349 * @param cls closure
1350 * @param proc function to call the value (once only).
1351 * @param iter_cls closure for proc
1354 mysql_plugin_get_replication (void *cls,
1355 PluginDatumProcessor proc, void *proc_cls)
1357 struct Plugin *plugin = cls;
1362 rc.proc_cls = proc_cls;
1363 execute_select (plugin,
1364 plugin->select_replication,
1372 * Get a random item for expiration.
1373 * Call 'proc' with all values ZERO or NULL if the datastore is empty.
1375 * @param cls closure
1376 * @param proc function to call the value (once only).
1377 * @param proc_cls closure for proc
1380 mysql_plugin_get_expiration (void *cls,
1381 PluginDatumProcessor proc, void *proc_cls)
1383 struct Plugin *plugin = cls;
1386 nt = (long long) GNUNET_TIME_absolute_get().abs_value;
1387 execute_select (plugin,
1388 plugin->select_expiration,
1390 MYSQL_TYPE_LONGLONG, &nt, GNUNET_YES,
1399 * @param cls the "struct Plugin*"
1402 mysql_plugin_drop (void *cls)
1404 struct Plugin *plugin = cls;
1406 if (GNUNET_OK != run_statement (plugin,
1407 "DROP TABLE gn090"))
1409 plugin->env->duc (plugin->env->cls, 0);
1414 * Entry point for the plugin.
1416 * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
1417 * @return our "struct Plugin*"
1420 libgnunet_plugin_datastore_mysql_init (void *cls)
1422 struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1423 struct GNUNET_DATASTORE_PluginFunctions *api;
1424 struct Plugin *plugin;
1426 plugin = GNUNET_malloc (sizeof (struct Plugin));
1428 plugin->cnffile = get_my_cnf_path (env->cfg);
1429 if (GNUNET_OK != iopen (plugin))
1432 GNUNET_free_non_null (plugin->cnffile);
1433 GNUNET_free (plugin);
1436 #define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
1437 #define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
1438 if (MRUNS ("CREATE TABLE IF NOT EXISTS gn090 ("
1439 " repl INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1440 " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1441 " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1442 " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1443 " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1444 " hash BINARY(64) NOT NULL DEFAULT '',"
1445 " vhash BINARY(64) NOT NULL DEFAULT '',"
1446 " value BLOB NOT NULL DEFAULT '',"
1447 " uid BIGINT NOT NULL AUTO_INCREMENT,"
1448 " PRIMARY KEY (uid),"
1449 " INDEX idx_hash (hash(64)),"
1450 " INDEX idx_hash_uid (hash(64),uid),"
1451 " INDEX idx_hash_vhash (hash(64),vhash(64)),"
1452 " INDEX idx_hash_type_uid (hash(64),type,uid),"
1453 " INDEX idx_prio (prio),"
1454 " INDEX idx_repl (repl),"
1455 " INDEX idx_expire_prio (expire,prio),"
1456 " INDEX idx_anonLevel_uid (anonLevel,uid)"
1457 ") ENGINE=InnoDB") ||
1458 MRUNS ("SET AUTOCOMMIT = 1") ||
1459 PINIT (plugin->insert_entry, INSERT_ENTRY) ||
1460 PINIT (plugin->delete_entry_by_uid, DELETE_ENTRY_BY_UID) ||
1461 PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
1462 PINIT (plugin->select_entry_by_hash_and_vhash, SELECT_ENTRY_BY_HASH_AND_VHASH)
1463 || PINIT (plugin->select_entry_by_hash_and_type, SELECT_ENTRY_BY_HASH_AND_TYPE)
1464 || PINIT (plugin->select_entry_by_hash_vhash_and_type,
1465 SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1466 || PINIT (plugin->count_entry_by_hash, COUNT_ENTRY_BY_HASH)
1467 || PINIT (plugin->get_size, SELECT_SIZE)
1468 || PINIT (plugin->count_entry_by_hash_and_vhash, COUNT_ENTRY_BY_HASH_AND_VHASH)
1469 || PINIT (plugin->count_entry_by_hash_and_type, COUNT_ENTRY_BY_HASH_AND_TYPE)
1470 || PINIT (plugin->count_entry_by_hash_vhash_and_type,
1471 COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1472 || PINIT (plugin->update_entry, UPDATE_ENTRY)
1473 || PINIT (plugin->dec_repl, DEC_REPL)
1474 || PINIT (plugin->zero_iter, SELECT_IT_NON_ANONYMOUS)
1475 || PINIT (plugin->select_expiration, SELECT_IT_EXPIRATION)
1476 || PINIT (plugin->select_replication, SELECT_IT_REPLICATION) )
1479 GNUNET_free_non_null (plugin->cnffile);
1480 GNUNET_free (plugin);
1486 api = GNUNET_malloc (sizeof (struct GNUNET_DATASTORE_PluginFunctions));
1488 api->estimate_size = &mysql_plugin_estimate_size;
1489 api->put = &mysql_plugin_put;
1490 api->update = &mysql_plugin_update;
1491 api->get_key = &mysql_plugin_get_key;
1492 api->get_replication = &mysql_plugin_get_replication;
1493 api->get_expiration = &mysql_plugin_get_expiration;
1494 api->get_zero_anonymity = &mysql_plugin_get_zero_anonymity;
1495 api->drop = &mysql_plugin_drop;
1496 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1497 "mysql", _("Mysql database running\n"));
1503 * Exit point from the plugin.
1504 * @param cls our "struct Plugin*"
1505 * @return always NULL
1508 libgnunet_plugin_datastore_mysql_done (void *cls)
1510 struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1511 struct Plugin *plugin = api->cls;
1514 GNUNET_free_non_null (plugin->cnffile);
1515 GNUNET_free (plugin);
1517 mysql_library_end ();
1521 /* end of plugin_datastore_mysql.c */