- debug
[oweals/gnunet.git] / src / namecache / plugin_namecache_postgres.c
1  /*
2   * This file is part of GNUnet
3   * (C) 2009-2013 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 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 "namecache.h"
32
33
34 /**
35  * After how many ms "busy" should a DB operation fail for good?
36  * A low value makes sure that we are more responsive to requests
37  * (especially PUTs).  A high value guarantees a higher success
38  * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
39  *
40  * The default value of 1s should ensure that users do not experience
41  * huge latencies while at the same time allowing operations to succeed
42  * with reasonable probability.
43  */
44 #define BUSY_TIMEOUT_MS 1000
45
46
47 /**
48  * Log an error message at log-level 'level' that indicates
49  * a failure of the command 'cmd' on file 'filename'
50  * with the message given by strerror(errno).
51  */
52 #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)
53
54 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__)
55
56
57 /**
58  * Context for all functions in this plugin.
59  */
60 struct Plugin
61 {
62
63   const struct GNUNET_CONFIGURATION_Handle *cfg;
64
65   /**
66    * Native Postgres database handle.
67    */
68   PGconn *dbh;
69
70 };
71
72
73 /**
74  * Create our database indices.
75  *
76  * @param dbh handle to the database
77  */
78 static void
79 create_indices (PGconn * dbh)
80 {
81   /* create indices */
82   if ( (GNUNET_OK !=
83         GNUNET_POSTGRES_exec (dbh,
84                               "CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)")) ||
85        (GNUNET_OK !=
86         GNUNET_POSTGRES_exec (dbh,
87                               "CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)")) )
88     LOG (GNUNET_ERROR_TYPE_ERROR,
89          _("Failed to create indices\n"));
90 }
91
92
93 /**
94  * Initialize the database connections and associated
95  * data structures (create tables and indices
96  * as needed as well).
97  *
98  * @param plugin the plugin context (state for this module)
99  * @return GNUNET_OK on success
100  */
101 static int
102 database_setup (struct Plugin *plugin)
103 {
104   PGresult *res;
105
106   plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg,
107                                          "namecache-postgres");
108   if (NULL == plugin->dbh)
109     return GNUNET_SYSERR;
110   if (GNUNET_YES ==
111       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
112                                             "namecache-postgres",
113                                             "TEMPORARY_TABLE"))
114   {
115     res =
116       PQexec (plugin->dbh,
117               "CREATE TEMPORARY TABLE ns096blocks ("
118               " query BYTEA NOT NULL DEFAULT '',"
119               " block BYTEA NOT NULL DEFAULT '',"
120               " expiration_time BIGINT NOT NULL DEFAULT 0"
121               ")" "WITH OIDS");
122   }
123   else
124   {
125     res =
126       PQexec (plugin->dbh,
127               "CREATE TABLE ns096blocks ("
128               " query BYTEA NOT NULL DEFAULT '',"
129               " block BYTEA NOT NULL DEFAULT '',"
130               " expiration_time BIGINT NOT NULL DEFAULT 0"
131               ")" "WITH OIDS");
132   }
133   if ( (NULL == res) ||
134        ((PQresultStatus (res) != PGRES_COMMAND_OK) &&
135         (0 != strcmp ("42P07",    /* duplicate table */
136                       PQresultErrorField
137                       (res,
138                        PG_DIAG_SQLSTATE)))))
139   {
140     (void) GNUNET_POSTGRES_check_result (plugin->dbh, res,
141                                          PGRES_COMMAND_OK, "CREATE TABLE",
142                                          "ns096blocks");
143     PQfinish (plugin->dbh);
144     plugin->dbh = NULL;
145     return GNUNET_SYSERR;
146   }
147   if (PQresultStatus (res) == PGRES_COMMAND_OK)
148     create_indices (plugin->dbh);
149   PQclear (res);
150
151   if ((GNUNET_OK !=
152        GNUNET_POSTGRES_prepare (plugin->dbh,
153                                 "cache_block",
154                                 "INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
155                                 "($1, $2, $3)", 3)) ||
156       (GNUNET_OK !=
157        GNUNET_POSTGRES_prepare (plugin->dbh,
158                                 "expire_blocks",
159                                 "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) ||
160       (GNUNET_OK !=
161        GNUNET_POSTGRES_prepare (plugin->dbh,
162                                 "delete_block",
163                                 "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) ||
164       (GNUNET_OK !=
165        GNUNET_POSTGRES_prepare (plugin->dbh,
166                                 "lookup_block",
167                                 "SELECT block FROM ns096blocks WHERE query=$1"
168                                 " ORDER BY expiration_time DESC LIMIT 1", 1)))
169   {
170     PQfinish (plugin->dbh);
171     plugin->dbh = NULL;
172     return GNUNET_SYSERR;
173   }
174   return GNUNET_OK;
175 }
176
177
178 /**
179  * Removes any expired block.
180  *
181  * @param plugin the plugin
182  */
183 static void
184 namecache_postgres_expire_blocks (struct Plugin *plugin)
185 {
186   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
187   struct GNUNET_TIME_AbsoluteNBO now_be = GNUNET_TIME_absolute_hton (now);
188   const char *paramValues[] = {
189     (const char *) &now_be
190   };
191   int paramLengths[] = {
192     sizeof (now_be)
193   };
194   const int paramFormats[] = { 1 };
195   PGresult *res;
196
197   res =
198     PQexecPrepared (plugin->dbh, "expire_blocks", 1,
199                     paramValues, paramLengths, paramFormats, 1);
200   if (GNUNET_OK !=
201       GNUNET_POSTGRES_check_result (plugin->dbh,
202                                     res,
203                                     PGRES_COMMAND_OK,
204                                     "PQexecPrepared",
205                                     "expire_blocks"))
206     return;
207   PQclear (res);
208 }
209
210
211 /**
212  * Delete older block in the datastore.
213  *
214  * @param the plugin
215  * @param query query for the block
216  * @param expiration time how old does the block have to be for deletion
217  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
218  */
219 static void
220 delete_old_block (struct Plugin *plugin,
221                   const struct GNUNET_HashCode *query,
222                   struct GNUNET_TIME_AbsoluteNBO expiration_time)
223 {
224   const char *paramValues[] = {
225     (const char *) query,
226     (const char *) &expiration_time
227   };
228   int paramLengths[] = {
229     sizeof (*query),
230     sizeof (expiration_time)
231   };
232   const int paramFormats[] = { 1, 1 };
233   PGresult *res;
234
235   res =
236     PQexecPrepared (plugin->dbh, "delete_block", 2,
237                     paramValues, paramLengths, paramFormats, 1);
238   if (GNUNET_OK !=
239       GNUNET_POSTGRES_check_result (plugin->dbh,
240                                     res,
241                                     PGRES_COMMAND_OK,
242                                     "PQexecPrepared",
243                                     "delete_block"))
244     return;
245   PQclear (res);
246 }
247
248
249 /**
250  * Cache a block in the datastore.
251  *
252  * @param cls closure (internal context for the plugin)
253  * @param block block to cache
254  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
255  */
256 static int
257 namecache_postgres_cache_block (void *cls,
258                                 const struct GNUNET_GNSRECORD_Block *block)
259 {
260   struct Plugin *plugin = cls;
261   struct GNUNET_HashCode query;
262   size_t block_size = ntohl (block->purpose.size) +
263     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
264     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
265   const char *paramValues[] = {
266     (const char *) &query,
267     (const char *) block,
268     (const char *) &block->expiration_time
269   };
270   int paramLengths[] = {
271     sizeof (query),
272     (int) block_size,
273     sizeof (block->expiration_time)
274   };
275   const int paramFormats[] = { 1, 1, 1 };
276   PGresult *res;
277
278   namecache_postgres_expire_blocks (plugin);
279   GNUNET_CRYPTO_hash (&block->derived_key,
280                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
281                       &query);
282   if (block_size > 64 * 65536)
283   {
284     GNUNET_break (0);
285     return GNUNET_SYSERR;
286   }
287   delete_old_block (plugin, &query, block->expiration_time);
288
289   res =
290     PQexecPrepared (plugin->dbh, "cache_block", 3,
291                     paramValues, paramLengths, paramFormats, 1);
292   if (GNUNET_OK !=
293       GNUNET_POSTGRES_check_result (plugin->dbh,
294                                     res,
295                                     PGRES_COMMAND_OK,
296                                     "PQexecPrepared",
297                                     "cache_block"))
298     return GNUNET_SYSERR;
299   PQclear (res);
300   return GNUNET_OK;
301 }
302
303
304 /**
305  * Get the block for a particular zone and label in the
306  * datastore.  Will return at most one result to the iterator.
307  *
308  * @param cls closure (internal context for the plugin)
309  * @param query hash of public key derived from the zone and the label
310  * @param iter function to call with the result
311  * @param iter_cls closure for @a iter
312  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
313  */
314 static int
315 namecache_postgres_lookup_block (void *cls,
316                                  const struct GNUNET_HashCode *query,
317                                  GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
318 {
319   struct Plugin *plugin = cls;
320   const char *paramValues[] = {
321     (const char *) query
322   };
323   int paramLengths[] = {
324     sizeof (*query)
325   };
326   const int paramFormats[] = { 1 };
327   PGresult *res;
328   unsigned int cnt;
329   size_t bsize;
330   const struct GNUNET_GNSRECORD_Block *block;
331
332   res = PQexecPrepared (plugin->dbh,
333                         "lookup_block", 1,
334                         paramValues, paramLengths, paramFormats,
335                         1);
336   if (GNUNET_OK !=
337       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK,
338                                     "PQexecPrepared",
339                                     "lookup_block"))
340   {
341     LOG (GNUNET_ERROR_TYPE_DEBUG,
342          "Failing lookup (postgres error)\n");
343     return GNUNET_SYSERR;
344   }
345   if (0 == (cnt = PQntuples (res)))
346   {
347     /* no result */
348     LOG (GNUNET_ERROR_TYPE_DEBUG,
349          "Ending iteration (no more results)\n");
350     PQclear (res);
351     return GNUNET_NO;
352   }
353   GNUNET_assert (1 == cnt);
354   GNUNET_assert (1 != PQnfields (res));
355   bsize = PQgetlength (res, 0, 0);
356   block = (const struct GNUNET_GNSRECORD_Block *) PQgetvalue (res, 0, 0);
357   if ( (bsize < sizeof (*block)) ||
358        (bsize != ntohl (block->purpose.size) +
359         sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
360         sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) )
361   {
362     GNUNET_break (0);
363     LOG (GNUNET_ERROR_TYPE_DEBUG,
364          "Failing lookup (corrupt block)\n");
365     PQclear (res);
366     return GNUNET_SYSERR;
367   }
368   iter (iter_cls, block);
369   PQclear (res);
370   return GNUNET_OK;
371 }
372
373
374 /**
375  * Shutdown database connection and associate data
376  * structures.
377  *
378  * @param plugin the plugin context (state for this module)
379  */
380 static void
381 database_shutdown (struct Plugin *plugin)
382 {
383   PQfinish (plugin->dbh);
384   plugin->dbh = NULL;
385 }
386
387
388 /**
389  * Entry point for the plugin.
390  *
391  * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
392  * @return NULL on error, othrewise the plugin context
393  */
394 void *
395 libgnunet_plugin_namecache_postgres_init (void *cls)
396 {
397   static struct Plugin plugin;
398   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
399   struct GNUNET_NAMECACHE_PluginFunctions *api;
400
401   if (NULL != plugin.cfg)
402     return NULL;                /* can only initialize once! */
403   memset (&plugin, 0, sizeof (struct Plugin));
404   plugin.cfg = cfg;
405   if (GNUNET_OK != database_setup (&plugin))
406   {
407     database_shutdown (&plugin);
408     return NULL;
409   }
410   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
411   api->cls = &plugin;
412   api->cache_block = &namecache_postgres_cache_block;
413   api->lookup_block = &namecache_postgres_lookup_block;
414   LOG (GNUNET_ERROR_TYPE_INFO,
415        _("Postgres database running\n"));
416   return api;
417 }
418
419
420 /**
421  * Exit point from the plugin.
422  *
423  * @param cls the plugin context (as returned by "init")
424  * @return always NULL
425  */
426 void *
427 libgnunet_plugin_namecache_postgres_done (void *cls)
428 {
429   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
430   struct Plugin *plugin = api->cls;
431
432   database_shutdown (plugin);
433   plugin->cfg = NULL;
434   GNUNET_free (api);
435   LOG (GNUNET_ERROR_TYPE_DEBUG,
436        "postgres plugin is finished\n");
437   return NULL;
438 }
439
440 /* end of plugin_namecache_postgres.c */