fix
[oweals/gnunet.git] / src / datastore / plugin_datastore_mysql.c
1 /*
2      This file is part of GNUnet
3      (C) 2009, 2010, 2011 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  * @verbatim
52        [datastore]
53        DATABASE = "mysql"
54    @endverbatim
55  * 2) Then access mysql as root,
56  * @verbatim
57      $ mysql -u root -p
58    @endverbatim
59  *    and do the following. [You should replace $USER with the username
60  *    that will be running the gnunetd process].
61  * @verbatim
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');
66       FLUSH PRIVILEGES;
67    @endverbatim
68  * 3) In the $HOME directory of $USER, create a ".my.cnf" file
69  *    with the following lines
70  * @verbatim
71       [client]
72       user=$USER
73       password=$the_password_you_like
74    @endverbatim
75  *
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,
81  * of course).<p>
82  *
83  * 4) Still, perhaps you should briefly try if the DB connection
84  *    works. First, login as $USER. Then use,
85  *
86  * @verbatim
87      $ mysql -u $USER -p $the_password_you_like
88      mysql> use gnunet;
89    @endverbatim
90  *
91  *    If you get the message &quot;Database changed&quot; it probably works.
92  *
93  *    [If you get &quot;ERROR 2002: Can't connect to local MySQL server
94  *     through socket '/tmp/mysql.sock' (2)&quot; it may be resolvable by
95  *     &quot;ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock&quot;
96  *     so there may be some additional trouble depending on your mysql setup.]
97  *
98  * REPAIRING TABLES
99  *
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):
107  * @verbatim
108      mysql> REPAIR TABLE gn090;
109    @endverbatim
110  *
111  * PROBLEMS?
112  *
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.
117  */
118
119 #include "platform.h"
120 #include "gnunet_datastore_plugin.h"
121 #include "gnunet_util_lib.h"
122 #include <mysql/mysql.h>
123
124 #define DEBUG_MYSQL GNUNET_NO
125
126 #define MAX_DATUM_SIZE 65536
127
128 /**
129  * Maximum number of supported parameters for a prepared
130  * statement.  Increase if needed.
131  */
132 #define MAX_PARAM 16
133
134 /**
135  * Die with an error message that indicates
136  * a failure of the command 'cmd' with the message given
137  * by strerror(errno).
138  */
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);
140
141 /**
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).
145  */
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);
147
148
149 struct GNUNET_MysqlStatementHandle
150 {
151   struct GNUNET_MysqlStatementHandle *next;
152
153   struct GNUNET_MysqlStatementHandle *prev;
154
155   char *query;
156
157   MYSQL_STMT *statement;
158
159   int valid;
160
161 };
162
163
164 /**
165  * Context for all functions in this plugin.
166  */
167 struct Plugin 
168 {
169   /**
170    * Our execution environment.
171    */
172   struct GNUNET_DATASTORE_PluginEnvironment *env;
173
174   /**
175    * Handle to talk to MySQL.
176    */
177   MYSQL *dbf;
178   
179   /**
180    * We keep all prepared statements in a DLL.  This is the head.
181    */
182   struct GNUNET_MysqlStatementHandle *shead;
183
184   /**
185    * We keep all prepared statements in a DLL.  This is the tail.
186    */
187   struct GNUNET_MysqlStatementHandle *stail;
188
189   /**
190    * Filename of "my.cnf" (msyql configuration).
191    */
192   char *cnffile;
193
194   /**
195    * Prepared statements.
196    */
197 #define INSERT_ENTRY "INSERT INTO gn090 (repl,type,prio,anonLevel,expire,hash,vhash,value) VALUES (?,?,?,?,?,?,?,?)"
198   struct GNUNET_MysqlStatementHandle *insert_entry;
199   
200 #define DELETE_ENTRY_BY_UID "DELETE FROM gn090 WHERE uid=?"
201   struct GNUNET_MysqlStatementHandle *delete_entry_by_uid;
202
203 #define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn090 WHERE hash=?"
204   struct GNUNET_MysqlStatementHandle *count_entry_by_hash;
205   
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;
208
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;
211
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;
214  
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;
217
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;
220  
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;
223   
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;
226
227 #define UPDATE_ENTRY "UPDATE gn090 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE uid=?"
228   struct GNUNET_MysqlStatementHandle *update_entry;
229
230 #define DEC_REPL "UPDATE gn090 SET repl=GREATEST (0, repl - 1) WHERE uid=?"
231   struct GNUNET_MysqlStatementHandle *dec_repl;
232
233 #define SELECT_SIZE "SELECT SUM(BIT_LENGTH(value) DIV 8) FROM gn090"
234   struct GNUNET_MysqlStatementHandle *get_size;
235
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;
238
239 #define SELECT_IT_EXPIRATION "(SELECT type,prio,anonLevel,expire,hash,value,uid FROM gn090 WHERE expire < ? ORDER BY prio ASC LIMIT 1) "\
240   "UNION "\
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;
244
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;
247
248 };
249
250
251 /**
252  * Obtain the location of ".my.cnf".
253  *
254  * @param cfg our configuration
255  * @return NULL on error
256  */
257 static char *
258 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg)
259 {
260   char *cnffile;
261   char *home_dir;
262   struct stat st;
263 #ifndef WINDOWS
264   struct passwd *pw;
265 #endif
266   int configured;
267
268 #ifndef WINDOWS
269   pw = getpwuid (getuid ());
270   if (!pw)
271     {
272       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 
273                            "getpwuid");
274       return NULL;
275     }
276   if (GNUNET_YES ==
277       GNUNET_CONFIGURATION_have_value (cfg,
278                                        "datastore-mysql", "CONFIG"))
279     {
280       GNUNET_assert (GNUNET_OK == 
281                      GNUNET_CONFIGURATION_get_value_filename (cfg,
282                                                               "datastore-mysql", "CONFIG", &cnffile));
283       configured = GNUNET_YES;
284     }
285   else
286     {
287       home_dir = GNUNET_strdup (pw->pw_dir);
288 #else
289       home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
290       plibc_conv_to_win_path ("~/", home_dir);
291 #endif
292       GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
293       GNUNET_free (home_dir);
294       configured = GNUNET_NO;
295     }
296   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
297               _("Trying to use file `%s' for MySQL configuration.\n"),
298               cnffile);
299   if ((0 != STAT (cnffile, &st)) ||
300       (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
301     {
302       if (configured == GNUNET_YES)
303         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
304                     _("Could not access file `%s': %s\n"), cnffile,
305                     STRERROR (errno));
306       GNUNET_free (cnffile);
307       return NULL;
308     }
309   return cnffile;
310 }
311
312
313 /**
314  * Free a prepared statement.
315  *
316  * @param plugin plugin context
317  * @param s prepared statement
318  */
319 static void
320 prepared_statement_destroy (struct Plugin *plugin, 
321                             struct GNUNET_MysqlStatementHandle *s)
322 {
323   GNUNET_CONTAINER_DLL_remove (plugin->shead,
324                                plugin->stail,
325                                s);
326   if (s->valid)
327     mysql_stmt_close (s->statement);
328   GNUNET_free (s->query);
329   GNUNET_free (s);
330 }
331
332
333 /**
334  * Close database connection and all prepared statements (we got a DB
335  * disconnect error).
336  * 
337  * @param plugin plugin context
338  */
339 static int
340 iclose (struct Plugin *plugin)
341 {
342   struct GNUNET_MysqlStatementHandle *spos;
343
344   spos = plugin->shead;
345   while (NULL != plugin->shead)
346     prepared_statement_destroy (plugin,
347                                 plugin->shead);
348   if (plugin->dbf != NULL)
349     {
350       mysql_close (plugin->dbf);
351       plugin->dbf = NULL;
352     }
353   return GNUNET_OK;
354 }
355
356
357 /**
358  * Open the connection with the database (and initialize
359  * our default options).
360  *
361  * @param plugin plugin context
362  * @return GNUNET_OK on success
363  */
364 static int
365 iopen (struct Plugin *plugin)
366 {
367   char *mysql_dbname;
368   char *mysql_server;
369   char *mysql_user;
370   char *mysql_password;
371   unsigned long long mysql_port;
372   my_bool reconnect;
373   unsigned int timeout;
374
375   plugin->dbf = mysql_init (NULL);
376   if (plugin->dbf == NULL)
377     return GNUNET_SYSERR;
378   if (plugin->cnffile != NULL)
379     mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_FILE, plugin->cnffile);
380   mysql_options (plugin->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
381   reconnect = 0;
382   mysql_options (plugin->dbf, MYSQL_OPT_RECONNECT, &reconnect);
383   mysql_options (plugin->dbf,
384                  MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
385   mysql_options(plugin->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
386   timeout = 60; /* in seconds */
387   mysql_options (plugin->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
388   mysql_options (plugin->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
389   mysql_dbname = NULL;
390   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
391                                                      "datastore-mysql", "DATABASE"))
392     GNUNET_assert (GNUNET_OK == 
393                    GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
394                                                           "datastore-mysql", "DATABASE", 
395                                                           &mysql_dbname));
396   else
397     mysql_dbname = GNUNET_strdup ("gnunet");
398   mysql_user = NULL;
399   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
400                                                      "datastore-mysql", "USER"))
401     {
402       GNUNET_assert (GNUNET_OK == 
403                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
404                                                            "datastore-mysql", "USER", 
405                                                            &mysql_user));
406     }
407   mysql_password = NULL;
408   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
409                                                      "datastore-mysql", "PASSWORD"))
410     {
411       GNUNET_assert (GNUNET_OK ==
412                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
413                                                            "datastore-mysql", "PASSWORD",
414                                                            &mysql_password));
415     }
416   mysql_server = NULL;
417   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
418                                                      "datastore-mysql", "HOST"))
419     {
420       GNUNET_assert (GNUNET_OK == 
421                     GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
422                                                            "datastore-mysql", "HOST", 
423                                                            &mysql_server));
424     }
425   mysql_port = 0;
426   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (plugin->env->cfg,
427                                                      "datastore-mysql", "PORT"))
428     {
429       GNUNET_assert (GNUNET_OK ==
430                     GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg, "datastore-mysql",
431                                                            "PORT", &mysql_port));
432     }
433
434   GNUNET_assert (mysql_dbname != NULL);
435   mysql_real_connect (plugin->dbf, 
436                       mysql_server, 
437                       mysql_user, mysql_password,
438                       mysql_dbname, 
439                       (unsigned int) mysql_port, NULL,
440                       CLIENT_IGNORE_SIGPIPE);
441   GNUNET_free_non_null (mysql_server);
442   GNUNET_free_non_null (mysql_user);
443   GNUNET_free_non_null (mysql_password);
444   GNUNET_free (mysql_dbname);
445   if (mysql_error (plugin->dbf)[0])
446     {
447       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
448                  "mysql_real_connect", plugin);
449       return GNUNET_SYSERR;
450     }
451   return GNUNET_OK;
452 }
453
454
455 /**
456  * Run the given MySQL statement.
457  *
458  * @param plugin plugin context
459  * @param statement SQL statement to run
460  * @return GNUNET_OK on success, GNUNET_SYSERR on error
461  */
462 static int
463 run_statement (struct Plugin *plugin,
464                const char *statement)
465 {
466   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
467     return GNUNET_SYSERR;
468   mysql_query (plugin->dbf, statement);
469   if (mysql_error (plugin->dbf)[0])
470     {
471       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
472                  "mysql_query", plugin);
473       iclose (plugin);
474       return GNUNET_SYSERR;
475     }
476   return GNUNET_OK;
477 }
478
479
480 /**
481  * Create a prepared statement.
482  *
483  * @param plugin plugin context
484  * @param statement SQL statement text to prepare
485  * @return NULL on error
486  */
487 static struct GNUNET_MysqlStatementHandle *
488 prepared_statement_create (struct Plugin *plugin, 
489                            const char *statement)
490 {
491   struct GNUNET_MysqlStatementHandle *ret;
492
493   ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
494   ret->query = GNUNET_strdup (statement);
495   GNUNET_CONTAINER_DLL_insert (plugin->shead,
496                                plugin->stail,
497                                ret);
498   return ret;
499 }
500
501
502 /**
503  * Prepare a statement for running.
504  *
505  * @param plugin plugin context
506  * @param ret handle to prepared statement
507  * @return GNUNET_OK on success
508  */
509 static int
510 prepare_statement (struct Plugin *plugin, 
511                    struct GNUNET_MysqlStatementHandle *ret)
512 {
513   if (GNUNET_YES == ret->valid)
514     return GNUNET_OK;
515   if ((NULL == plugin->dbf) && 
516       (GNUNET_OK != iopen (plugin)))
517     return GNUNET_SYSERR;
518   ret->statement = mysql_stmt_init (plugin->dbf);
519   if (ret->statement == NULL)
520     {
521       iclose (plugin);
522       return GNUNET_SYSERR;
523     }
524   if (mysql_stmt_prepare (ret->statement, 
525                           ret->query,
526                           strlen (ret->query)))
527     {
528       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
529                        "mysql",
530                        _("Failed to prepare statement `%s'\n"),
531                        ret->query);
532       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
533                  "mysql_stmt_prepare", 
534                  plugin);
535       mysql_stmt_close (ret->statement);
536       ret->statement = NULL;
537       iclose (plugin);
538       return GNUNET_SYSERR;
539     }
540   ret->valid = GNUNET_YES;
541   return GNUNET_OK;
542
543 }
544
545
546 /**
547  * Bind the parameters for the given MySQL statement
548  * and run it.
549  *
550  * @param plugin plugin context
551  * @param s statement to bind and run
552  * @param ap arguments for the binding
553  * @return GNUNET_SYSERR on error, GNUNET_OK on success
554  */
555 static int
556 init_params (struct Plugin *plugin,
557              struct GNUNET_MysqlStatementHandle *s,
558              va_list ap)
559 {
560   MYSQL_BIND qbind[MAX_PARAM];
561   unsigned int pc;
562   unsigned int off;
563   enum enum_field_types ft;
564
565   pc = mysql_stmt_param_count (s->statement);
566   if (pc > MAX_PARAM)
567     {
568       /* increase internal constant! */
569       GNUNET_break (0);
570       return GNUNET_SYSERR;
571     }
572   memset (qbind, 0, sizeof (qbind));
573   off = 0;
574   ft = 0;
575   while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
576     {
577       qbind[off].buffer_type = ft;
578       switch (ft)
579         {
580         case MYSQL_TYPE_FLOAT:
581           qbind[off].buffer = va_arg (ap, float *);
582           break;
583         case MYSQL_TYPE_LONGLONG:
584           qbind[off].buffer = va_arg (ap, unsigned long long *);
585           qbind[off].is_unsigned = va_arg (ap, int);
586           break;
587         case MYSQL_TYPE_LONG:
588           qbind[off].buffer = va_arg (ap, unsigned int *);
589           qbind[off].is_unsigned = va_arg (ap, int);
590           break;
591         case MYSQL_TYPE_VAR_STRING:
592         case MYSQL_TYPE_STRING:
593         case MYSQL_TYPE_BLOB:
594           qbind[off].buffer = va_arg (ap, void *);
595           qbind[off].buffer_length = va_arg (ap, unsigned long);
596           qbind[off].length = va_arg (ap, unsigned long *);
597           break;
598         default:
599           /* unsupported type */
600           GNUNET_break (0);
601           return GNUNET_SYSERR;
602         }
603       pc--;
604       off++;
605     }
606   if (! ( (pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1) ) )
607     {
608       GNUNET_assert (0);
609       return GNUNET_SYSERR;
610     }
611   if (mysql_stmt_bind_param (s->statement, qbind))
612     {
613       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
614                   _("`%s' failed at %s:%d with error: %s\n"),
615                   "mysql_stmt_bind_param",
616                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
617       iclose (plugin);
618       return GNUNET_SYSERR;
619     }
620   if (mysql_stmt_execute (s->statement))
621     {
622       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
623                   _("`%s' for `%s' failed at %s:%d with error: %s\n"),
624                   "mysql_stmt_execute",
625                   s->query,
626                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
627       iclose (plugin);
628       return GNUNET_SYSERR;
629     }
630   return GNUNET_OK;
631 }
632
633
634 /**
635  * Run a prepared SELECT statement.
636  *
637  * @param plugin plugin context
638  * @param s statement to run
639  * @param result_size number of elements in results array
640  * @param results pointer to already initialized MYSQL_BIND
641  *        array (of sufficient size) for passing results
642  * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
643  *        values (size + buffer-reference for pointers); terminated
644  *        with "-1"
645  * @return GNUNET_SYSERR on error, otherwise GNUNET_OK or GNUNET_NO (no result)
646  */
647 static int
648 prepared_statement_run_select_va (struct Plugin *plugin,
649                                   struct GNUNET_MysqlStatementHandle *s,
650                                   unsigned int result_size,
651                                   MYSQL_BIND *results,
652                                   va_list ap)
653 {
654   int ret;
655   unsigned int rsize;
656
657   if (GNUNET_OK != prepare_statement (plugin, s))
658     {
659       GNUNET_break (0);
660       return GNUNET_SYSERR;
661     }
662   if (GNUNET_OK != init_params (plugin, s, ap))
663     {
664       GNUNET_break (0);
665       return GNUNET_SYSERR;
666     }
667   rsize = mysql_stmt_field_count (s->statement);
668   if (rsize > result_size)
669     {
670       GNUNET_break (0);
671       return GNUNET_SYSERR;
672     }
673   if (mysql_stmt_bind_result (s->statement, results))
674     {
675       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
676                   _("`%s' failed at %s:%d with error: %s\n"),
677                   "mysql_stmt_bind_result",
678                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
679       iclose (plugin);
680       return GNUNET_SYSERR;
681     }
682   ret = mysql_stmt_fetch (s->statement);
683   if (ret == MYSQL_NO_DATA)
684     return GNUNET_NO;
685   if (ret != 0)
686     {
687       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
688                   _("`%s' failed at %s:%d with error: %s\n"),
689                   "mysql_stmt_fetch",
690                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
691       iclose (plugin);
692       return GNUNET_SYSERR;
693     }
694   mysql_stmt_reset (s->statement);
695   return GNUNET_OK;
696 }
697
698
699 /**
700  * Run a prepared SELECT statement.
701  *
702  * @param plugin plugin context
703  * @param s statement to run
704  * @param result_size number of elements in results array
705  * @param results pointer to already initialized MYSQL_BIND
706  *        array (of sufficient size) for passing results
707  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
708  *        values (size + buffer-reference for pointers); terminated
709  *        with "-1"
710  * @return GNUNET_SYSERR on error, otherwise
711  *         the number of successfully affected (or queried) rows
712  */
713 static int
714 prepared_statement_run_select (struct Plugin *plugin,
715                                struct GNUNET_MysqlStatementHandle *s,
716                                unsigned int result_size,
717                                MYSQL_BIND *results,
718                                ...)
719 {
720   va_list ap;
721   int ret;
722
723   va_start (ap, results);
724   ret = prepared_statement_run_select_va (plugin, s, 
725                                           result_size, results,
726                                           ap);
727   va_end (ap);
728   return ret;
729 }
730
731
732 /**
733  * Run a prepared statement that does NOT produce results.
734  *
735  * @param plugin plugin context
736  * @param s statement to run
737  * @param insert_id NULL or address where to store the row ID of whatever
738  *        was inserted (only for INSERT statements!)
739  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
740  *        values (size + buffer-reference for pointers); terminated
741  *        with "-1"
742  * @return GNUNET_SYSERR on error, otherwise
743  *         the number of successfully affected rows
744  */
745 static int
746 prepared_statement_run (struct Plugin *plugin,
747                         struct GNUNET_MysqlStatementHandle *s,
748                         unsigned long long *insert_id, ...)
749 {
750   va_list ap;
751   int affected;
752
753   if (GNUNET_OK != prepare_statement (plugin, s))
754     return GNUNET_SYSERR;
755   va_start (ap, insert_id);
756   if (GNUNET_OK != init_params (plugin, s, ap))
757     {
758       va_end (ap);
759       return GNUNET_SYSERR;
760     }
761   va_end (ap);
762   affected = mysql_stmt_affected_rows (s->statement);
763   if (NULL != insert_id)
764     *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
765   mysql_stmt_reset (s->statement);
766   return affected;
767 }
768
769
770 /**
771  * Delete an entry from the gn090 table.
772  *
773  * @param plugin plugin context
774  * @param uid unique ID of the entry to delete
775  * @return GNUNET_OK on success, GNUNET_NO if no such value exists, GNUNET_SYSERR on error
776  */
777 static int
778 do_delete_entry (struct Plugin *plugin,
779                  unsigned long long uid)
780 {
781   int ret;
782  
783 #if DEBUG_MYSQL
784   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
785               "Deleting value %llu from gn090 table\n",
786               uid);
787 #endif
788   ret = prepared_statement_run (plugin,
789                                 plugin->delete_entry_by_uid,
790                                 NULL,
791                                 MYSQL_TYPE_LONGLONG, &uid, GNUNET_YES,
792                                 -1);
793   if (ret >= 0)
794     return GNUNET_OK;
795   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
796               "Deleting value %llu from gn090 table failed\n",
797               uid);
798   return ret;
799 }
800
801
802 /**
803  * Get an estimate of how much space the database is
804  * currently using.
805  *
806  * @param cls our "struct Plugin *"
807  * @return number of bytes used on disk
808  */
809 static unsigned long long
810 mysql_plugin_estimate_size (void *cls)
811 {
812   struct Plugin *plugin = cls;
813   MYSQL_BIND cbind[1];
814   long long total;
815
816   memset (cbind, 0, sizeof (cbind));
817   total = 0;
818   cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
819   cbind[0].buffer = &total;
820   cbind[0].is_unsigned = GNUNET_NO;
821   if (GNUNET_OK != 
822       prepared_statement_run_select (plugin,
823                                      plugin->get_size,
824                                      1, cbind, 
825                                      -1))
826     return 0;
827   return total;
828 }
829
830
831 /**
832  * Store an item in the datastore.
833  *
834  * @param cls closure
835  * @param key key for the item
836  * @param size number of bytes in data
837  * @param data content stored
838  * @param type type of the content
839  * @param priority priority of the content
840  * @param anonymity anonymity-level for the content
841  * @param replication replication-level for the content
842  * @param expiration expiration time for the content
843  * @param msg set to error message
844  * @return GNUNET_OK on success
845  */
846 static int
847 mysql_plugin_put (void *cls,
848                   const GNUNET_HashCode * key,
849                   uint32_t size,
850                   const void *data,
851                   enum GNUNET_BLOCK_Type type,
852                   uint32_t priority,
853                   uint32_t anonymity,
854                   uint32_t replication,
855                   struct GNUNET_TIME_Absolute expiration,
856                   char **msg)
857 {
858   struct Plugin *plugin = cls;
859   unsigned int irepl = replication;
860   unsigned int ipriority = priority;
861   unsigned int ianonymity = anonymity;
862   unsigned long long lexpiration = expiration.abs_value;
863   unsigned long hashSize;
864   unsigned long hashSize2;
865   unsigned long lsize;
866   GNUNET_HashCode vhash;
867
868   if (size > MAX_DATUM_SIZE)
869     {
870       GNUNET_break (0);
871       return GNUNET_SYSERR;
872     }
873   hashSize = sizeof (GNUNET_HashCode);
874   hashSize2 = sizeof (GNUNET_HashCode);
875   lsize = size;
876   GNUNET_CRYPTO_hash (data, size, &vhash);
877   if (GNUNET_OK !=
878       prepared_statement_run (plugin,
879                               plugin->insert_entry,
880                               NULL,
881                               MYSQL_TYPE_LONG, &irepl, GNUNET_YES,
882                               MYSQL_TYPE_LONG, &type, GNUNET_YES,
883                               MYSQL_TYPE_LONG, &ipriority, GNUNET_YES,
884                               MYSQL_TYPE_LONG, &ianonymity, GNUNET_YES,
885                               MYSQL_TYPE_LONGLONG, &lexpiration, GNUNET_YES,
886                               MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
887                               MYSQL_TYPE_BLOB, &vhash, hashSize2, &hashSize2,
888                               MYSQL_TYPE_BLOB, data, lsize, &lsize, 
889                               -1))
890     return GNUNET_SYSERR;    
891 #if DEBUG_MYSQL
892   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
893               "Inserted value `%s' with size %u into gn090 table\n",
894               GNUNET_h2s (key),
895               (unsigned int) size);
896 #endif
897   if (size > 0)
898     plugin->env->duc (plugin->env->cls,
899                       size);
900   return GNUNET_OK;
901 }
902
903
904 /**
905  * Update the priority for a particular key in the datastore.  If
906  * the expiration time in value is different than the time found in
907  * the datastore, the higher value should be kept.  For the
908  * anonymity level, the lower value is to be used.  The specified
909  * priority should be added to the existing priority, ignoring the
910  * priority in value.
911  *
912  * Note that it is possible for multiple values to match this put.
913  * In that case, all of the respective values are updated.
914  *
915  * @param cls our "struct Plugin*"
916  * @param uid unique identifier of the datum
917  * @param delta by how much should the priority
918  *     change?  If priority + delta < 0 the
919  *     priority should be set to 0 (never go
920  *     negative).
921  * @param expire new expiration time should be the
922  *     MAX of any existing expiration time and
923  *     this value
924  * @param msg set to error message
925  * @return GNUNET_OK on success
926  */
927 static int
928 mysql_plugin_update (void *cls,
929                      uint64_t uid,
930                      int delta, 
931                      struct GNUNET_TIME_Absolute expire,
932                      char **msg)
933 {
934   struct Plugin *plugin = cls;
935   unsigned long long vkey = uid;
936   unsigned long long lexpire = expire.abs_value;
937   int ret;
938
939 #if DEBUG_MYSQL
940   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
941               "Updating value %llu adding %d to priority and maxing exp at %llu\n",
942               vkey,
943               delta,
944               lexpire);
945 #endif
946   ret = prepared_statement_run (plugin,
947                                 plugin->update_entry,
948                                 NULL,
949                                 MYSQL_TYPE_LONG, &delta, GNUNET_NO,
950                                 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
951                                 MYSQL_TYPE_LONGLONG, &lexpire, GNUNET_YES,
952                                 MYSQL_TYPE_LONGLONG, &vkey, GNUNET_YES, 
953                                 -1);
954   if (ret != GNUNET_OK)
955     {
956       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
957                   "Failed to update value %llu\n",
958                   vkey);
959     }
960   return ret;
961 }
962
963
964 /**
965  * Run the given select statement and call 'proc' on the resulting
966  * values (which must be in particular positions).
967  *
968  * @param plugin the plugin handle
969  * @param stmt select statement to run
970  * @param proc function to call on result
971  * @param proc_cls closure for proc
972  * @param ... arguments to initialize stmt
973  */
974 static void 
975 execute_select (struct Plugin *plugin,
976                 struct GNUNET_MysqlStatementHandle *stmt,
977                 PluginDatumProcessor proc, void *proc_cls,
978                 ...)
979 {
980   va_list ap;
981   int ret;
982   unsigned int type;
983   unsigned int priority;
984   unsigned int anonymity;
985   unsigned long long exp;
986   unsigned long hashSize;
987   unsigned long size;
988   unsigned long long uid;
989   char value[GNUNET_DATASTORE_MAX_VALUE_SIZE];
990   GNUNET_HashCode key;
991   struct GNUNET_TIME_Absolute expiration;
992   MYSQL_BIND rbind[7];
993
994   hashSize = sizeof (GNUNET_HashCode);
995   memset (rbind, 0, sizeof (rbind));
996   rbind[0].buffer_type = MYSQL_TYPE_LONG;
997   rbind[0].buffer = &type;
998   rbind[0].is_unsigned = 1;
999   rbind[1].buffer_type = MYSQL_TYPE_LONG;
1000   rbind[1].buffer = &priority;
1001   rbind[1].is_unsigned = 1;
1002   rbind[2].buffer_type = MYSQL_TYPE_LONG;
1003   rbind[2].buffer = &anonymity;
1004   rbind[2].is_unsigned = 1;
1005   rbind[3].buffer_type = MYSQL_TYPE_LONGLONG;
1006   rbind[3].buffer = &exp;
1007   rbind[3].is_unsigned = 1;
1008   rbind[4].buffer_type = MYSQL_TYPE_BLOB;
1009   rbind[4].buffer = &key;
1010   rbind[4].buffer_length = hashSize;
1011   rbind[4].length = &hashSize;
1012   rbind[5].buffer_type = MYSQL_TYPE_BLOB;
1013   rbind[5].buffer = value;
1014   rbind[5].buffer_length = size = sizeof (value);
1015   rbind[5].length = &size;
1016   rbind[6].buffer_type = MYSQL_TYPE_LONGLONG;
1017   rbind[6].buffer = &uid;
1018   rbind[6].is_unsigned = 1;
1019
1020   va_start (ap, proc_cls);
1021   ret = prepared_statement_run_select_va (plugin,
1022                                           stmt,
1023                                           7, rbind,
1024                                           ap);
1025   va_end (ap);
1026   if (ret <= 0)
1027     {
1028       proc (proc_cls, 
1029             NULL, 0, NULL, 0, 0, 0, 
1030             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1031       return;
1032     }
1033   GNUNET_assert (size <= sizeof(value));
1034   if ( (rbind[4].buffer_length != sizeof (GNUNET_HashCode)) ||
1035        (hashSize != sizeof (GNUNET_HashCode)) )
1036     {
1037       GNUNET_break (0);
1038       proc (proc_cls, 
1039             NULL, 0, NULL, 0, 0, 0, 
1040             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1041       return;
1042     }     
1043 #if DEBUG_MYSQL
1044   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1045               "Found %u-byte value under key `%s' with prio %u, anon %u, expire %llu selecting from gn090 table\n",
1046               (unsigned int) size,
1047               GNUNET_h2s (&key),
1048               priority,
1049               anonymity,
1050               exp);
1051 #endif
1052   GNUNET_assert (size < MAX_DATUM_SIZE);
1053   expiration.abs_value = exp;
1054   ret = proc (proc_cls, 
1055               &key,
1056               size, value,
1057               type, priority, anonymity, expiration,
1058               uid);
1059   if (ret == GNUNET_NO)
1060     {
1061       do_delete_entry (plugin, uid);
1062       if (size != 0)
1063         plugin->env->duc (plugin->env->cls,
1064                           - size);
1065     }
1066 }
1067
1068
1069
1070 /**
1071  * Get one of the results for a particular key in the datastore.
1072  *
1073  * @param cls closure
1074  * @param offset offset of the result (mod #num-results); 
1075  *               specific ordering does not matter for the offset
1076  * @param key key to match, never NULL
1077  * @param vhash hash of the value, maybe NULL (to
1078  *        match all values that have the right key).
1079  *        Note that for DBlocks there is no difference
1080  *        betwen key and vhash, but for other blocks
1081  *        there may be!
1082  * @param type entries of which type are relevant?
1083  *     Use 0 for any type.
1084  * @param proc function to call on each matching value; however,
1085  *        after the first call to "proc", the plugin must wait
1086  *        until "NextRequest" was called before giving the processor
1087  *        the next item; finally, the "proc" should be called once
1088  *        once with a NULL value at the end ("next_cls" should be NULL
1089  *        for that last call)
1090  * @param proc_cls closure for proc
1091  */
1092 static void
1093 mysql_plugin_get_key (void *cls,
1094                       uint64_t offset,
1095                       const GNUNET_HashCode *key,
1096                       const GNUNET_HashCode *vhash,
1097                       enum GNUNET_BLOCK_Type type,                    
1098                       PluginDatumProcessor proc, void *proc_cls)
1099 {
1100   struct Plugin *plugin = cls;
1101   int ret;
1102   MYSQL_BIND cbind[1];
1103   long long total;
1104   unsigned long hashSize;
1105   unsigned long hashSize2;
1106   unsigned long long off;
1107
1108   GNUNET_assert (key != NULL);
1109   GNUNET_assert (NULL != proc);
1110   hashSize = sizeof (GNUNET_HashCode);
1111   hashSize2 = sizeof (GNUNET_HashCode);
1112   memset (cbind, 0, sizeof (cbind));
1113   total = -1;
1114   cbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
1115   cbind[0].buffer = &total;
1116   cbind[0].is_unsigned = GNUNET_NO;
1117   if (type != 0)
1118     {
1119       if (vhash != NULL)
1120         {
1121           ret =
1122             prepared_statement_run_select (plugin,
1123                                            plugin->count_entry_by_hash_vhash_and_type, 
1124                                            1, cbind, 
1125                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1126                                            MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2, 
1127                                            MYSQL_TYPE_LONG, &type, GNUNET_YES,
1128                                            -1);
1129         }
1130       else
1131         {
1132           ret =
1133             prepared_statement_run_select (plugin,
1134                                            plugin->count_entry_by_hash_and_type, 
1135                                            1, cbind, 
1136                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1137                                            MYSQL_TYPE_LONG, &type, GNUNET_YES,
1138                                            -1);
1139         }
1140     }
1141   else
1142     {
1143       if (vhash != NULL)
1144         {
1145           ret =
1146             prepared_statement_run_select (plugin,
1147                                            plugin->count_entry_by_hash_and_vhash, 
1148                                            1, cbind,
1149                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1150                                            MYSQL_TYPE_BLOB, vhash, hashSize2, &hashSize2, 
1151                                            -1);
1152
1153         }
1154       else
1155         {
1156           ret =
1157             prepared_statement_run_select (plugin,
1158                                            plugin->count_entry_by_hash,
1159                                            1, cbind, 
1160                                            MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1161                                            -1);
1162         }
1163     }
1164   if ((ret != GNUNET_OK) || (0 >= total))
1165     {
1166       proc (proc_cls, 
1167             NULL, 0, NULL, 0, 0, 0, 
1168             GNUNET_TIME_UNIT_ZERO_ABS, 0);
1169       return;
1170     }
1171   offset = offset % total;
1172   off = (unsigned long long) offset;
1173 #if DEBUG_MYSQL
1174   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1175               "Obtaining %llu/%lld result for GET `%s'\n",
1176               off,
1177               total,
1178               GNUNET_h2s (key));
1179 #endif
1180
1181   if (type != GNUNET_BLOCK_TYPE_ANY)
1182     {
1183       if (NULL != vhash)
1184         {
1185           execute_select (plugin,
1186                           plugin->select_entry_by_hash_vhash_and_type, 
1187                           proc, proc_cls,
1188                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1189                           MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize,
1190                           MYSQL_TYPE_LONG, &type, GNUNET_YES, 
1191                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1192                           -1);
1193         }
1194       else
1195         {
1196           execute_select (plugin,
1197                           plugin->select_entry_by_hash_and_type, 
1198                           proc, proc_cls,
1199                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1200                           MYSQL_TYPE_LONG, &type, GNUNET_YES, 
1201                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1202                           -1);
1203         }
1204     }
1205   else
1206     {
1207       if (NULL != vhash)
1208         {
1209           execute_select (plugin,
1210                           plugin->select_entry_by_hash_and_vhash, 
1211                           proc, proc_cls,
1212                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize, 
1213                           MYSQL_TYPE_BLOB, vhash, hashSize, &hashSize, 
1214                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES, 
1215                           -1);
1216         }
1217       else
1218         {
1219           execute_select (plugin,
1220                           plugin->select_entry_by_hash, 
1221                           proc, proc_cls,
1222                           MYSQL_TYPE_BLOB, key, hashSize, &hashSize,
1223                           MYSQL_TYPE_LONGLONG, &off, GNUNET_YES, 
1224                           -1);
1225         }
1226     }
1227 }
1228
1229
1230 /**
1231  * Get a zero-anonymity datum from the datastore.
1232  *
1233  * @param cls our "struct Plugin*"
1234  * @param offset offset of the result
1235  * @param type entries of which type should be considered?
1236  *        Use 0 for any type.
1237  * @param iter function to call on each matching value;
1238  *        will be called once with a NULL value at the end
1239  * @param iter_cls closure for iter
1240  */
1241 static void
1242 mysql_plugin_get_zero_anonymity (void *cls,
1243                                  uint64_t offset,
1244                                  enum GNUNET_BLOCK_Type type,
1245                                  PluginDatumProcessor proc, void *proc_cls)
1246 {
1247   struct Plugin *plugin = cls;
1248   unsigned long long off;
1249
1250   off = (unsigned long long) offset;
1251   execute_select (plugin,
1252                   plugin->zero_iter,
1253                   proc, proc_cls,
1254                   MYSQL_TYPE_LONG, &type, GNUNET_YES,
1255                   MYSQL_TYPE_LONGLONG, &off, GNUNET_YES,
1256                   -1);
1257
1258 }
1259
1260
1261 /**
1262  * Context for 'repl_proc' function.
1263  */
1264 struct ReplCtx
1265 {
1266   
1267   /**
1268    * Plugin handle.
1269    */
1270   struct Plugin *plugin;
1271   
1272   /**
1273    * Function to call for the result (or the NULL).
1274    */
1275   PluginDatumProcessor proc;
1276   
1277   /**
1278    * Closure for proc.
1279    */
1280   void *proc_cls;
1281 };
1282
1283
1284 /**
1285  * Wrapper for the processor for 'mysql_plugin_get_replication'.
1286  * Decrements the replication counter and calls the original
1287  * iterator.
1288  *
1289  * @param cls closure
1290  * @param key key for the content
1291  * @param size number of bytes in data
1292  * @param data content stored
1293  * @param type type of the content
1294  * @param priority priority of the content
1295  * @param anonymity anonymity-level for the content
1296  * @param expiration expiration time for the content
1297  * @param uid unique identifier for the datum;
1298  *        maybe 0 if no unique identifier is available
1299  *
1300  * @return GNUNET_SYSERR to abort the iteration, GNUNET_OK to continue
1301  *         (continue on call to "next", of course),
1302  *         GNUNET_NO to delete the item and continue (if supported)
1303  */
1304 static int
1305 repl_proc (void *cls,
1306            const GNUNET_HashCode *key,
1307            uint32_t size,
1308            const void *data,
1309            enum GNUNET_BLOCK_Type type,
1310            uint32_t priority,
1311            uint32_t anonymity,
1312            struct GNUNET_TIME_Absolute expiration, 
1313            uint64_t uid)
1314 {
1315   struct ReplCtx *rc = cls;
1316   struct Plugin *plugin = rc->plugin;
1317   unsigned long long oid;
1318   int ret;
1319   int iret;
1320
1321   ret = rc->proc (rc->proc_cls,
1322                   key,
1323                   size, data, 
1324                   type, priority, anonymity, expiration,
1325                   uid);
1326   if (NULL != key)
1327     {
1328       oid = (unsigned long long) uid;
1329       iret = prepared_statement_run (plugin,
1330                                      plugin->dec_repl,
1331                                      NULL,
1332                                      MYSQL_TYPE_LONGLONG, &oid, GNUNET_YES, 
1333                                      -1);
1334       if (iret == GNUNET_SYSERR)
1335         {
1336           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1337                       "Failed to reduce replication counter\n");
1338           return GNUNET_SYSERR;
1339         }
1340     }
1341   return ret;
1342 }
1343
1344
1345 /**
1346  * Get a random item for replication.  Returns a single, not expired,
1347  * random item from those with the highest replication counters.  The
1348  * item's replication counter is decremented by one IF it was positive
1349  * before.  Call 'proc' with all values ZERO or NULL if the datastore
1350  * is empty.
1351  *
1352  * @param cls closure
1353  * @param proc function to call the value (once only).
1354  * @param iter_cls closure for proc
1355  */
1356 static void
1357 mysql_plugin_get_replication (void *cls,
1358                               PluginDatumProcessor proc, void *proc_cls)
1359 {
1360   struct Plugin *plugin = cls;
1361   struct ReplCtx rc;
1362   
1363   rc.plugin = plugin;
1364   rc.proc = proc;
1365   rc.proc_cls = proc_cls;
1366   execute_select (plugin,
1367                   plugin->select_replication, 
1368                   &repl_proc, &rc,
1369                   -1);
1370
1371 }
1372
1373
1374 /**
1375  * Get a random item for expiration.
1376  * Call 'proc' with all values ZERO or NULL if the datastore is empty.
1377  *
1378  * @param cls closure
1379  * @param proc function to call the value (once only).
1380  * @param proc_cls closure for proc
1381  */
1382 static void
1383 mysql_plugin_get_expiration (void *cls,
1384                              PluginDatumProcessor proc, void *proc_cls)
1385 {
1386   struct Plugin *plugin = cls;
1387   long long nt;
1388
1389   nt = (long long) GNUNET_TIME_absolute_get().abs_value;
1390   execute_select (plugin,
1391                   plugin->select_expiration, 
1392                   proc, proc_cls,
1393                   MYSQL_TYPE_LONGLONG, &nt, GNUNET_YES, 
1394                   -1);
1395
1396 }
1397
1398
1399 /**
1400  * Drop database.
1401  *
1402  * @param cls the "struct Plugin*"
1403  */
1404 static void 
1405 mysql_plugin_drop (void *cls)
1406 {
1407   struct Plugin *plugin = cls;
1408
1409   if (GNUNET_OK != run_statement (plugin,
1410                                   "DROP TABLE gn090"))
1411     return;           /* error */
1412   plugin->env->duc (plugin->env->cls, 0);
1413 }
1414
1415
1416 /**
1417  * Entry point for the plugin.
1418  *
1419  * @param cls the "struct GNUNET_DATASTORE_PluginEnvironment*"
1420  * @return our "struct Plugin*"
1421  */
1422 void *
1423 libgnunet_plugin_datastore_mysql_init (void *cls)
1424 {
1425   struct GNUNET_DATASTORE_PluginEnvironment *env = cls;
1426   struct GNUNET_DATASTORE_PluginFunctions *api;
1427   struct Plugin *plugin;
1428
1429   plugin = GNUNET_malloc (sizeof (struct Plugin));
1430   plugin->env = env;
1431   plugin->cnffile = get_my_cnf_path (env->cfg);
1432   if (GNUNET_OK != iopen (plugin))
1433     {
1434       iclose (plugin);
1435       GNUNET_free_non_null (plugin->cnffile);
1436       GNUNET_free (plugin);
1437       return NULL;
1438     }
1439 #define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
1440 #define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
1441   if (MRUNS ("CREATE TABLE IF NOT EXISTS gn090 ("
1442              " repl INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1443              " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1444              " prio INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1445              " anonLevel INT(11) UNSIGNED NOT NULL DEFAULT 0,"
1446              " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
1447              " hash BINARY(64) NOT NULL DEFAULT '',"
1448              " vhash BINARY(64) NOT NULL DEFAULT '',"
1449              " value BLOB NOT NULL DEFAULT '',"
1450              " uid BIGINT NOT NULL AUTO_INCREMENT,"
1451              " PRIMARY KEY (uid),"
1452              " INDEX idx_hash (hash(64)),"
1453              " INDEX idx_hash_uid (hash(64),uid),"
1454              " INDEX idx_hash_vhash (hash(64),vhash(64)),"
1455              " INDEX idx_hash_type_uid (hash(64),type,uid),"
1456              " INDEX idx_prio (prio),"
1457              " INDEX idx_repl (repl),"
1458              " INDEX idx_expire_prio (expire,prio),"
1459              " INDEX idx_anonLevel_uid (anonLevel,uid)"
1460              ") ENGINE=InnoDB") ||
1461       MRUNS ("SET AUTOCOMMIT = 1") ||
1462       PINIT (plugin->insert_entry, INSERT_ENTRY) ||
1463       PINIT (plugin->delete_entry_by_uid, DELETE_ENTRY_BY_UID) ||
1464       PINIT (plugin->select_entry_by_hash, SELECT_ENTRY_BY_HASH) ||
1465       PINIT (plugin->select_entry_by_hash_and_vhash, SELECT_ENTRY_BY_HASH_AND_VHASH)
1466       || PINIT (plugin->select_entry_by_hash_and_type, SELECT_ENTRY_BY_HASH_AND_TYPE)
1467       || PINIT (plugin->select_entry_by_hash_vhash_and_type,
1468                 SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1469       || PINIT (plugin->count_entry_by_hash, COUNT_ENTRY_BY_HASH)
1470       || PINIT (plugin->get_size, SELECT_SIZE)
1471       || PINIT (plugin->count_entry_by_hash_and_vhash, COUNT_ENTRY_BY_HASH_AND_VHASH)
1472       || PINIT (plugin->count_entry_by_hash_and_type, COUNT_ENTRY_BY_HASH_AND_TYPE)
1473       || PINIT (plugin->count_entry_by_hash_vhash_and_type,
1474                 COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE)
1475       || PINIT (plugin->update_entry, UPDATE_ENTRY)
1476       || PINIT (plugin->dec_repl, DEC_REPL)
1477       || PINIT (plugin->zero_iter, SELECT_IT_NON_ANONYMOUS) 
1478       || PINIT (plugin->select_expiration, SELECT_IT_EXPIRATION) 
1479       || PINIT (plugin->select_replication, SELECT_IT_REPLICATION) )
1480     {
1481       iclose (plugin);
1482       GNUNET_free_non_null (plugin->cnffile);
1483       GNUNET_free (plugin);
1484       return NULL;
1485     }
1486 #undef PINIT
1487 #undef MRUNS
1488
1489   api = GNUNET_malloc (sizeof (struct GNUNET_DATASTORE_PluginFunctions));
1490   api->cls = plugin;
1491   api->estimate_size = &mysql_plugin_estimate_size;
1492   api->put = &mysql_plugin_put;
1493   api->update = &mysql_plugin_update;
1494   api->get_key = &mysql_plugin_get_key;
1495   api->get_replication = &mysql_plugin_get_replication;
1496   api->get_expiration = &mysql_plugin_get_expiration;
1497   api->get_zero_anonymity = &mysql_plugin_get_zero_anonymity;
1498   api->drop = &mysql_plugin_drop;
1499   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1500                    "mysql", _("Mysql database running\n"));
1501   return api;
1502 }
1503
1504
1505 /**
1506  * Exit point from the plugin.
1507  * @param cls our "struct Plugin*"
1508  * @return always NULL
1509  */
1510 void *
1511 libgnunet_plugin_datastore_mysql_done (void *cls)
1512 {
1513   struct GNUNET_DATASTORE_PluginFunctions *api = cls;
1514   struct Plugin *plugin = api->cls;
1515
1516   iclose (plugin);
1517   GNUNET_free_non_null (plugin->cnffile);
1518   GNUNET_free (plugin);
1519   GNUNET_free (api);
1520   mysql_library_end ();
1521   return NULL;
1522 }
1523
1524 /* end of plugin_datastore_mysql.c */