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