2 This file is part of GNUnet
3 (C) 2006, 2009, 2010 Christian Grothoff (and other contributing authors)
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.
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.
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.
22 * @file datacache/plugin_datacache_mysql.c
23 * @brief mysql for an implementation of a database backend for the datacache
24 * @author Christian Grothoff
28 * 1) Access mysql as root,
34 * and do the following. [You should replace $USER with the username
35 * that will be running the gnunetd process].
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');
43 * 2) In the $HOME directory of $USER, create a ".my.cnf" file
44 * with the following lines
48 password=$the_password_you_like
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,
59 * 3) Still, perhaps you should briefly try if the DB connection
60 * works. First, login as $USER. Then use,
62 $ mysql -u $USER -p $the_password_you_like
66 * If you get the message "Database changed" it probably works.
68 * [If you get "ERROR 2002: Can't connect to local MySQL server
69 * through socket '/tmp/mysql.sock' (2)" it may be resolvable by
70 * "ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock"
71 * so there may be some additional trouble depending on your mysql setup.]
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.
81 #include "gnunet_util_lib.h"
82 #include "gnunet_datacache_plugin.h"
83 #include "gnunet_mysql_lib.h"
84 #include <mysql/mysql.h>
86 #define DEBUG_DATACACHE_MYSQL GNUNET_EXTRA_LOGGING
89 * Estimate of the per-entry overhead (including indices).
91 #define OVERHEAD ((4*2+4*2+8*2+8*2+sizeof(GNUNET_HashCode)*5+8))
94 * Die with an error message that indicates
95 * a failure of the command 'cmd' with the message given
98 #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);
101 * Log an error message at log-level 'level' that indicates
102 * a failure of the command 'cmd' on file 'filename'
103 * with the message given by strerror(errno).
105 #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);
109 * Context for all functions in this plugin.
114 * Our execution environment.
116 struct GNUNET_DATACACHE_PluginEnvironment *env;
119 * Handle to the mysql database.
121 struct GNUNET_MYSQL_Context *mc;
123 #define SELECT_VALUE_STMT "SELECT value,expire FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ? LIMIT 1 OFFSET ?"
124 struct GNUNET_MYSQL_StatementHandle *select_value;
126 #define COUNT_VALUE_STMT "SELECT count(*) FROM gn080dstore FORCE INDEX (hashidx) WHERE hash=? AND type=? AND expire >= ?"
127 struct GNUNET_MYSQL_StatementHandle *count_value;
129 #define SELECT_OLD_VALUE_STMT "SELECT hash, vhash, type, value FROM gn080dstore FORCE INDEX (expireidx) ORDER BY puttime ASC LIMIT 1"
130 struct GNUNET_MYSQL_StatementHandle *select_old_value;
132 #define DELETE_VALUE_STMT "DELETE FROM gn080dstore WHERE hash = ? AND vhash = ? AND type = ? AND value = ?"
133 struct GNUNET_MYSQL_StatementHandle *delete_value;
135 #define INSERT_VALUE_STMT "INSERT INTO gn080dstore (type, puttime, expire, hash, vhash, value) "\
136 "VALUES (?, ?, ?, ?, ?, ?)"
137 struct GNUNET_MYSQL_StatementHandle *insert_value;
139 #define UPDATE_VALUE_STMT "UPDATE gn080dstore FORCE INDEX (allidx) SET puttime=?, expire=? "\
140 "WHERE hash=? AND vhash=? AND type=?"
141 struct GNUNET_MYSQL_StatementHandle *update_value;
147 * Create temporary table and prepare statements.
149 * @param plugin plugin context
150 * @return GNUNET_OK on success
153 itable (struct Plugin *plugin)
155 #define MRUNS(a) (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, a) )
157 ("CREATE TEMPORARY TABLE gn080dstore ("
158 " type INT(11) UNSIGNED NOT NULL DEFAULT 0,"
159 " puttime BIGINT UNSIGNED NOT NULL DEFAULT 0,"
160 " expire BIGINT UNSIGNED NOT NULL DEFAULT 0,"
161 " hash BINARY(64) NOT NULL DEFAULT '',"
162 " vhash BINARY(64) NOT NULL DEFAULT '',"
163 " value BLOB NOT NULL DEFAULT '',"
164 " INDEX hashidx (hash(64),type,expire),"
165 " INDEX allidx (hash(64),vhash(64),type)," " INDEX expireidx (puttime)"
166 ") ENGINE=InnoDB") || MRUNS ("SET AUTOCOMMIT = 1"))
167 return GNUNET_SYSERR;
169 #define PINIT(a,b) (NULL == (a = GNUNET_MYSQL_statement_prepare (plugin->mc, b)))
170 if (PINIT (plugin->select_value, SELECT_VALUE_STMT) ||
171 PINIT (plugin->count_value, COUNT_VALUE_STMT) ||
172 PINIT (plugin->select_old_value, SELECT_OLD_VALUE_STMT) ||
173 PINIT (plugin->delete_value, DELETE_VALUE_STMT) ||
174 PINIT (plugin->insert_value, INSERT_VALUE_STMT) ||
175 PINIT (plugin->update_value, UPDATE_VALUE_STMT))
176 return GNUNET_SYSERR;
183 * Store an item in the datastore.
185 * @param cls closure (our "struct Plugin")
186 * @param key key to store data under
187 * @param size number of bytes in data
188 * @param data data to store
189 * @param type type of the value
190 * @param discard_time when to discard the value in any case
191 * @return 0 on error, number of bytes used otherwise
194 mysql_plugin_put (void *cls, const GNUNET_HashCode * key, size_t size,
195 const char *data, enum GNUNET_BLOCK_Type type,
196 struct GNUNET_TIME_Absolute discard_time)
198 struct Plugin *plugin = cls;
199 struct GNUNET_TIME_Absolute now;
200 unsigned long k_length;
201 unsigned long h_length;
202 unsigned long v_length;
203 unsigned long long v_now;
204 unsigned long long v_discard_time;
206 GNUNET_HashCode vhash;
209 if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE)
210 return GNUNET_SYSERR;
211 GNUNET_CRYPTO_hash (data, size, &vhash);
212 now = GNUNET_TIME_absolute_get ();
214 /* first try UPDATE */
215 h_length = sizeof (GNUNET_HashCode);
216 k_length = sizeof (GNUNET_HashCode);
219 v_now = (unsigned long long) now.abs_value;
220 v_discard_time = (unsigned long long) discard_time.abs_value;
222 GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->update_value, NULL,
223 MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
224 MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
225 MYSQL_TYPE_BLOB, key, sizeof (GNUNET_HashCode),
226 &k_length, MYSQL_TYPE_BLOB, &vhash,
227 sizeof (GNUNET_HashCode), &h_length,
228 MYSQL_TYPE_LONG, &v_type, GNUNET_YES, -1))
232 h_length = sizeof (GNUNET_HashCode);
233 k_length = sizeof (GNUNET_HashCode);
237 GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->insert_value, NULL,
238 MYSQL_TYPE_LONG, &type, GNUNET_YES,
239 MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
240 MYSQL_TYPE_LONGLONG, &v_discard_time, GNUNET_YES,
241 MYSQL_TYPE_BLOB, key, sizeof (GNUNET_HashCode),
242 &k_length, MYSQL_TYPE_BLOB, &vhash,
243 sizeof (GNUNET_HashCode), &h_length,
244 MYSQL_TYPE_BLOB, data, (unsigned long) size,
247 if (ret == GNUNET_SYSERR)
249 return GNUNET_SYSERR;
251 return size + OVERHEAD;
256 return_ok (void *cls, unsigned int num_values, MYSQL_BIND * values)
263 * Iterate over the results for a particular key
266 * @param cls closure (our "struct Plugin")
268 * @param type entries of which type are relevant?
269 * @param iter maybe NULL (to just count)
270 * @param iter_cls closure for iter
271 * @return the number of results found
274 mysql_plugin_get (void *cls, const GNUNET_HashCode * key,
275 enum GNUNET_BLOCK_Type type, GNUNET_DATACACHE_Iterator iter,
278 struct Plugin *plugin = cls;
280 unsigned long h_length;
281 unsigned long v_length;
282 unsigned long long v_expire;
283 struct GNUNET_TIME_Absolute now;
284 struct GNUNET_TIME_Absolute expire;
286 unsigned long long total;
287 unsigned long long v_now;
291 char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
293 now = GNUNET_TIME_absolute_get ();
294 h_length = sizeof (GNUNET_HashCode);
295 v_length = sizeof (buffer);
297 memset (rbind, 0, sizeof (rbind));
298 rbind[0].buffer_type = MYSQL_TYPE_LONGLONG;
299 rbind[0].buffer = &total;
300 rbind[0].is_unsigned = GNUNET_YES;
302 v_now = (unsigned long long) now.abs_value;
305 GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->count_value, 1, rbind,
306 return_ok, NULL, MYSQL_TYPE_BLOB, key,
307 sizeof (GNUNET_HashCode), &h_length,
308 MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
309 MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
310 -1))) || (-1 == total))
312 if (ret == GNUNET_SYSERR)
314 return GNUNET_SYSERR;
316 if ((iter == NULL) || (total == 0))
319 off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, total);
323 memset (rbind, 0, sizeof (rbind));
324 rbind[0].buffer_type = MYSQL_TYPE_BLOB;
325 rbind[0].buffer_length = sizeof (buffer);
326 rbind[0].length = &v_length;
327 rbind[0].buffer = buffer;
328 rbind[1].buffer_type = MYSQL_TYPE_LONGLONG;
329 rbind[1].is_unsigned = 1;
330 rbind[1].buffer = &v_expire;
331 off = (off + 1) % total;
334 GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->select_value, 2, rbind,
335 return_ok, NULL, MYSQL_TYPE_BLOB, key,
336 sizeof (GNUNET_HashCode), &h_length,
337 MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
338 MYSQL_TYPE_LONGLONG, &v_now, GNUNET_YES,
339 MYSQL_TYPE_LONG, &off, GNUNET_YES, -1)))
341 if (ret == GNUNET_SYSERR)
343 return GNUNET_SYSERR;
346 expire.abs_value = v_expire;
347 if (GNUNET_OK != iter (iter_cls, expire, key, v_length, buffer, type))
355 * Delete the entry with the lowest expiration value
356 * from the datacache right now.
358 * @param cls closure (our "struct Plugin")
359 * @return GNUNET_OK on success, GNUNET_SYSERR on error
362 mysql_plugin_del (void *cls)
364 struct Plugin *plugin = cls;
368 GNUNET_HashCode v_key;
369 GNUNET_HashCode vhash;
370 unsigned long k_length;
371 unsigned long h_length;
372 unsigned long v_length;
374 char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
376 k_length = sizeof (GNUNET_HashCode);
377 h_length = sizeof (GNUNET_HashCode);
378 v_length = sizeof (buffer);
379 memset (rbind, 0, sizeof (rbind));
380 rbind[0].buffer_type = MYSQL_TYPE_BLOB;
381 rbind[0].buffer_length = sizeof (GNUNET_HashCode);
382 rbind[0].length = &k_length;
383 rbind[0].buffer = &v_key;
384 rbind[1].buffer_type = MYSQL_TYPE_BLOB;
385 rbind[1].buffer_length = sizeof (GNUNET_HashCode);
386 rbind[1].length = &h_length;
387 rbind[1].buffer = &vhash;
388 rbind[2].buffer_type = MYSQL_TYPE_LONG;
389 rbind[2].is_unsigned = 1;
390 rbind[2].buffer = &v_type;
391 rbind[3].buffer_type = MYSQL_TYPE_BLOB;
392 rbind[3].buffer_length = sizeof (buffer);
393 rbind[3].length = &v_length;
394 rbind[3].buffer = buffer;
397 GNUNET_MYSQL_statement_run_prepared_select (plugin->mc, plugin->select_old_value, 4,
398 rbind, return_ok, NULL, -1))) ||
401 GNUNET_MYSQL_statement_run_prepared (plugin->mc, plugin->delete_value, NULL,
402 MYSQL_TYPE_BLOB, &v_key,
403 sizeof (GNUNET_HashCode), &k_length,
404 MYSQL_TYPE_BLOB, &vhash,
405 sizeof (GNUNET_HashCode), &h_length,
406 MYSQL_TYPE_LONG, &v_type, GNUNET_YES,
407 MYSQL_TYPE_BLOB, buffer,
408 (unsigned long) sizeof (buffer), &v_length,
411 if (ret == GNUNET_SYSERR)
413 return GNUNET_SYSERR;
415 plugin->env->delete_notify (plugin->env->cls, &v_key, v_length + OVERHEAD);
422 * Entry point for the plugin.
424 * @param cls closure (the "struct GNUNET_DATACACHE_PluginEnvironmnet")
425 * @return the plugin's closure (our "struct Plugin")
428 libgnunet_plugin_datacache_mysql_init (void *cls)
430 struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
431 struct GNUNET_DATACACHE_PluginFunctions *api;
432 struct Plugin *plugin;
434 plugin = GNUNET_malloc (sizeof (struct Plugin));
436 plugin->mc = GNUNET_MYSQL_context_create (env->cfg, "datacache-mysql");
437 if ( (NULL == plugin->mc) ||
438 (GNUNET_OK != itable (plugin)) )
440 if (NULL != plugin->mc)
441 GNUNET_MYSQL_context_destroy (plugin->mc);
442 GNUNET_free (plugin);
445 api = GNUNET_malloc (sizeof (struct GNUNET_DATACACHE_PluginFunctions));
447 api->get = &mysql_plugin_get;
448 api->put = &mysql_plugin_put;
449 api->del = &mysql_plugin_del;
450 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
451 _("MySQL datacache running\n"));
457 * Exit point from the plugin.
459 * @param cls closure (our "struct Plugin")
463 libgnunet_plugin_datacache_mysql_done (void *cls)
465 struct GNUNET_DATACACHE_PluginFunctions *api = cls;
466 struct Plugin *plugin = api->cls;
468 GNUNET_MYSQL_context_destroy (plugin->mc);
469 GNUNET_free (plugin);
471 mysql_library_end ();
476 /* end of plugin_datacache_mysql.c */