-fix
[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_EXTRA_LOGGING
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)); GNUNET_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)) || (0 != ACCESS (cnffile, R_OK)) ||
226       (!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 ==
307       GNUNET_CONFIGURATION_have_value (ret->env->cfg, "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 ==
318       GNUNET_CONFIGURATION_have_value (ret->env->cfg, "datacache-mysql",
319                                        "USER"))
320   {
321     GNUNET_assert (GNUNET_OK ==
322                    GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
323                                                           "datacache-mysql",
324                                                           "USER", &mysql_user));
325   }
326   mysql_password = NULL;
327   if (GNUNET_YES ==
328       GNUNET_CONFIGURATION_have_value (ret->env->cfg, "datacache-mysql",
329                                        "PASSWORD"))
330   {
331     GNUNET_assert (GNUNET_OK ==
332                    GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
333                                                           "datacache-mysql",
334                                                           "PASSWORD",
335                                                           &mysql_password));
336   }
337   mysql_server = NULL;
338   if (GNUNET_YES ==
339       GNUNET_CONFIGURATION_have_value (ret->env->cfg, "datacache-mysql",
340                                        "HOST"))
341   {
342     GNUNET_assert (GNUNET_OK ==
343                    GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
344                                                           "datacache-mysql",
345                                                           "HOST",
346                                                           &mysql_server));
347   }
348   mysql_port = 0;
349   if (GNUNET_YES ==
350       GNUNET_CONFIGURATION_have_value (ret->env->cfg, "datacache-mysql",
351                                        "PORT"))
352   {
353     GNUNET_assert (GNUNET_OK ==
354                    GNUNET_CONFIGURATION_get_value_number (ret->env->cfg,
355                                                           "datacache-mysql",
356                                                           "PORT", &mysql_port));
357   }
358
359   GNUNET_assert (mysql_dbname != NULL);
360   mysql_real_connect (ret->dbf, mysql_server, mysql_user, mysql_password,
361                       mysql_dbname, (unsigned int) mysql_port, NULL,
362                       CLIENT_IGNORE_SIGPIPE);
363   GNUNET_free_non_null (mysql_server);
364   GNUNET_free_non_null (mysql_user);
365   GNUNET_free_non_null (mysql_password);
366   GNUNET_free (mysql_dbname);
367   if (mysql_error (ret->dbf)[0])
368   {
369     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", ret);
370     return GNUNET_SYSERR;
371   }
372   return GNUNET_OK;
373 }
374
375
376 /**
377  * Run the given MySQL statement.
378  *
379  * @return GNUNET_OK on success, GNUNET_SYSERR on error
380  */
381 static int
382 run_statement (struct Plugin *plugin, const char *statement)
383 {
384   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
385     return GNUNET_SYSERR;
386   mysql_query (plugin->dbf, statement);
387   if (mysql_error (plugin->dbf)[0])
388   {
389     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_query", plugin);
390     iclose (plugin);
391     return GNUNET_SYSERR;
392   }
393   return GNUNET_OK;
394 }
395
396 /**
397  * Create a prepared statement.
398  *
399  * @return NULL on error
400  */
401 static struct GNUNET_MysqlStatementHandle *
402 prepared_statement_create (struct Plugin *plugin, const char *statement)
403 {
404   struct GNUNET_MysqlStatementHandle *ret;
405
406   ret = GNUNET_malloc (sizeof (struct GNUNET_MysqlStatementHandle));
407   ret->query = GNUNET_strdup (statement);
408   GNUNET_CONTAINER_DLL_insert (plugin->shead, plugin->stail, ret);
409   return ret;
410 }
411
412
413 /**
414  * Prepare a statement for running.
415  *
416  * @return GNUNET_OK on success
417  */
418 static int
419 prepare_statement (struct Plugin *plugin,
420                    struct GNUNET_MysqlStatementHandle *ret)
421 {
422   if (GNUNET_YES == ret->valid)
423     return GNUNET_OK;
424   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
425     return GNUNET_SYSERR;
426   ret->statement = mysql_stmt_init (plugin->dbf);
427   if (ret->statement == NULL)
428   {
429     iclose (plugin);
430     return GNUNET_SYSERR;
431   }
432   if (mysql_stmt_prepare (ret->statement, ret->query, strlen (ret->query)))
433   {
434     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", plugin);
435     mysql_stmt_close (ret->statement);
436     ret->statement = NULL;
437     iclose (plugin);
438     return GNUNET_SYSERR;
439   }
440   ret->valid = GNUNET_YES;
441   return GNUNET_OK;
442
443 }
444
445
446 /**
447  * Bind the parameters for the given MySQL statement
448  * and run it.
449  *
450  * @param plugin plugin context
451  * @param s statement to bind and run
452  * @param ap arguments for the binding
453  * @return GNUNET_SYSERR on error, GNUNET_OK on success
454  */
455 static int
456 init_params (struct Plugin *plugin, struct GNUNET_MysqlStatementHandle *s,
457              va_list ap)
458 {
459   MYSQL_BIND qbind[MAX_PARAM];
460   unsigned int pc;
461   unsigned int off;
462   enum enum_field_types ft;
463
464   pc = mysql_stmt_param_count (s->statement);
465   if (pc > MAX_PARAM)
466   {
467     /* increase internal constant! */
468     GNUNET_break (0);
469     return GNUNET_SYSERR;
470   }
471   memset (qbind, 0, sizeof (qbind));
472   off = 0;
473   ft = 0;
474   while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
475   {
476     qbind[off].buffer_type = ft;
477     switch (ft)
478     {
479     case MYSQL_TYPE_FLOAT:
480       qbind[off].buffer = va_arg (ap, float *);
481
482       break;
483     case MYSQL_TYPE_LONGLONG:
484       qbind[off].buffer = va_arg (ap, unsigned long long *);
485       qbind[off].is_unsigned = va_arg (ap, int);
486
487       break;
488     case MYSQL_TYPE_LONG:
489       qbind[off].buffer = va_arg (ap, unsigned int *);
490       qbind[off].is_unsigned = va_arg (ap, int);
491
492       break;
493     case MYSQL_TYPE_VAR_STRING:
494     case MYSQL_TYPE_STRING:
495     case MYSQL_TYPE_BLOB:
496       qbind[off].buffer = va_arg (ap, void *);
497       qbind[off].buffer_length = va_arg (ap, unsigned long);
498       qbind[off].length = va_arg (ap, unsigned long *);
499
500       break;
501     default:
502       /* unsupported type */
503       GNUNET_break (0);
504       return GNUNET_SYSERR;
505     }
506     pc--;
507     off++;
508   }
509   if (!((pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1)))
510   {
511     GNUNET_break (0);
512     return GNUNET_SYSERR;
513   }
514   if (mysql_stmt_bind_param (s->statement, qbind))
515   {
516     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
517                 _("`%s' failed at %s:%d with error: %s\n"),
518                 "mysql_stmt_bind_param", __FILE__, __LINE__,
519                 mysql_stmt_error (s->statement));
520     iclose (plugin);
521     return GNUNET_SYSERR;
522   }
523   if (mysql_stmt_execute (s->statement))
524   {
525     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
526                 _("`%s' failed at %s:%d with error: %s\n"),
527                 "mysql_stmt_execute", __FILE__, __LINE__,
528                 mysql_stmt_error (s->statement));
529     iclose (plugin);
530     return GNUNET_SYSERR;
531   }
532   return GNUNET_OK;
533 }
534
535 /**
536  * Type of a callback that will be called for each
537  * data set returned from MySQL.
538  *
539  * @param cls user-defined argument
540  * @param num_values number of elements in values
541  * @param values values returned by MySQL
542  * @return GNUNET_OK to continue iterating, GNUNET_SYSERR to abort
543  */
544 typedef int (*GNUNET_MysqlDataProcessor) (void *cls, unsigned int num_values,
545                                           MYSQL_BIND * values);
546
547
548 /**
549  * Run a prepared SELECT statement.
550  *
551  * @param plugin plugin context
552  * @param s handle to SELECT statment
553  * @param result_size number of elements in results array
554  * @param results pointer to already initialized MYSQL_BIND
555  *        array (of sufficient size) for passing results
556  * @param processor function to call on each result
557  * @param processor_cls extra argument to processor
558  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
559  *        values (size + buffer-reference for pointers); terminated
560  *        with "-1"
561  * @return GNUNET_SYSERR on error, otherwise
562  *         the number of successfully affected (or queried) rows
563  */
564 static int
565 prepared_statement_run_select (struct Plugin *plugin,
566                                struct GNUNET_MysqlStatementHandle *s,
567                                unsigned int result_size, MYSQL_BIND * results,
568                                GNUNET_MysqlDataProcessor processor,
569                                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", __FILE__, __LINE__,
600                 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", __FILE__, __LINE__,
616                   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
680       ("CREATE TEMPORARY TABLE gn080dstore ("
681        "  type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
682        "  puttime BIGINT UNSIGNED NOT NULL DEFAULT 0,"
683        "  expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
684        "  hash BINARY(64) NOT NULL DEFAULT '',"
685        "  vhash BINARY(64) NOT NULL DEFAULT '',"
686        "  value BLOB NOT NULL DEFAULT '',"
687        "  INDEX hashidx (hash(64),type,expire),"
688        "  INDEX allidx (hash(64),vhash(64),type)," "  INDEX expireidx (puttime)"
689        ") ENGINE=InnoDB") || 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, const GNUNET_HashCode * key, size_t size,
718                   const char *data, enum GNUNET_BLOCK_Type type,
719                   struct GNUNET_TIME_Absolute discard_time)
720 {
721   struct Plugin *plugin = cls;
722   struct GNUNET_TIME_Absolute now;
723   unsigned long k_length;
724   unsigned long h_length;
725   unsigned long v_length;
726   unsigned long long v_now;
727   unsigned long long v_discard_time;
728   unsigned int v_type;
729   GNUNET_HashCode vhash;
730   int ret;
731
732   if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE)
733     return GNUNET_SYSERR;
734   GNUNET_CRYPTO_hash (data, size, &vhash);
735   now = GNUNET_TIME_absolute_get ();
736
737   /* first try UPDATE */
738   h_length = sizeof (GNUNET_HashCode);
739   k_length = sizeof (GNUNET_HashCode);
740   v_length = size;
741   v_type = type;
742   v_now = (unsigned long long) now.abs_value;
743   v_discard_time = (unsigned long long) discard_time.abs_value;
744   if (GNUNET_OK ==
745       prepared_statement_run (plugin, plugin->update_value, NULL,
746                               MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
747                               MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
748                               MYSQL_TYPE_BLOB, key, sizeof (GNUNET_HashCode),
749                               &k_length, MYSQL_TYPE_BLOB, &vhash,
750                               sizeof (GNUNET_HashCode), &h_length,
751                               MYSQL_TYPE_LONG, &v_type, GNUNET_YES, -1))
752     return GNUNET_OK;
753
754   /* now try INSERT */
755   h_length = sizeof (GNUNET_HashCode);
756   k_length = sizeof (GNUNET_HashCode);
757   v_length = size;
758   if (GNUNET_OK !=
759       (ret =
760        prepared_statement_run (plugin, plugin->insert_value, NULL,
761                                MYSQL_TYPE_LONG, &type, GNUNET_YES,
762                                MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
763                                MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
764                                MYSQL_TYPE_BLOB, key, sizeof (GNUNET_HashCode),
765                                &k_length, MYSQL_TYPE_BLOB, &vhash,
766                                sizeof (GNUNET_HashCode), &h_length,
767                                MYSQL_TYPE_BLOB, data, (unsigned long) size,
768                                &v_length, -1)))
769   {
770     if (ret == GNUNET_SYSERR)
771       itable (plugin);
772     return GNUNET_SYSERR;
773   }
774   return size + OVERHEAD;
775 }
776
777
778 static int
779 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
780 {
781   return GNUNET_OK;
782 }
783
784
785 /**
786  * Iterate over the results for a particular key
787  * in the datastore.
788  *
789  * @param cls closure (our "struct Plugin")
790  * @param key
791  * @param type entries of which type are relevant?
792  * @param iter maybe NULL (to just count)
793  * @param iter_cls closure for iter
794  * @return the number of results found
795  */
796 static unsigned int
797 mysql_plugin_get (void *cls, const GNUNET_HashCode * key,
798                   enum GNUNET_BLOCK_Type type, GNUNET_DATACACHE_Iterator iter,
799                   void *iter_cls)
800 {
801   struct Plugin *plugin = cls;
802   MYSQL_BIND rbind[3];
803   unsigned long h_length;
804   unsigned long v_length;
805   unsigned long long v_expire;
806   struct GNUNET_TIME_Absolute now;
807   struct GNUNET_TIME_Absolute expire;
808   unsigned int cnt;
809   unsigned long long total;
810   unsigned long long v_now;
811   unsigned int off;
812   unsigned int v_type;
813   int ret;
814   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
815
816   now = GNUNET_TIME_absolute_get ();
817   h_length = sizeof (GNUNET_HashCode);
818   v_length = sizeof (buffer);
819   total = -1;
820   memset (rbind, 0, sizeof (rbind));
821   rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
822   rbind[0].buffer = &total;
823   rbind[0].is_unsigned = GNUNET_YES;
824   v_type = type;
825   v_now = (unsigned long long) now.abs_value;
826   if ((GNUNET_OK !=
827        (ret =
828         prepared_statement_run_select (plugin, plugin->count_value, 1, rbind,
829                                        return_ok, NULL, MYSQL_TYPE_BLOB, key,
830                                        sizeof (GNUNET_HashCode), &h_length,
831                                        MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
832                                        MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
833                                        -1))) || (-1 == total))
834   {
835     if (ret == GNUNET_SYSERR)
836       itable (plugin);
837     return GNUNET_SYSERR;
838   }
839   if ((iter == NULL) || (total == 0))
840     return (int) total;
841
842   off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
843   cnt = 0;
844   while (cnt < total)
845   {
846     memset (rbind, 0, sizeof (rbind));
847     rbind[0].buffer_type = MYSQL_TYPE_BLOB;
848     rbind[0].buffer_length = sizeof (buffer);
849     rbind[0].length = &v_length;
850     rbind[0].buffer = buffer;
851     rbind[1].buffer_type = MYSQL_TYPE_LONGLONG;
852     rbind[1].is_unsigned = 1;
853     rbind[1].buffer = &v_expire;
854     off = (off + 1) % total;
855     if (GNUNET_OK !=
856         (ret =
857          prepared_statement_run_select (plugin, plugin->select_value, 2, rbind,
858                                         return_ok, NULL, MYSQL_TYPE_BLOB, key,
859                                         sizeof (GNUNET_HashCode), &h_length,
860                                         MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
861                                         MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
862                                         MYSQL_TYPE_LONG, &off, GNUNET_YES, -1)))
863     {
864       if (ret == GNUNET_SYSERR)
865         itable (plugin);
866       return GNUNET_SYSERR;
867     }
868     cnt++;
869     expire.abs_value = v_expire;
870     if (GNUNET_OK != iter (iter_cls, expire, key, v_length, buffer, type))
871       break;
872   }
873   return cnt;
874 }
875
876
877 /**
878  * Delete the entry with the lowest expiration value
879  * from the datacache right now.
880  *
881  * @param cls closure (our "struct Plugin")
882  * @return GNUNET_OK on success, GNUNET_SYSERR on error
883  */
884 static int
885 mysql_plugin_del (void *cls)
886 {
887   struct Plugin *plugin = cls;
888
889   MYSQL_BIND rbind[5];
890   unsigned int v_type;
891   GNUNET_HashCode v_key;
892   GNUNET_HashCode vhash;
893   unsigned long k_length;
894   unsigned long h_length;
895   unsigned long v_length;
896   int ret;
897   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
898
899   k_length = sizeof (GNUNET_HashCode);
900   h_length = sizeof (GNUNET_HashCode);
901   v_length = sizeof (buffer);
902   memset (rbind, 0, sizeof (rbind));
903   rbind[0].buffer_type = MYSQL_TYPE_BLOB;
904   rbind[0].buffer_length = sizeof (GNUNET_HashCode);
905   rbind[0].length = &k_length;
906   rbind[0].buffer = &v_key;
907   rbind[1].buffer_type = MYSQL_TYPE_BLOB;
908   rbind[1].buffer_length = sizeof (GNUNET_HashCode);
909   rbind[1].length = &h_length;
910   rbind[1].buffer = &vhash;
911   rbind[2].buffer_type = MYSQL_TYPE_LONG;
912   rbind[2].is_unsigned = 1;
913   rbind[2].buffer = &v_type;
914   rbind[3].buffer_type = MYSQL_TYPE_BLOB;
915   rbind[3].buffer_length = sizeof (buffer);
916   rbind[3].length = &v_length;
917   rbind[3].buffer = buffer;
918   if ((GNUNET_OK !=
919        (ret =
920         prepared_statement_run_select (plugin, plugin->select_old_value, 4,
921                                        rbind, return_ok, NULL, -1))) ||
922       (GNUNET_OK !=
923        (ret =
924         prepared_statement_run (plugin, plugin->delete_value, NULL,
925                                 MYSQL_TYPE_BLOB, &v_key,
926                                 sizeof (GNUNET_HashCode), &k_length,
927                                 MYSQL_TYPE_BLOB, &vhash,
928                                 sizeof (GNUNET_HashCode), &h_length,
929                                 MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
930                                 MYSQL_TYPE_BLOB, buffer,
931                                 (unsigned long) sizeof (buffer), &v_length,
932                                 -1))))
933   {
934     if (ret == GNUNET_SYSERR)
935       itable (plugin);
936     return GNUNET_SYSERR;
937   }
938   plugin->env->delete_notify (plugin->env->cls, &v_key, v_length + OVERHEAD);
939
940   return GNUNET_OK;
941 }
942
943
944 /**
945  * Entry point for the plugin.
946  *
947  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
948  * @return the plugin's closure (our "struct Plugin")
949  */
950 void *
951 libgnunet_plugin_datacache_mysql_init (void *cls)
952 {
953   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
954   struct GNUNET_DATACACHE_PluginFunctions *api;
955   struct Plugin *plugin;
956
957   plugin = GNUNET_malloc (sizeof (struct Plugin));
958   plugin->env = env;
959   plugin->cnffile = get_my_cnf_path (env->cfg);
960   if (GNUNET_OK != iopen (plugin))
961   {
962     GNUNET_free_non_null (plugin->cnffile);
963     GNUNET_free (plugin);
964     return NULL;
965   }
966   if (GNUNET_OK != itable (plugin))
967   {
968     iclose (plugin);
969     GNUNET_free_non_null (plugin->cnffile);
970     GNUNET_free (plugin);
971     return NULL;
972   }
973   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
974   api->cls = plugin;
975   api->get = &mysql_plugin_get;
976   api->put = &mysql_plugin_put;
977   api->del = &mysql_plugin_del;
978   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
979                    _("MySQL datacache running\n"));
980   return api;
981 }
982
983
984 /**
985  * Exit point from the plugin.
986  *
987  * @param cls closure (our "struct Plugin")
988  * @return NULL
989  */
990 void *
991 libgnunet_plugin_datacache_mysql_done (void *cls)
992 {
993   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
994   struct Plugin *plugin = api->cls;
995
996   iclose (plugin);
997   GNUNET_free_non_null (plugin->cnffile);
998   GNUNET_free (plugin);
999   GNUNET_free (api);
1000   mysql_library_end ();
1001   return NULL;
1002 }
1003
1004
1005 /* end of plugin_datacache_mysql.c */