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