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