- notify clients when a tunnel times out
[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 "gnunet_mysql_lib.h"
84 #include <mysql/mysql.h>
85
86
87 /**
88  * Estimate of the per-entry overhead (including indices).
89  */
90 #define OVERHEAD ((4*2+4*2+8*2+8*2+sizeof(struct GNUNET_HashCode)*5+8))
91
92 /**
93  * Die with an error message that indicates
94  * a failure of the command 'cmd' with the message given
95  * by strerror(errno).
96  */
97 #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);
98
99 /**
100  * Log an error message at log-level 'level' that indicates
101  * a failure of the command 'cmd' on file 'filename'
102  * with the message given by strerror(errno).
103  */
104 #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);
105
106
107 /**
108  * Context for all functions in this plugin.
109  */
110 struct Plugin
111 {
112   /**
113    * Our execution environment.
114    */
115   struct GNUNET_DATACACHE_PluginEnvironment *env;
116
117   /**
118    * Handle to the mysql database.
119    */
120   struct GNUNET_MYSQL_Context *mc;
121
122 #define SELECT_VALUE_STMT "SELECT value,expire FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ? LIMIT 1 OFFSET ?"
123   struct GNUNET_MYSQL_StatementHandle *select_value;
124
125 #define COUNT_VALUE_STMT "SELECT count(*) FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ?"
126   struct GNUNET_MYSQL_StatementHandle *count_value;
127
128 #define SELECT_OLD_VALUE_STMT "SELECT hash, vhash, type, value FROM gn080dstore FORCE INDEX (expireidx) ORDER BY puttime ASC LIMIT 1"
129   struct GNUNET_MYSQL_StatementHandle *select_old_value;
130
131 #define DELETE_VALUE_STMT "DELETE FROM gn080dstore WHERE hash = ? AND vhash = ? AND type = ? AND value = ?"
132   struct GNUNET_MYSQL_StatementHandle *delete_value;
133
134 #define INSERT_VALUE_STMT "INSERT INTO gn080dstore (type, puttime, expire, hash, vhash, value) "\
135                           "VALUES (?, ?, ?, ?, ?, ?)"
136   struct GNUNET_MYSQL_StatementHandle *insert_value;
137
138 #define UPDATE_VALUE_STMT "UPDATE gn080dstore FORCE INDEX (allidx) SET puttime=?, expire=? "\
139                           "WHERE hash=? AND vhash=? AND type=?"
140   struct GNUNET_MYSQL_StatementHandle *update_value;
141
142 };
143
144
145 /**
146  * Create temporary table and prepare statements.
147  *
148  * @param plugin plugin context
149  * @return GNUNET_OK on success
150  */
151 static int
152 itable (struct Plugin *plugin)
153 {
154 #define MRUNS(a) (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, a) )
155   if (MRUNS
156       ("CREATE TEMPORARY TABLE gn080dstore ("
157        "  type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
158        "  puttime BIGINT UNSIGNED NOT NULL DEFAULT 0,"
159        "  expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
160        "  hash BINARY(64) NOT NULL DEFAULT '',"
161        "  vhash BINARY(64) NOT NULL DEFAULT '',"
162        "  value BLOB NOT NULL DEFAULT '',"
163        "  INDEX hashidx (hash(64),type,expire),"
164        "  INDEX allidx (hash(64),vhash(64),type)," "  INDEX expireidx (puttime)"
165        ") ENGINE=InnoDB") || MRUNS ("SET AUTOCOMMIT = 1"))
166     return GNUNET_SYSERR;
167 #undef MRUNS
168 #define PINIT(a,b) (NULL == (a = GNUNET_MYSQL_statement_prepare (plugin->mc, b)))
169   if (PINIT (plugin->select_value, SELECT_VALUE_STMT) ||
170       PINIT (plugin->count_value, COUNT_VALUE_STMT) ||
171       PINIT (plugin->select_old_value, SELECT_OLD_VALUE_STMT) ||
172       PINIT (plugin->delete_value, DELETE_VALUE_STMT) ||
173       PINIT (plugin->insert_value, INSERT_VALUE_STMT) ||
174       PINIT (plugin->update_value, UPDATE_VALUE_STMT))
175     return GNUNET_SYSERR;
176 #undef PINIT
177   return GNUNET_OK;
178 }
179
180
181 /**
182  * Store an item in the datastore.
183  *
184  * @param cls closure (our "struct Plugin")
185  * @param key key to store data under
186  * @param size number of bytes in data
187  * @param data data to store
188  * @param type type of the value
189  * @param discard_time when to discard the value in any case
190  * @return 0 on error, number of bytes used otherwise
191  */
192 static size_t
193 mysql_plugin_put (void *cls, const struct GNUNET_HashCode * key, size_t size,
194                   const char *data, enum GNUNET_BLOCK_Type type,
195                   struct GNUNET_TIME_Absolute discard_time)
196 {
197   struct Plugin *plugin = cls;
198   struct GNUNET_TIME_Absolute now;
199   unsigned long k_length;
200   unsigned long h_length;
201   unsigned long v_length;
202   unsigned long long v_now;
203   unsigned long long v_discard_time;
204   unsigned int v_type;
205   struct GNUNET_HashCode vhash;
206   int ret;
207
208   if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE)
209     return GNUNET_SYSERR;
210   GNUNET_CRYPTO_hash (data, size, &vhash);
211   now = GNUNET_TIME_absolute_get ();
212
213   /* first try UPDATE */
214   h_length = sizeof (struct GNUNET_HashCode);
215   k_length = sizeof (struct GNUNET_HashCode);
216   v_length = size;
217   v_type = type;
218   v_now = (unsigned long long) now.abs_value;
219   v_discard_time = (unsigned long long) discard_time.abs_value;
220   if (GNUNET_OK ==
221       GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->update_value, NULL,
222                               MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
223                               MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
224                               MYSQL_TYPE_BLOB, key, sizeof (struct GNUNET_HashCode),
225                               &k_length, MYSQL_TYPE_BLOB, &vhash,
226                               sizeof (struct GNUNET_HashCode), &h_length,
227                               MYSQL_TYPE_LONG, &v_type, GNUNET_YES, -1))
228     return GNUNET_OK;
229
230   /* now try INSERT */
231   h_length = sizeof (struct GNUNET_HashCode);
232   k_length = sizeof (struct GNUNET_HashCode);
233   v_length = size;
234   if (GNUNET_OK !=
235       (ret =
236        GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->insert_value, NULL,
237                                MYSQL_TYPE_LONG, &type, GNUNET_YES,
238                                MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
239                                MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
240                                MYSQL_TYPE_BLOB, key, sizeof (struct GNUNET_HashCode),
241                                &k_length, MYSQL_TYPE_BLOB, &vhash,
242                                sizeof (struct GNUNET_HashCode), &h_length,
243                                MYSQL_TYPE_BLOB, data, (unsigned long) size,
244                                &v_length, -1)))
245   {
246     if (ret == GNUNET_SYSERR)
247       itable (plugin);
248     return GNUNET_SYSERR;
249   }
250   return size + OVERHEAD;
251 }
252
253
254 static int
255 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
256 {
257   return GNUNET_OK;
258 }
259
260
261 /**
262  * Iterate over the results for a particular key
263  * in the datastore.
264  *
265  * @param cls closure (our "struct Plugin")
266  * @param key
267  * @param type entries of which type are relevant?
268  * @param iter maybe NULL (to just count)
269  * @param iter_cls closure for iter
270  * @return the number of results found
271  */
272 static unsigned int
273 mysql_plugin_get (void *cls, const struct GNUNET_HashCode * key,
274                   enum GNUNET_BLOCK_Type type, GNUNET_DATACACHE_Iterator iter,
275                   void *iter_cls)
276 {
277   struct Plugin *plugin = cls;
278   MYSQL_BIND rbind[3];
279   unsigned long h_length;
280   unsigned long v_length;
281   unsigned long long v_expire;
282   struct GNUNET_TIME_Absolute now;
283   struct GNUNET_TIME_Absolute expire;
284   unsigned int cnt;
285   unsigned long long total;
286   unsigned long long v_now;
287   unsigned int off;
288   unsigned int v_type;
289   int ret;
290   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
291
292   now = GNUNET_TIME_absolute_get ();
293   h_length = sizeof (struct GNUNET_HashCode);
294   v_length = sizeof (buffer);
295   total = -1;
296   memset (rbind, 0, sizeof (rbind));
297   rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
298   rbind[0].buffer = &total;
299   rbind[0].is_unsigned = GNUNET_YES;
300   v_type = type;
301   v_now = (unsigned long long) now.abs_value;
302   if ((GNUNET_OK !=
303        (ret =
304         GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->count_value, 1, rbind,
305                                        return_ok, NULL, MYSQL_TYPE_BLOB, key,
306                                        sizeof (struct GNUNET_HashCode), &h_length,
307                                        MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
308                                        MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
309                                        -1))) || (-1 == total))
310   {
311     if (ret == GNUNET_SYSERR)
312       itable (plugin);
313     return GNUNET_SYSERR;
314   }
315   if ((iter == NULL) || (total == 0))
316     return (int) total;
317
318   off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
319   cnt = 0;
320   while (cnt < total)
321   {
322     memset (rbind, 0, sizeof (rbind));
323     rbind[0].buffer_type = MYSQL_TYPE_BLOB;
324     rbind[0].buffer_length = sizeof (buffer);
325     rbind[0].length = &v_length;
326     rbind[0].buffer = buffer;
327     rbind[1].buffer_type = MYSQL_TYPE_LONGLONG;
328     rbind[1].is_unsigned = 1;
329     rbind[1].buffer = &v_expire;
330     off = (off + 1) % total;
331     if (GNUNET_OK !=
332         (ret =
333          GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->select_value, 2, rbind,
334                                         return_ok, NULL, MYSQL_TYPE_BLOB, key,
335                                         sizeof (struct GNUNET_HashCode), &h_length,
336                                         MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
337                                         MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
338                                         MYSQL_TYPE_LONG, &off, GNUNET_YES, -1)))
339     {
340       if (ret == GNUNET_SYSERR)
341         itable (plugin);
342       return GNUNET_SYSERR;
343     }
344     cnt++;
345     expire.abs_value = v_expire;
346     if (GNUNET_OK != iter (iter_cls, expire, key, v_length, buffer, type))
347       break;
348   }
349   return cnt;
350 }
351
352
353 /**
354  * Delete the entry with the lowest expiration value
355  * from the datacache right now.
356  *
357  * @param cls closure (our "struct Plugin")
358  * @return GNUNET_OK on success, GNUNET_SYSERR on error
359  */
360 static int
361 mysql_plugin_del (void *cls)
362 {
363   struct Plugin *plugin = cls;
364
365   MYSQL_BIND rbind[5];
366   unsigned int v_type;
367   struct GNUNET_HashCode v_key;
368   struct GNUNET_HashCode vhash;
369   unsigned long k_length;
370   unsigned long h_length;
371   unsigned long v_length;
372   int ret;
373   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
374
375   k_length = sizeof (struct GNUNET_HashCode);
376   h_length = sizeof (struct GNUNET_HashCode);
377   v_length = sizeof (buffer);
378   memset (rbind, 0, sizeof (rbind));
379   rbind[0].buffer_type = MYSQL_TYPE_BLOB;
380   rbind[0].buffer_length = sizeof (struct GNUNET_HashCode);
381   rbind[0].length = &k_length;
382   rbind[0].buffer = &v_key;
383   rbind[1].buffer_type = MYSQL_TYPE_BLOB;
384   rbind[1].buffer_length = sizeof (struct GNUNET_HashCode);
385   rbind[1].length = &h_length;
386   rbind[1].buffer = &vhash;
387   rbind[2].buffer_type = MYSQL_TYPE_LONG;
388   rbind[2].is_unsigned = 1;
389   rbind[2].buffer = &v_type;
390   rbind[3].buffer_type = MYSQL_TYPE_BLOB;
391   rbind[3].buffer_length = sizeof (buffer);
392   rbind[3].length = &v_length;
393   rbind[3].buffer = buffer;
394   if ((GNUNET_OK !=
395        (ret =
396         GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->select_old_value, 4,
397                                        rbind, return_ok, NULL, -1))) ||
398       (GNUNET_OK !=
399        (ret =
400         GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->delete_value, NULL,
401                                 MYSQL_TYPE_BLOB, &v_key,
402                                 sizeof (struct GNUNET_HashCode), &k_length,
403                                 MYSQL_TYPE_BLOB, &vhash,
404                                 sizeof (struct GNUNET_HashCode), &h_length,
405                                 MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
406                                 MYSQL_TYPE_BLOB, buffer,
407                                 (unsigned long) sizeof (buffer), &v_length,
408                                 -1))))
409   {
410     if (ret == GNUNET_SYSERR)
411       itable (plugin);
412     return GNUNET_SYSERR;
413   }
414   plugin->env->delete_notify (plugin->env->cls, &v_key, v_length + OVERHEAD);
415
416   return GNUNET_OK;
417 }
418
419
420 /**
421  * Entry point for the plugin.
422  *
423  * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
424  * @return the plugin's closure (our "struct Plugin")
425  */
426 void *
427 libgnunet_plugin_datacache_mysql_init (void *cls)
428 {
429   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
430   struct GNUNET_DATACACHE_PluginFunctions *api;
431   struct Plugin *plugin;
432
433   plugin = GNUNET_malloc (sizeof (struct Plugin));
434   plugin->env = env;
435   plugin->mc = GNUNET_MYSQL_context_create (env->cfg, "datacache-mysql");
436   if ( (NULL == plugin->mc) ||
437        (GNUNET_OK != itable (plugin)) )
438   {
439     if (NULL != plugin->mc)
440       GNUNET_MYSQL_context_destroy (plugin->mc);
441     GNUNET_free (plugin);
442     return NULL;
443   }
444   api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
445   api->cls = plugin;
446   api->get = &mysql_plugin_get;
447   api->put = &mysql_plugin_put;
448   api->del = &mysql_plugin_del;
449   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
450                    _("MySQL datacache running\n"));
451   return api;
452 }
453
454
455 /**
456  * Exit point from the plugin.
457  *
458  * @param cls closure (our "struct Plugin")
459  * @return NULL
460  */
461 void *
462 libgnunet_plugin_datacache_mysql_done (void *cls)
463 {
464   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
465   struct Plugin *plugin = api->cls;
466
467   GNUNET_MYSQL_context_destroy (plugin->mc);
468   GNUNET_free (plugin);
469   GNUNET_free (api);
470   return NULL;
471 }
472
473
474 /* end of plugin_datacache_mysql.c */