bec8bffd27b6b3e2fd2d86cb4dfb79bffe1186c9
[oweals/gnunet.git] / src / namecache / plugin_namecache_postgres.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013, 2016 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namecache/plugin_namecache_postgres.c
23  * @brief postgres-based namecache backend
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
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"
33
34
35 /**
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).
40  *
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.
44  */
45 #define BUSY_TIMEOUT_MS 1000
46
47
48 /**
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).
52  */
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)
54
55 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
56
57
58 /**
59  * Context for all functions in this plugin.
60  */
61 struct Plugin
62 {
63
64   const struct GNUNET_CONFIGURATION_Handle *cfg;
65
66   /**
67    * Native Postgres database handle.
68    */
69   PGconn *dbh;
70
71 };
72
73
74 /**
75  * Create our database indices.
76  *
77  * @param dbh handle to the database
78  */
79 static void
80 create_indices (PGconn * dbh)
81 {
82   /* create indices */
83   if ( (GNUNET_OK !=
84         GNUNET_POSTGRES_exec (dbh,
85                               "CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)")) ||
86        (GNUNET_OK !=
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"));
91 }
92
93
94 /**
95  * Initialize the database connections and associated
96  * data structures (create tables and indices
97  * as needed as well).
98  *
99  * @param plugin the plugin context (state for this module)
100  * @return GNUNET_OK on success
101  */
102 static int
103 database_setup (struct Plugin *plugin)
104 {
105   PGresult *res;
106
107   plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg,
108                                          "namecache-postgres");
109   if (NULL == plugin->dbh)
110     return GNUNET_SYSERR;
111   if (GNUNET_YES ==
112       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
113                                             "namecache-postgres",
114                                             "TEMPORARY_TABLE"))
115   {
116     res =
117       PQexec (plugin->dbh,
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"
122               ")" "WITH OIDS");
123   }
124   else
125   {
126     res =
127       PQexec (plugin->dbh,
128               "CREATE TABLE ns096blocks ("
129               " query BYTEA NOT NULL DEFAULT '',"
130               " block BYTEA NOT NULL DEFAULT '',"
131               " expiration_time BIGINT NOT NULL DEFAULT 0"
132               ")" "WITH OIDS");
133   }
134   if ( (NULL == res) ||
135        ((PQresultStatus (res) != PGRES_COMMAND_OK) &&
136         (0 != strcmp ("42P07",    /* duplicate table */
137                       PQresultErrorField
138                       (res,
139                        PG_DIAG_SQLSTATE)))))
140   {
141     (void) GNUNET_POSTGRES_check_result (plugin->dbh, res,
142                                          PGRES_COMMAND_OK, "CREATE TABLE",
143                                          "ns096blocks");
144     PQfinish (plugin->dbh);
145     plugin->dbh = NULL;
146     return GNUNET_SYSERR;
147   }
148   if (PQresultStatus (res) == PGRES_COMMAND_OK)
149     create_indices (plugin->dbh);
150   PQclear (res);
151
152   if ((GNUNET_OK !=
153        GNUNET_POSTGRES_prepare (plugin->dbh,
154                                 "cache_block",
155                                 "INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
156                                 "($1, $2, $3)", 3)) ||
157       (GNUNET_OK !=
158        GNUNET_POSTGRES_prepare (plugin->dbh,
159                                 "expire_blocks",
160                                 "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) ||
161       (GNUNET_OK !=
162        GNUNET_POSTGRES_prepare (plugin->dbh,
163                                 "delete_block",
164                                 "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) ||
165       (GNUNET_OK !=
166        GNUNET_POSTGRES_prepare (plugin->dbh,
167                                 "lookup_block",
168                                 "SELECT block FROM ns096blocks WHERE query=$1"
169                                 " ORDER BY expiration_time DESC LIMIT 1", 1)))
170   {
171     PQfinish (plugin->dbh);
172     plugin->dbh = NULL;
173     return GNUNET_SYSERR;
174   }
175   return GNUNET_OK;
176 }
177
178
179 /**
180  * Removes any expired block.
181  *
182  * @param plugin the plugin
183  */
184 static void
185 namecache_postgres_expire_blocks (struct Plugin *plugin)
186 {
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
191   };
192   PGresult *res;
193
194   res = GNUNET_PQ_exec_prepared (plugin->dbh,
195                                  "expire_blocks",
196                                  params);
197   if (GNUNET_OK !=
198       GNUNET_POSTGRES_check_result (plugin->dbh,
199                                     res,
200                                     PGRES_COMMAND_OK,
201                                     "PQexecPrepared",
202                                     "expire_blocks"))
203     return;
204   PQclear (res);
205 }
206
207
208 /**
209  * Delete older block in the datastore.
210  *
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
214  */
215 static void
216 delete_old_block (struct Plugin *plugin,
217                   const struct GNUNET_HashCode *query,
218                   struct GNUNET_TIME_AbsoluteNBO expiration_time)
219 {
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
224   };
225   PGresult *res;
226
227   res = GNUNET_PQ_exec_prepared (plugin->dbh,
228                                  "delete_block",
229                                  params);
230   if (GNUNET_OK !=
231       GNUNET_POSTGRES_check_result (plugin->dbh,
232                                     res,
233                                     PGRES_COMMAND_OK,
234                                     "PQexecPrepared",
235                                     "delete_block"))
236     return;
237   PQclear (res);
238 }
239
240
241 /**
242  * Cache a block in the datastore.
243  *
244  * @param cls closure (internal context for the plugin)
245  * @param block block to cache
246  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
247  */
248 static int
249 namecache_postgres_cache_block (void *cls,
250                                 const struct GNUNET_GNSRECORD_Block *block)
251 {
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
262   };
263   PGresult *res;
264
265   namecache_postgres_expire_blocks (plugin);
266   GNUNET_CRYPTO_hash (&block->derived_key,
267                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
268                       &query);
269   if (block_size > 64 * 65536)
270   {
271     GNUNET_break (0);
272     return GNUNET_SYSERR;
273   }
274   delete_old_block (plugin, &query, block->expiration_time);
275
276   res = GNUNET_PQ_exec_prepared (plugin->dbh,
277                                  "cache_block",
278                                  params);
279   if (GNUNET_OK !=
280       GNUNET_POSTGRES_check_result (plugin->dbh,
281                                     res,
282                                     PGRES_COMMAND_OK,
283                                     "PQexecPrepared",
284                                     "cache_block"))
285     return GNUNET_SYSERR;
286   PQclear (res);
287   return GNUNET_OK;
288 }
289
290
291 /**
292  * Get the block for a particular zone and label in the
293  * datastore.  Will return at most one result to the iterator.
294  *
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
300  */
301 static int
302 namecache_postgres_lookup_block (void *cls,
303                                  const struct GNUNET_HashCode *query,
304                                  GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
305 {
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
310   };
311   PGresult *res;
312   unsigned int cnt;
313   size_t bsize;
314   const struct GNUNET_GNSRECORD_Block *block;
315
316   res = GNUNET_PQ_exec_prepared (plugin->dbh,
317                                  "lookup_block",
318                                  params);
319   if (GNUNET_OK !=
320       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK,
321                                     "PQexecPrepared",
322                                     "lookup_block"))
323   {
324     LOG (GNUNET_ERROR_TYPE_DEBUG,
325          "Failing lookup (postgres error)\n");
326     return GNUNET_SYSERR;
327   }
328   if (0 == (cnt = PQntuples (res)))
329   {
330     /* no result */
331     LOG (GNUNET_ERROR_TYPE_DEBUG,
332          "Ending iteration (no more results)\n");
333     PQclear (res);
334     return GNUNET_NO;
335   }
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)) )
344   {
345     GNUNET_break (0);
346     LOG (GNUNET_ERROR_TYPE_DEBUG,
347          "Failing lookup (corrupt block)\n");
348     PQclear (res);
349     return GNUNET_SYSERR;
350   }
351   iter (iter_cls, block);
352   PQclear (res);
353   return GNUNET_OK;
354 }
355
356
357 /**
358  * Shutdown database connection and associate data
359  * structures.
360  *
361  * @param plugin the plugin context (state for this module)
362  */
363 static void
364 database_shutdown (struct Plugin *plugin)
365 {
366   PQfinish (plugin->dbh);
367   plugin->dbh = NULL;
368 }
369
370
371 /**
372  * Entry point for the plugin.
373  *
374  * @param cls the `struct GNUNET_NAMECACHE_PluginEnvironment *`
375  * @return NULL on error, otherwise the plugin context
376  */
377 void *
378 libgnunet_plugin_namecache_postgres_init (void *cls)
379 {
380   static struct Plugin plugin;
381   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
382   struct GNUNET_NAMECACHE_PluginFunctions *api;
383
384   if (NULL != plugin.cfg)
385     return NULL;                /* can only initialize once! */
386   memset (&plugin, 0, sizeof (struct Plugin));
387   plugin.cfg = cfg;
388   if (GNUNET_OK != database_setup (&plugin))
389   {
390     database_shutdown (&plugin);
391     return NULL;
392   }
393   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
394   api->cls = &plugin;
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"));
399   return api;
400 }
401
402
403 /**
404  * Exit point from the plugin.
405  *
406  * @param cls the plugin context (as returned by "init")
407  * @return always NULL
408  */
409 void *
410 libgnunet_plugin_namecache_postgres_done (void *cls)
411 {
412   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
413   struct Plugin *plugin = api->cls;
414
415   database_shutdown (plugin);
416   plugin->cfg = NULL;
417   GNUNET_free (api);
418   LOG (GNUNET_ERROR_TYPE_DEBUG,
419        "postgres plugin is finished\n");
420   return NULL;
421 }
422
423 /* end of plugin_namecache_postgres.c */