2 * This file is part of GNUnet
3 * Copyright (C) 2009-2013, 2016 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
22 * @file namecache/plugin_namecache_postgres.c
23 * @brief postgres-based namecache backend
24 * @author Christian Grothoff
27 #include "gnunet_namecache_plugin.h"
28 #include "gnunet_namecache_service.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "gnunet_postgres_lib.h"
31 #include "gnunet_pq_lib.h"
32 #include "namecache.h"
36 * After how many ms "busy" should a DB operation fail for good?
37 * A low value makes sure that we are more responsive to requests
38 * (especially PUTs). A high value guarantees a higher success
39 * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
41 * The default value of 1s should ensure that users do not experience
42 * huge latencies while at the same time allowing operations to succeed
43 * with reasonable probability.
45 #define BUSY_TIMEOUT_MS 1000
49 * Log an error message at log-level 'level' that indicates
50 * a failure of the command 'cmd' on file 'filename'
51 * with the message given by strerror(errno).
53 #define LOG_POSTGRES(db, level, cmd) do { GNUNET_log_from (level, "namecache-postgres", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0)
55 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
59 * Context for all functions in this plugin.
64 const struct GNUNET_CONFIGURATION_Handle *cfg;
67 * Native Postgres database handle.
75 * Create our database indices.
77 * @param dbh handle to the database
80 create_indices (PGconn * dbh)
84 GNUNET_POSTGRES_exec (dbh,
85 "CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)")) ||
87 GNUNET_POSTGRES_exec (dbh,
88 "CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)")) )
89 LOG (GNUNET_ERROR_TYPE_ERROR,
90 _("Failed to create indices\n"));
95 * Initialize the database connections and associated
96 * data structures (create tables and indices
99 * @param plugin the plugin context (state for this module)
100 * @return GNUNET_OK on success
103 database_setup (struct Plugin *plugin)
107 plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg,
108 "namecache-postgres");
109 if (NULL == plugin->dbh)
110 return GNUNET_SYSERR;
112 GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
113 "namecache-postgres",
118 "CREATE TEMPORARY TABLE ns096blocks ("
119 " query BYTEA NOT NULL DEFAULT '',"
120 " block BYTEA NOT NULL DEFAULT '',"
121 " expiration_time BIGINT NOT NULL DEFAULT 0"
128 "CREATE TABLE ns096blocks ("
129 " query BYTEA NOT NULL DEFAULT '',"
130 " block BYTEA NOT NULL DEFAULT '',"
131 " expiration_time BIGINT NOT NULL DEFAULT 0"
134 if ( (NULL == res) ||
135 ((PQresultStatus (res) != PGRES_COMMAND_OK) &&
136 (0 != strcmp ("42P07", /* duplicate table */
139 PG_DIAG_SQLSTATE)))))
141 (void) GNUNET_POSTGRES_check_result (plugin->dbh, res,
142 PGRES_COMMAND_OK, "CREATE TABLE",
144 PQfinish (plugin->dbh);
146 return GNUNET_SYSERR;
148 if (PQresultStatus (res) == PGRES_COMMAND_OK)
149 create_indices (plugin->dbh);
153 GNUNET_POSTGRES_prepare (plugin->dbh,
155 "INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
156 "($1, $2, $3)", 3)) ||
158 GNUNET_POSTGRES_prepare (plugin->dbh,
160 "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) ||
162 GNUNET_POSTGRES_prepare (plugin->dbh,
164 "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) ||
166 GNUNET_POSTGRES_prepare (plugin->dbh,
168 "SELECT block FROM ns096blocks WHERE query=$1"
169 " ORDER BY expiration_time DESC LIMIT 1", 1)))
171 PQfinish (plugin->dbh);
173 return GNUNET_SYSERR;
180 * Removes any expired block.
182 * @param plugin the plugin
185 namecache_postgres_expire_blocks (struct Plugin *plugin)
187 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
188 struct GNUNET_PQ_QueryParam params[] = {
189 GNUNET_PQ_query_param_absolute_time (&now),
190 GNUNET_PQ_query_param_end
194 res = GNUNET_PQ_exec_prepared (plugin->dbh,
198 GNUNET_POSTGRES_check_result (plugin->dbh,
209 * Delete older block in the datastore.
211 * @param plugin the plugin
212 * @param query query for the block
213 * @param expiration_time how old does the block have to be for deletion
216 delete_old_block (struct Plugin *plugin,
217 const struct GNUNET_HashCode *query,
218 struct GNUNET_TIME_AbsoluteNBO expiration_time)
220 struct GNUNET_PQ_QueryParam params[] = {
221 GNUNET_PQ_query_param_auto_from_type (query),
222 GNUNET_PQ_query_param_absolute_time_nbo (&expiration_time),
223 GNUNET_PQ_query_param_end
227 res = GNUNET_PQ_exec_prepared (plugin->dbh,
231 GNUNET_POSTGRES_check_result (plugin->dbh,
242 * Cache a block in the datastore.
244 * @param cls closure (internal context for the plugin)
245 * @param block block to cache
246 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
249 namecache_postgres_cache_block (void *cls,
250 const struct GNUNET_GNSRECORD_Block *block)
252 struct Plugin *plugin = cls;
253 struct GNUNET_HashCode query;
254 size_t block_size = ntohl (block->purpose.size) +
255 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
256 sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
257 struct GNUNET_PQ_QueryParam params[] = {
258 GNUNET_PQ_query_param_auto_from_type (&query),
259 GNUNET_PQ_query_param_fixed_size (block, block_size),
260 GNUNET_PQ_query_param_absolute_time_nbo (&block->expiration_time),
261 GNUNET_PQ_query_param_end
265 namecache_postgres_expire_blocks (plugin);
266 GNUNET_CRYPTO_hash (&block->derived_key,
267 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
269 if (block_size > 64 * 65536)
272 return GNUNET_SYSERR;
274 delete_old_block (plugin, &query, block->expiration_time);
276 res = GNUNET_PQ_exec_prepared (plugin->dbh,
280 GNUNET_POSTGRES_check_result (plugin->dbh,
285 return GNUNET_SYSERR;
292 * Get the block for a particular zone and label in the
293 * datastore. Will return at most one result to the iterator.
295 * @param cls closure (internal context for the plugin)
296 * @param query hash of public key derived from the zone and the label
297 * @param iter function to call with the result
298 * @param iter_cls closure for @a iter
299 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
302 namecache_postgres_lookup_block (void *cls,
303 const struct GNUNET_HashCode *query,
304 GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
306 struct Plugin *plugin = cls;
307 struct GNUNET_PQ_QueryParam params[] = {
308 GNUNET_PQ_query_param_auto_from_type (query),
309 GNUNET_PQ_query_param_end
314 const struct GNUNET_GNSRECORD_Block *block;
316 res = GNUNET_PQ_exec_prepared (plugin->dbh,
320 GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK,
324 LOG (GNUNET_ERROR_TYPE_DEBUG,
325 "Failing lookup (postgres error)\n");
326 return GNUNET_SYSERR;
328 if (0 == (cnt = PQntuples (res)))
331 LOG (GNUNET_ERROR_TYPE_DEBUG,
332 "Ending iteration (no more results)\n");
336 GNUNET_assert (1 == cnt);
337 GNUNET_assert (1 != PQnfields (res));
338 bsize = PQgetlength (res, 0, 0);
339 block = (const struct GNUNET_GNSRECORD_Block *) PQgetvalue (res, 0, 0);
340 if ( (bsize < sizeof (*block)) ||
341 (bsize != ntohl (block->purpose.size) +
342 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
343 sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) )
346 LOG (GNUNET_ERROR_TYPE_DEBUG,
347 "Failing lookup (corrupt block)\n");
349 return GNUNET_SYSERR;
351 iter (iter_cls, block);
358 * Shutdown database connection and associate data
361 * @param plugin the plugin context (state for this module)
364 database_shutdown (struct Plugin *plugin)
366 PQfinish (plugin->dbh);
372 * Entry point for the plugin.
374 * @param cls the `struct GNUNET_NAMECACHE_PluginEnvironment *`
375 * @return NULL on error, otherwise the plugin context
378 libgnunet_plugin_namecache_postgres_init (void *cls)
380 static struct Plugin plugin;
381 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
382 struct GNUNET_NAMECACHE_PluginFunctions *api;
384 if (NULL != plugin.cfg)
385 return NULL; /* can only initialize once! */
386 memset (&plugin, 0, sizeof (struct Plugin));
388 if (GNUNET_OK != database_setup (&plugin))
390 database_shutdown (&plugin);
393 api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
395 api->cache_block = &namecache_postgres_cache_block;
396 api->lookup_block = &namecache_postgres_lookup_block;
397 LOG (GNUNET_ERROR_TYPE_INFO,
398 _("Postgres database running\n"));
404 * Exit point from the plugin.
406 * @param cls the plugin context (as returned by "init")
407 * @return always NULL
410 libgnunet_plugin_namecache_postgres_done (void *cls)
412 struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
413 struct Plugin *plugin = api->cls;
415 database_shutdown (plugin);
418 LOG (GNUNET_ERROR_TYPE_DEBUG,
419 "postgres plugin is finished\n");
423 /* end of plugin_namecache_postgres.c */