Testing and core related changes.
[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 #ifndef WINDOWS
187   struct passwd *pw;
188 #endif
189   int configured;
190
191 #ifndef WINDOWS
192   pw = getpwuid (getuid ());
193   if (!pw)
194     {
195       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 
196                            "getpwuid");
197       return NULL;
198     }
199   if (GNUNET_YES ==
200       GNUNET_CONFIGURATION_have_value (cfg,
201                                        "datacache-mysql", "CONFIG"))
202     {
203       GNUNET_assert (GNUNET_OK == 
204                      GNUNET_CONFIGURATION_get_value_filename (cfg,
205                                                               "datacache-mysql", "CONFIG", &cnffile));
206       configured = GNUNET_YES;
207     }
208   else
209     {
210       home_dir = GNUNET_strdup (pw->pw_dir);
211 #else
212       home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
213       plibc_conv_to_win_path ("~/", home_dir);
214 #endif
215       GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
216       GNUNET_free (home_dir);
217       configured = GNUNET_NO;
218     }
219   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
220               _("Trying to use file `%s' for MySQL configuration.\n"),
221               cnffile);
222   if ((0 != STAT (cnffile, &st)) ||
223       (0 != ACCESS (cnffile, R_OK)) || (!S_ISREG (st.st_mode)))
224     {
225       if (configured == GNUNET_YES)
226         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
227                     _("Could not access file `%s': %s\n"), cnffile,
228                     STRERROR (errno));
229       GNUNET_free (cnffile);
230       return NULL;
231     }
232   return cnffile;
233 }
234
235
236 /**
237  * Free a prepared statement.
238  *
239  * @param plugin plugin context
240  * @param s prepared statement
241  */
242 static void
243 prepared_statement_destroy (struct Plugin *plugin, 
244                             struct GNUNET_MysqlStatementHandle
245                             *s)
246 {
247   GNUNET_CONTAINER_DLL_remove (plugin->shead,
248                                plugin->stail,
249                                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,
266                                 plugin->shead);
267   if (plugin->dbf != NULL)
268     {
269       mysql_close (plugin->dbf);
270       plugin->dbf = NULL;
271     }
272   return GNUNET_OK;
273 }
274
275
276 /**
277  * Open the connection with the database (and initialize
278  * our default options).
279  *
280  * @return GNUNET_OK on success
281  */
282 static int
283 iopen (struct Plugin *ret)
284 {
285   char *mysql_dbname;
286   char *mysql_server;
287   char *mysql_user;
288   char *mysql_password;
289   unsigned long long mysql_port;
290   my_bool reconnect;
291   unsigned int timeout;
292
293   ret->dbf = mysql_init (NULL);
294   if (ret->dbf == NULL)
295     return GNUNET_SYSERR;
296   if (ret->cnffile != NULL)
297     mysql_options (ret->dbf, MYSQL_READ_DEFAULT_FILE, ret->cnffile);
298   mysql_options (ret->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
299   reconnect = 0;
300   mysql_options (ret->dbf, MYSQL_OPT_RECONNECT, &reconnect);
301   mysql_options (ret->dbf,
302                  MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
303   mysql_options(ret->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
304   timeout = 60; /* in seconds */
305   mysql_options (ret->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
306   mysql_options (ret->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
307   mysql_dbname = NULL;
308   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
309                                                      "datacache-mysql", "DATABASE"))
310     GNUNET_assert (GNUNET_OK == 
311                    GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
312                                                           "datacache-mysql", "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", "USER", 
323                                                            &mysql_user));
324     }
325   mysql_password = NULL;
326   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
327                                                      "datacache-mysql", "PASSWORD"))
328     {
329       GNUNET_assert (GNUNET_OK ==
330                     GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
331                                                            "datacache-mysql", "PASSWORD",
332                                                            &mysql_password));
333     }
334   mysql_server = NULL;
335   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
336                                                      "datacache-mysql", "HOST"))
337     {
338       GNUNET_assert (GNUNET_OK == 
339                     GNUNET_CONFIGURATION_get_value_string (ret->env->cfg,
340                                                            "datacache-mysql", "HOST", 
341                                                            &mysql_server));
342     }
343   mysql_port = 0;
344   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (ret->env->cfg,
345                                                      "datacache-mysql", "PORT"))
346     {
347       GNUNET_assert (GNUNET_OK ==
348                     GNUNET_CONFIGURATION_get_value_number (ret->env->cfg, "datacache-mysql",
349                                                            "PORT", &mysql_port));
350     }
351
352   GNUNET_assert (mysql_dbname != NULL);
353   mysql_real_connect (ret->dbf, mysql_server, mysql_user, mysql_password,
354                       mysql_dbname, (unsigned int) mysql_port, NULL,
355                       CLIENT_IGNORE_SIGPIPE);
356   GNUNET_free_non_null (mysql_server);
357   GNUNET_free_non_null (mysql_user);
358   GNUNET_free_non_null (mysql_password);
359   GNUNET_free (mysql_dbname);
360   if (mysql_error (ret->dbf)[0])
361     {
362       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
363                  "mysql_real_connect", ret);
364       return GNUNET_SYSERR;
365     }
366   return GNUNET_OK;
367 }
368
369
370 /**
371  * Run the given MySQL statement.
372  *
373  * @return GNUNET_OK on success, GNUNET_SYSERR on error
374  */
375 static int
376 run_statement (struct Plugin *plugin,
377                const char *statement)
378 {
379   if ((NULL == plugin->dbf) && (GNUNET_OK != iopen (plugin)))
380     return GNUNET_SYSERR;
381   mysql_query (plugin->dbf, statement);
382   if (mysql_error (plugin->dbf)[0])
383     {
384       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
385                  "mysql_query", plugin);
386       iclose (plugin);
387       return GNUNET_SYSERR;
388     }
389   return GNUNET_OK;
390 }
391
392 /**
393  * Create a prepared statement.
394  *
395  * @return NULL on error
396  */
397 static struct GNUNET_MysqlStatementHandle *
398 prepared_statement_create (struct Plugin *plugin, 
399                            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,
406                                plugin->stail,
407                                ret);
408   return ret;
409 }
410
411
412 /**
413  * Prepare a statement for running.
414  *
415  * @return GNUNET_OK on success
416  */
417 static int
418 prepare_statement (struct Plugin *plugin, 
419                    struct GNUNET_MysqlStatementHandle *ret)
420 {
421   if (GNUNET_YES == ret->valid)
422     return GNUNET_OK;
423   if ((NULL == plugin->dbf) && 
424       (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, 
433                           ret->query,
434                           strlen (ret->query)))
435     {
436       LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
437                  "mysql_stmt_prepare", 
438                  plugin);
439       mysql_stmt_close (ret->statement);
440       ret->statement = NULL;
441       iclose (plugin);
442       return GNUNET_SYSERR;
443     }
444   ret->valid = GNUNET_YES;
445   return GNUNET_OK;
446
447 }
448
449
450 /**
451  * Bind the parameters for the given MySQL statement
452  * and run it.
453  *
454  * @param plugin plugin context
455  * @param s statement to bind and run
456  * @param ap arguments for the binding
457  * @return GNUNET_SYSERR on error, GNUNET_OK on success
458  */
459 static int
460 init_params (struct Plugin *plugin,
461              struct GNUNET_MysqlStatementHandle *s,
462              va_list ap)
463 {
464   MYSQL_BIND qbind[MAX_PARAM];
465   unsigned int pc;
466   unsigned int off;
467   enum enum_field_types ft;
468
469   pc = mysql_stmt_param_count (s->statement);
470   if (pc > MAX_PARAM)
471     {
472       /* increase internal constant! */
473       GNUNET_break (0);
474       return GNUNET_SYSERR;
475     }
476   memset (qbind, 0, sizeof (qbind));
477   off = 0;
478   ft = 0;
479   while ( (pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))) )
480     {
481       qbind[off].buffer_type = ft;
482       switch (ft)
483         {
484         case MYSQL_TYPE_FLOAT:
485           qbind[off].buffer = va_arg (ap, float *);
486           break;
487         case MYSQL_TYPE_LONGLONG:
488           qbind[off].buffer = va_arg (ap, unsigned long long *);
489           qbind[off].is_unsigned = va_arg (ap, int);
490           break;
491         case MYSQL_TYPE_LONG:
492           qbind[off].buffer = va_arg (ap, unsigned int *);
493           qbind[off].is_unsigned = va_arg (ap, int);
494           break;
495         case MYSQL_TYPE_VAR_STRING:
496         case MYSQL_TYPE_STRING:
497         case MYSQL_TYPE_BLOB:
498           qbind[off].buffer = va_arg (ap, void *);
499           qbind[off].buffer_length = va_arg (ap, unsigned long);
500           qbind[off].length = va_arg (ap, unsigned long *);
501           break;
502         default:
503           /* unsupported type */
504           GNUNET_break (0);
505           return GNUNET_SYSERR;
506         }
507       pc--;
508       off++;
509     }
510   if (! ( (pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1)) )
511     {
512       GNUNET_break (0);
513       return GNUNET_SYSERR;
514     }
515   if (mysql_stmt_bind_param (s->statement, qbind))
516     {
517       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
518                   _("`%s' failed at %s:%d with error: %s\n"),
519                   "mysql_stmt_bind_param",
520                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
521       iclose (plugin);
522       return GNUNET_SYSERR;
523     }
524   if (mysql_stmt_execute (s->statement))
525     {
526       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
527                   _("`%s' failed at %s:%d with error: %s\n"),
528                   "mysql_stmt_execute",
529                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
530       iclose (plugin);
531       return GNUNET_SYSERR;
532     }
533   return GNUNET_OK;
534 }
535
536 /**
537  * Type of a callback that will be called for each
538  * data set returned from MySQL.
539  *
540  * @param cls user-defined argument
541  * @param num_values number of elements in values
542  * @param values values returned by MySQL
543  * @return GNUNET_OK to continue iterating, GNUNET_SYSERR to abort
544  */
545 typedef int (*GNUNET_MysqlDataProcessor) (void *cls,
546                                           unsigned int num_values,
547                                           MYSQL_BIND * values);
548
549
550 /**
551  * Run a prepared SELECT statement.
552  *
553  * @param plugin plugin context
554  * @param s handle to SELECT statment
555  * @param result_size number of elements in results array
556  * @param results pointer to already initialized MYSQL_BIND
557  *        array (of sufficient size) for passing results
558  * @param processor function to call on each result
559  * @param processor_cls extra argument to processor
560  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
561  *        values (size + buffer-reference for pointers); terminated
562  *        with "-1"
563  * @return GNUNET_SYSERR on error, otherwise
564  *         the number of successfully affected (or queried) rows
565  */
566 static int
567 prepared_statement_run_select (struct Plugin *plugin,
568                                struct GNUNET_MysqlStatementHandle
569                                *s,
570                                unsigned int result_size,
571                                MYSQL_BIND * results,
572                                GNUNET_MysqlDataProcessor
573                                processor, void *processor_cls,
574                                ...)
575 {
576   va_list ap;
577   int ret;
578   unsigned int rsize;
579   int total;
580
581   if (GNUNET_OK != prepare_statement (plugin, s))
582     {
583       GNUNET_break (0);
584       return GNUNET_SYSERR;
585     }
586   va_start (ap, processor_cls);
587   if (GNUNET_OK != init_params (plugin, s, ap))
588     {
589       GNUNET_break (0);
590       va_end (ap);
591       return GNUNET_SYSERR;
592     }
593   va_end (ap);
594   rsize = mysql_stmt_field_count (s->statement);
595   if (rsize > result_size)
596     {
597       GNUNET_break (0);
598       return GNUNET_SYSERR;
599     }
600   if (mysql_stmt_bind_result (s->statement, results))
601     {
602       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
603                   _("`%s' failed at %s:%d with error: %s\n"),
604                   "mysql_stmt_bind_result",
605                   __FILE__, __LINE__, mysql_stmt_error (s->statement));
606       iclose (plugin);
607       return GNUNET_SYSERR;
608     }
609
610   total = 0;
611   while (1)
612     {
613       ret = mysql_stmt_fetch (s->statement);
614       if (ret == MYSQL_NO_DATA)
615         break;
616       if (ret != 0)
617         {
618           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
619                       _("`%s' failed at %s:%d with error: %s\n"),
620                       "mysql_stmt_fetch",
621                       __FILE__, __LINE__, mysql_stmt_error (s->statement));
622           iclose (plugin);
623           return GNUNET_SYSERR;
624         }
625       if (processor != NULL)
626         if (GNUNET_OK != processor (processor_cls, rsize, results))
627           break;
628       total++;
629     }
630   mysql_stmt_reset (s->statement);
631   return total;
632 }
633
634
635
636 /**
637  * Run a prepared statement that does NOT produce results.
638  *
639  * @param plugin plugin context
640  * @param s handle to SELECT statment
641  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
642  *        values (size + buffer-reference for pointers); terminated
643  *        with "-1"
644  * @param insert_id NULL or address where to store the row ID of whatever
645  *        was inserted (only for INSERT statements!)
646  * @return GNUNET_SYSERR on error, otherwise
647  *         the number of successfully affected rows
648  */
649 static int
650 prepared_statement_run (struct Plugin *plugin,
651                         struct GNUNET_MysqlStatementHandle *s,
652                         unsigned long long *insert_id, ...)
653 {
654   va_list ap;
655   int affected;
656
657   if (GNUNET_OK != prepare_statement (plugin, s))
658     return GNUNET_SYSERR;
659   va_start (ap, insert_id);
660   if (GNUNET_OK != init_params (plugin, s, ap))
661     {
662       va_end (ap);
663       return GNUNET_SYSERR;
664     }
665   va_end (ap);
666   affected = mysql_stmt_affected_rows (s->statement);
667   if (NULL != insert_id)
668     *insert_id = (unsigned long long) mysql_stmt_insert_id (s->statement);
669   mysql_stmt_reset (s->statement);
670   return affected;
671 }
672
673
674 /**
675  * Create temporary table and prepare statements.
676  *
677  * @param plugin plugin context
678  * @return GNUNET_OK on success
679  */
680 static int
681 itable (struct Plugin *plugin)
682 {
683 #define MRUNS(a) (GNUNET_OK != run_statement (plugin, a) )
684   if (MRUNS ("CREATE TEMPORARY TABLE gn080dstore ("
685              "  type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
686              "  puttime BIGINT UNSIGNED NOT NULL DEFAULT 0,"
687              "  expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
688              "  hash BINARY(64) NOT NULL DEFAULT '',"
689              "  vhash BINARY(64) NOT NULL DEFAULT '',"
690              "  value BLOB NOT NULL DEFAULT '',"
691              "  INDEX hashidx (hash(64),type,expire),"
692              "  INDEX allidx (hash(64),vhash(64),type),"
693              "  INDEX expireidx (puttime)" ") ENGINE=InnoDB") ||
694       MRUNS ("SET AUTOCOMMIT = 1"))
695     return GNUNET_SYSERR;
696 #undef MRUNS
697 #define PINIT(a,b) (NULL == (a = prepared_statement_create(plugin, b)))
698   if (PINIT (plugin->select_value, SELECT_VALUE_STMT) ||
699       PINIT (plugin->count_value, COUNT_VALUE_STMT) ||
700       PINIT (plugin->select_old_value, SELECT_OLD_VALUE_STMT) ||
701       PINIT (plugin->delete_value, DELETE_VALUE_STMT) ||
702       PINIT (plugin->insert_value, INSERT_VALUE_STMT) ||
703       PINIT (plugin->update_value, UPDATE_VALUE_STMT))
704     return GNUNET_SYSERR;
705 #undef PINIT
706   return GNUNET_OK;
707 }
708
709
710 /**
711  * Store an item in the datastore.
712  *
713  * @param cls closure (our "struct Plugin")
714  * @param key key to store data under
715  * @param size number of bytes in data
716  * @param data data to store
717  * @param type type of the value
718  * @param discard_time when to discard the value in any case
719  * @return 0 on error, number of bytes used otherwise
720  */
721 static size_t 
722 mysql_plugin_put (void *cls,
723                   const GNUNET_HashCode * key,
724                   size_t size,
725                   const char *data,
726                   enum GNUNET_BLOCK_Type type,
727                   struct GNUNET_TIME_Absolute discard_time)
728 {
729   struct Plugin *plugin = cls;
730   struct GNUNET_TIME_Absolute now;
731   unsigned long k_length;
732   unsigned long h_length;
733   unsigned long v_length;
734   unsigned long long v_now;
735   unsigned long long v_discard_time;
736   unsigned int v_type;
737   GNUNET_HashCode vhash;
738   int ret;
739
740   if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE)
741     return GNUNET_SYSERR;
742   GNUNET_CRYPTO_hash (data, size, &vhash);
743   now = GNUNET_TIME_absolute_get ();
744
745   /* first try UPDATE */
746   h_length = sizeof (GNUNET_HashCode);
747   k_length = sizeof (GNUNET_HashCode);
748   v_length = size;
749   v_type = type;
750   v_now = (unsigned long long) now.abs_value;
751   v_discard_time = (unsigned long long) discard_time.abs_value;
752   if (GNUNET_OK ==
753       prepared_statement_run (plugin,
754                               plugin->update_value,
755                               NULL,
756                               MYSQL_TYPE_LONGLONG,
757                               &v_now,
758                               GNUNET_YES,
759                               MYSQL_TYPE_LONGLONG,
760                               &v_discard_time,
761                               GNUNET_YES,
762                               MYSQL_TYPE_BLOB,
763                               key,
764                               sizeof (GNUNET_HashCode),
765                               &k_length,
766                               MYSQL_TYPE_BLOB,
767                               &vhash,
768                               sizeof (GNUNET_HashCode),
769                               &h_length,
770                               MYSQL_TYPE_LONG,
771                               &v_type,
772                               GNUNET_YES, -1))
773     return GNUNET_OK;
774
775   /* now try INSERT */
776   h_length = sizeof (GNUNET_HashCode);
777   k_length = sizeof (GNUNET_HashCode);
778   v_length = size;
779   if (GNUNET_OK !=
780       (ret = prepared_statement_run (plugin,
781                                      plugin->insert_value,
782                                      NULL,
783                                      MYSQL_TYPE_LONG,
784                                      &type,
785                                      GNUNET_YES,
786                                      MYSQL_TYPE_LONGLONG,
787                                      &v_now,
788                                      GNUNET_YES,
789                                      MYSQL_TYPE_LONGLONG,
790                                      &v_discard_time,
791                                      GNUNET_YES,
792                                      MYSQL_TYPE_BLOB,
793                                      key,
794                                      sizeof (GNUNET_HashCode),
795                                      &k_length,
796                                      MYSQL_TYPE_BLOB,
797                                      &vhash,
798                                      sizeof (GNUNET_HashCode),
799                                      &h_length,
800                                      MYSQL_TYPE_BLOB,
801                                      data,
802                                      (unsigned long) size,
803                                      &v_length, -1)))
804     {
805       if (ret == GNUNET_SYSERR)
806         itable (plugin);
807       return GNUNET_SYSERR;
808     }
809   return size + OVERHEAD;
810 }
811
812
813 static int
814 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
815 {
816   return GNUNET_OK;
817 }
818
819
820 /**
821  * Iterate over the results for a particular key
822  * in the datastore.
823  *
824  * @param cls closure (our "struct Plugin")
825  * @param key
826  * @param type entries of which type are relevant?
827  * @param iter maybe NULL (to just count)
828  * @param iter_cls closure for iter
829  * @return the number of results found
830  */
831 static unsigned int 
832 mysql_plugin_get (void *cls,
833                    const GNUNET_HashCode * key,
834                    enum GNUNET_BLOCK_Type type,
835                    GNUNET_DATACACHE_Iterator iter,
836                    void *iter_cls)
837 {
838   struct Plugin *plugin = cls;
839   MYSQL_BIND rbind[3];
840   unsigned long h_length;
841   unsigned long v_length;
842   unsigned long long v_expire;
843   struct GNUNET_TIME_Absolute now;
844   struct GNUNET_TIME_Absolute expire;
845   unsigned int cnt;
846   unsigned long long total;
847   unsigned long long v_now;
848   unsigned int off;
849   unsigned int v_type;
850   int ret;
851   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
852
853   now = GNUNET_TIME_absolute_get ();
854   h_length = sizeof (GNUNET_HashCode);
855   v_length = sizeof (buffer);
856   total = -1;
857   memset (rbind, 0, sizeof (rbind));
858   rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
859   rbind[0].buffer = &total;
860   rbind[0].is_unsigned = GNUNET_YES;
861   v_type = type;
862   v_now = (unsigned long long) now.abs_value;
863   if ((GNUNET_OK !=
864        (ret = prepared_statement_run_select (plugin,
865                                              plugin->count_value,
866                                              1,
867                                              rbind,
868                                              return_ok,
869                                              NULL,
870                                              MYSQL_TYPE_BLOB,
871                                              key,
872                                              sizeof
873                                              (GNUNET_HashCode),
874                                              &h_length,
875                                              MYSQL_TYPE_LONG,
876                                              &v_type, GNUNET_YES,
877                                              MYSQL_TYPE_LONGLONG,
878                                              &v_now, GNUNET_YES,
879                                              -1)))
880       || (-1 == total))
881     {
882       if (ret == GNUNET_SYSERR)
883         itable (plugin);
884       return GNUNET_SYSERR;
885     }
886   if ((iter == NULL) || (total == 0))
887     return (int) total;
888
889   off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
890   cnt = 0;
891   while (cnt < total)
892     {
893       memset (rbind, 0, sizeof (rbind));
894       rbind[0].buffer_type = MYSQL_TYPE_BLOB;
895       rbind[0].buffer_length = sizeof (buffer);
896       rbind[0].length = &v_length;
897       rbind[0].buffer = buffer;
898       rbind[1].buffer_type = MYSQL_TYPE_LONGLONG;
899       rbind[1].is_unsigned = 1;
900       rbind[1].buffer = &v_expire;
901       off = (off + 1) % total;
902       if (GNUNET_OK !=
903           (ret = prepared_statement_run_select (plugin,
904                                                 plugin->select_value,
905                                                 2,
906                                                 rbind,
907                                                 return_ok,
908                                                 NULL,
909                                                 MYSQL_TYPE_BLOB,
910                                                 key,
911                                                 sizeof
912                                                 (GNUNET_HashCode),
913                                                 &h_length,
914                                                 MYSQL_TYPE_LONG,
915                                                 &v_type,
916                                                 GNUNET_YES,
917                                                 MYSQL_TYPE_LONGLONG,
918                                                 &v_now,
919                                                 GNUNET_YES,
920                                                 MYSQL_TYPE_LONG,
921                                                 &off,
922                                                 GNUNET_YES,
923                                                 -1)))
924         {
925           if (ret == GNUNET_SYSERR)
926             itable (plugin);
927           return GNUNET_SYSERR;
928         }
929       cnt++;
930       expire.abs_value = v_expire;
931       if (GNUNET_OK != iter (iter_cls, 
932                              expire,
933                              key, 
934                              v_length, buffer,
935                              type))
936         break;
937     }
938   return cnt;
939 }
940
941
942 /**
943  * Delete the entry with the lowest expiration value
944  * from the datacache right now.
945  * 
946  * @param cls closure (our "struct Plugin")
947  * @return GNUNET_OK on success, GNUNET_SYSERR on error
948  */ 
949 static int 
950 mysql_plugin_del (void *cls)
951 {
952   struct Plugin *plugin = cls;
953
954   MYSQL_BIND rbind[5];
955   unsigned int v_type;
956   GNUNET_HashCode v_key;
957   GNUNET_HashCode vhash;
958   unsigned long k_length;
959   unsigned long h_length;
960   unsigned long v_length;
961   int ret;
962   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
963
964   k_length = sizeof (GNUNET_HashCode);
965   h_length = sizeof (GNUNET_HashCode);
966   v_length = sizeof (buffer);
967   memset (rbind, 0, sizeof (rbind));
968   rbind[0].buffer_type = MYSQL_TYPE_BLOB;
969   rbind[0].buffer_length = sizeof (GNUNET_HashCode);
970   rbind[0].length = &k_length;
971   rbind[0].buffer = &v_key;
972   rbind[1].buffer_type = MYSQL_TYPE_BLOB;
973   rbind[1].buffer_length = sizeof (GNUNET_HashCode);
974   rbind[1].length = &h_length;
975   rbind[1].buffer = &vhash;
976   rbind[2].buffer_type = MYSQL_TYPE_LONG;
977   rbind[2].is_unsigned = 1;
978   rbind[2].buffer = &v_type;
979   rbind[3].buffer_type = MYSQL_TYPE_BLOB;
980   rbind[3].buffer_length = sizeof (buffer);
981   rbind[3].length = &v_length;
982   rbind[3].buffer = buffer;
983   if ((GNUNET_OK !=
984        (ret = prepared_statement_run_select (plugin,
985                                              plugin->select_old_value,
986                                              4,
987                                              rbind,
988                                              return_ok,
989                                              NULL,
990                                              -1))) ||
991       (GNUNET_OK !=
992        (ret = prepared_statement_run (plugin,
993                                       plugin->delete_value,
994                                       NULL,
995                                       MYSQL_TYPE_BLOB,
996                                       &v_key,
997                                       sizeof (GNUNET_HashCode),
998                                       &k_length,
999                                       MYSQL_TYPE_BLOB,
1000                                       &vhash,
1001                                       sizeof (GNUNET_HashCode),
1002                                       &h_length,
1003                                       MYSQL_TYPE_LONG,
1004                                       &v_type,
1005                                       GNUNET_YES,
1006                                       MYSQL_TYPE_BLOB,
1007                                       buffer,
1008                                       (unsigned long)
1009                                       sizeof (buffer),
1010                                       &v_length, -1))))
1011     {
1012       if (ret == GNUNET_SYSERR)
1013         itable (plugin);
1014       return GNUNET_SYSERR;
1015     }
1016   plugin->env->delete_notify (plugin->env->cls,
1017                               &v_key,
1018                               v_length + OVERHEAD);
1019
1020   return GNUNET_OK;
1021 }
1022
1023
1024 /**
1025  * Entry point for the plugin.
1026  *
1027  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
1028  * @return the plugin's closure (our "struct Plugin")
1029  */
1030 void *
1031 libgnunet_plugin_datacache_mysql_init (void *cls)
1032 {
1033   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
1034   struct GNUNET_DATACACHE_PluginFunctions *api;
1035   struct Plugin *plugin;
1036
1037   plugin = GNUNET_malloc (sizeof (struct Plugin));
1038   plugin->env = env;
1039   plugin->cnffile = get_my_cnf_path (env->cfg);
1040   if (GNUNET_OK !=
1041       iopen (plugin))
1042     {
1043       GNUNET_free_non_null (plugin->cnffile);
1044       GNUNET_free (plugin);
1045       return NULL;
1046     }
1047   if (GNUNET_OK !=
1048       itable (plugin))
1049     {
1050       iclose (plugin);
1051       GNUNET_free_non_null (plugin->cnffile);
1052       GNUNET_free (plugin);
1053       return NULL;
1054     }
1055   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
1056   api->cls = plugin;
1057   api->get = &mysql_plugin_get;
1058   api->put = &mysql_plugin_put;
1059   api->del = &mysql_plugin_del;
1060   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
1061                    "mysql", _("MySQL datacache running\n"));
1062   return api;
1063 }
1064
1065
1066 /**
1067  * Exit point from the plugin.
1068  *
1069  * @param cls closure (our "struct Plugin")
1070  * @return NULL
1071  */
1072 void *
1073 libgnunet_plugin_datacache_mysql_done (void *cls)
1074 {
1075   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
1076   struct Plugin *plugin = api->cls;
1077
1078   iclose (plugin);
1079   GNUNET_free_non_null (plugin->cnffile);
1080   GNUNET_free (plugin);
1081   GNUNET_free (api);
1082   mysql_library_end ();
1083   return NULL;
1084 }
1085
1086
1087 /* end of plugin_datacache_mysql.c */